1*28004fb7SRayhan Faizel /* 2*28004fb7SRayhan Faizel * BCM2835 SPI Master Controller 3*28004fb7SRayhan Faizel * 4*28004fb7SRayhan Faizel * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com> 5*28004fb7SRayhan Faizel * 6*28004fb7SRayhan Faizel * Permission is hereby granted, free of charge, to any person obtaining a copy 7*28004fb7SRayhan Faizel * of this software and associated documentation files (the "Software"), to deal 8*28004fb7SRayhan Faizel * in the Software without restriction, including without limitation the rights 9*28004fb7SRayhan Faizel * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10*28004fb7SRayhan Faizel * copies of the Software, and to permit persons to whom the Software is 11*28004fb7SRayhan Faizel * furnished to do so, subject to the following conditions: 12*28004fb7SRayhan Faizel * 13*28004fb7SRayhan Faizel * The above copyright notice and this permission notice shall be included in 14*28004fb7SRayhan Faizel * all copies or substantial portions of the Software. 15*28004fb7SRayhan Faizel * 16*28004fb7SRayhan Faizel * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17*28004fb7SRayhan Faizel * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18*28004fb7SRayhan Faizel * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19*28004fb7SRayhan Faizel * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20*28004fb7SRayhan Faizel * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21*28004fb7SRayhan Faizel * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22*28004fb7SRayhan Faizel * THE SOFTWARE. 23*28004fb7SRayhan Faizel */ 24*28004fb7SRayhan Faizel 25*28004fb7SRayhan Faizel #include "qemu/osdep.h" 26*28004fb7SRayhan Faizel #include "qemu/log.h" 27*28004fb7SRayhan Faizel #include "qemu/fifo8.h" 28*28004fb7SRayhan Faizel #include "hw/ssi/bcm2835_spi.h" 29*28004fb7SRayhan Faizel #include "hw/irq.h" 30*28004fb7SRayhan Faizel #include "migration/vmstate.h" 31*28004fb7SRayhan Faizel 32*28004fb7SRayhan Faizel static void bcm2835_spi_update_int(BCM2835SPIState *s) 33*28004fb7SRayhan Faizel { 34*28004fb7SRayhan Faizel int do_interrupt = 0; 35*28004fb7SRayhan Faizel 36*28004fb7SRayhan Faizel /* Interrupt on DONE */ 37*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_INTD && s->cs & BCM2835_SPI_CS_DONE) { 38*28004fb7SRayhan Faizel do_interrupt = 1; 39*28004fb7SRayhan Faizel } 40*28004fb7SRayhan Faizel /* Interrupt on RXR */ 41*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_INTR && s->cs & BCM2835_SPI_CS_RXR) { 42*28004fb7SRayhan Faizel do_interrupt = 1; 43*28004fb7SRayhan Faizel } 44*28004fb7SRayhan Faizel qemu_set_irq(s->irq, do_interrupt); 45*28004fb7SRayhan Faizel } 46*28004fb7SRayhan Faizel 47*28004fb7SRayhan Faizel static void bcm2835_spi_update_rx_flags(BCM2835SPIState *s) 48*28004fb7SRayhan Faizel { 49*28004fb7SRayhan Faizel /* Set RXD if RX FIFO is non empty */ 50*28004fb7SRayhan Faizel if (!fifo8_is_empty(&s->rx_fifo)) { 51*28004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_RXD; 52*28004fb7SRayhan Faizel } else { 53*28004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_RXD; 54*28004fb7SRayhan Faizel } 55*28004fb7SRayhan Faizel 56*28004fb7SRayhan Faizel /* Set RXF if RX FIFO is full */ 57*28004fb7SRayhan Faizel if (fifo8_is_full(&s->rx_fifo)) { 58*28004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_RXF; 59*28004fb7SRayhan Faizel } else { 60*28004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_RXF; 61*28004fb7SRayhan Faizel } 62*28004fb7SRayhan Faizel 63*28004fb7SRayhan Faizel /* Set RXR if RX FIFO is 3/4th used or above */ 64*28004fb7SRayhan Faizel if (fifo8_num_used(&s->rx_fifo) >= FIFO_SIZE_3_4) { 65*28004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_RXR; 66*28004fb7SRayhan Faizel } else { 67*28004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_RXR; 68*28004fb7SRayhan Faizel } 69*28004fb7SRayhan Faizel } 70*28004fb7SRayhan Faizel 71*28004fb7SRayhan Faizel static void bcm2835_spi_update_tx_flags(BCM2835SPIState *s) 72*28004fb7SRayhan Faizel { 73*28004fb7SRayhan Faizel /* Set TXD if TX FIFO is not full */ 74*28004fb7SRayhan Faizel if (fifo8_is_full(&s->tx_fifo)) { 75*28004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_TXD; 76*28004fb7SRayhan Faizel } else { 77*28004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_TXD; 78*28004fb7SRayhan Faizel } 79*28004fb7SRayhan Faizel 80*28004fb7SRayhan Faizel /* Set DONE if in TA mode and TX FIFO is empty */ 81*28004fb7SRayhan Faizel if (fifo8_is_empty(&s->tx_fifo) && s->cs & BCM2835_SPI_CS_TA) { 82*28004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_DONE; 83*28004fb7SRayhan Faizel } else { 84*28004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_DONE; 85*28004fb7SRayhan Faizel } 86*28004fb7SRayhan Faizel } 87*28004fb7SRayhan Faizel 88*28004fb7SRayhan Faizel static void bcm2835_spi_flush_tx_fifo(BCM2835SPIState *s) 89*28004fb7SRayhan Faizel { 90*28004fb7SRayhan Faizel uint8_t tx_byte, rx_byte; 91*28004fb7SRayhan Faizel 92*28004fb7SRayhan Faizel while (!fifo8_is_empty(&s->tx_fifo) && !fifo8_is_full(&s->rx_fifo)) { 93*28004fb7SRayhan Faizel tx_byte = fifo8_pop(&s->tx_fifo); 94*28004fb7SRayhan Faizel rx_byte = ssi_transfer(s->bus, tx_byte); 95*28004fb7SRayhan Faizel fifo8_push(&s->rx_fifo, rx_byte); 96*28004fb7SRayhan Faizel } 97*28004fb7SRayhan Faizel 98*28004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 99*28004fb7SRayhan Faizel bcm2835_spi_update_rx_flags(s); 100*28004fb7SRayhan Faizel } 101*28004fb7SRayhan Faizel 102*28004fb7SRayhan Faizel static uint64_t bcm2835_spi_read(void *opaque, hwaddr addr, unsigned size) 103*28004fb7SRayhan Faizel { 104*28004fb7SRayhan Faizel BCM2835SPIState *s = opaque; 105*28004fb7SRayhan Faizel uint32_t readval = 0; 106*28004fb7SRayhan Faizel 107*28004fb7SRayhan Faizel switch (addr) { 108*28004fb7SRayhan Faizel case BCM2835_SPI_CS: 109*28004fb7SRayhan Faizel readval = s->cs & 0xffffffff; 110*28004fb7SRayhan Faizel break; 111*28004fb7SRayhan Faizel case BCM2835_SPI_FIFO: 112*28004fb7SRayhan Faizel bcm2835_spi_flush_tx_fifo(s); 113*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_RXD) { 114*28004fb7SRayhan Faizel readval = fifo8_pop(&s->rx_fifo); 115*28004fb7SRayhan Faizel bcm2835_spi_update_rx_flags(s); 116*28004fb7SRayhan Faizel } 117*28004fb7SRayhan Faizel 118*28004fb7SRayhan Faizel bcm2835_spi_update_int(s); 119*28004fb7SRayhan Faizel break; 120*28004fb7SRayhan Faizel case BCM2835_SPI_CLK: 121*28004fb7SRayhan Faizel readval = s->clk & 0xffff; 122*28004fb7SRayhan Faizel break; 123*28004fb7SRayhan Faizel case BCM2835_SPI_DLEN: 124*28004fb7SRayhan Faizel readval = s->dlen & 0xffff; 125*28004fb7SRayhan Faizel break; 126*28004fb7SRayhan Faizel case BCM2835_SPI_LTOH: 127*28004fb7SRayhan Faizel readval = s->ltoh & 0xf; 128*28004fb7SRayhan Faizel break; 129*28004fb7SRayhan Faizel case BCM2835_SPI_DC: 130*28004fb7SRayhan Faizel readval = s->dc & 0xffffffff; 131*28004fb7SRayhan Faizel break; 132*28004fb7SRayhan Faizel default: 133*28004fb7SRayhan Faizel qemu_log_mask(LOG_GUEST_ERROR, 134*28004fb7SRayhan Faizel "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); 135*28004fb7SRayhan Faizel } 136*28004fb7SRayhan Faizel return readval; 137*28004fb7SRayhan Faizel } 138*28004fb7SRayhan Faizel 139*28004fb7SRayhan Faizel static void bcm2835_spi_write(void *opaque, hwaddr addr, 140*28004fb7SRayhan Faizel uint64_t value, unsigned int size) 141*28004fb7SRayhan Faizel { 142*28004fb7SRayhan Faizel BCM2835SPIState *s = opaque; 143*28004fb7SRayhan Faizel 144*28004fb7SRayhan Faizel switch (addr) { 145*28004fb7SRayhan Faizel case BCM2835_SPI_CS: 146*28004fb7SRayhan Faizel s->cs = (value & ~RO_MASK) | (s->cs & RO_MASK); 147*28004fb7SRayhan Faizel if (!(s->cs & BCM2835_SPI_CS_TA)) { 148*28004fb7SRayhan Faizel /* Clear DONE and RXR if TA is off */ 149*28004fb7SRayhan Faizel s->cs &= ~(BCM2835_SPI_CS_DONE); 150*28004fb7SRayhan Faizel s->cs &= ~(BCM2835_SPI_CS_RXR); 151*28004fb7SRayhan Faizel } 152*28004fb7SRayhan Faizel 153*28004fb7SRayhan Faizel /* Clear RX FIFO */ 154*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CLEAR_RX) { 155*28004fb7SRayhan Faizel fifo8_reset(&s->rx_fifo); 156*28004fb7SRayhan Faizel bcm2835_spi_update_rx_flags(s); 157*28004fb7SRayhan Faizel } 158*28004fb7SRayhan Faizel 159*28004fb7SRayhan Faizel /* Clear TX FIFO*/ 160*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CLEAR_TX) { 161*28004fb7SRayhan Faizel fifo8_reset(&s->tx_fifo); 162*28004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 163*28004fb7SRayhan Faizel } 164*28004fb7SRayhan Faizel 165*28004fb7SRayhan Faizel /* Set Transfer Active */ 166*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_TA) { 167*28004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 168*28004fb7SRayhan Faizel } 169*28004fb7SRayhan Faizel 170*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_DMAEN) { 171*28004fb7SRayhan Faizel qemu_log_mask(LOG_UNIMP, "%s: " \ 172*28004fb7SRayhan Faizel "DMA not supported\n", __func__); 173*28004fb7SRayhan Faizel } 174*28004fb7SRayhan Faizel 175*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_LEN) { 176*28004fb7SRayhan Faizel qemu_log_mask(LOG_UNIMP, "%s: " \ 177*28004fb7SRayhan Faizel "LoSSI not supported\n", __func__); 178*28004fb7SRayhan Faizel } 179*28004fb7SRayhan Faizel 180*28004fb7SRayhan Faizel bcm2835_spi_update_int(s); 181*28004fb7SRayhan Faizel break; 182*28004fb7SRayhan Faizel case BCM2835_SPI_FIFO: 183*28004fb7SRayhan Faizel /* 184*28004fb7SRayhan Faizel * According to documentation, writes to FIFO without TA controls 185*28004fb7SRayhan Faizel * CS and DLEN registers. This is supposed to be used in DMA mode 186*28004fb7SRayhan Faizel * which is currently unimplemented. Moreover, Linux does not make 187*28004fb7SRayhan Faizel * use of this and directly modifies the CS and DLEN registers. 188*28004fb7SRayhan Faizel */ 189*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_TA) { 190*28004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_TXD) { 191*28004fb7SRayhan Faizel fifo8_push(&s->tx_fifo, value & 0xff); 192*28004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 193*28004fb7SRayhan Faizel } 194*28004fb7SRayhan Faizel 195*28004fb7SRayhan Faizel bcm2835_spi_flush_tx_fifo(s); 196*28004fb7SRayhan Faizel bcm2835_spi_update_int(s); 197*28004fb7SRayhan Faizel } 198*28004fb7SRayhan Faizel break; 199*28004fb7SRayhan Faizel case BCM2835_SPI_CLK: 200*28004fb7SRayhan Faizel s->clk = value & 0xffff; 201*28004fb7SRayhan Faizel break; 202*28004fb7SRayhan Faizel case BCM2835_SPI_DLEN: 203*28004fb7SRayhan Faizel s->dlen = value & 0xffff; 204*28004fb7SRayhan Faizel break; 205*28004fb7SRayhan Faizel case BCM2835_SPI_LTOH: 206*28004fb7SRayhan Faizel s->ltoh = value & 0xf; 207*28004fb7SRayhan Faizel break; 208*28004fb7SRayhan Faizel case BCM2835_SPI_DC: 209*28004fb7SRayhan Faizel s->dc = value & 0xffffffff; 210*28004fb7SRayhan Faizel break; 211*28004fb7SRayhan Faizel default: 212*28004fb7SRayhan Faizel qemu_log_mask(LOG_GUEST_ERROR, 213*28004fb7SRayhan Faizel "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); 214*28004fb7SRayhan Faizel } 215*28004fb7SRayhan Faizel } 216*28004fb7SRayhan Faizel 217*28004fb7SRayhan Faizel static const MemoryRegionOps bcm2835_spi_ops = { 218*28004fb7SRayhan Faizel .read = bcm2835_spi_read, 219*28004fb7SRayhan Faizel .write = bcm2835_spi_write, 220*28004fb7SRayhan Faizel .endianness = DEVICE_NATIVE_ENDIAN, 221*28004fb7SRayhan Faizel }; 222*28004fb7SRayhan Faizel 223*28004fb7SRayhan Faizel static void bcm2835_spi_realize(DeviceState *dev, Error **errp) 224*28004fb7SRayhan Faizel { 225*28004fb7SRayhan Faizel BCM2835SPIState *s = BCM2835_SPI(dev); 226*28004fb7SRayhan Faizel s->bus = ssi_create_bus(dev, "spi"); 227*28004fb7SRayhan Faizel 228*28004fb7SRayhan Faizel memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_spi_ops, s, 229*28004fb7SRayhan Faizel TYPE_BCM2835_SPI, 0x18); 230*28004fb7SRayhan Faizel sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); 231*28004fb7SRayhan Faizel sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); 232*28004fb7SRayhan Faizel 233*28004fb7SRayhan Faizel fifo8_create(&s->tx_fifo, FIFO_SIZE); 234*28004fb7SRayhan Faizel fifo8_create(&s->rx_fifo, FIFO_SIZE); 235*28004fb7SRayhan Faizel } 236*28004fb7SRayhan Faizel static void bcm2835_spi_reset(DeviceState *dev) 237*28004fb7SRayhan Faizel { 238*28004fb7SRayhan Faizel BCM2835SPIState *s = BCM2835_SPI(dev); 239*28004fb7SRayhan Faizel 240*28004fb7SRayhan Faizel fifo8_reset(&s->tx_fifo); 241*28004fb7SRayhan Faizel fifo8_reset(&s->rx_fifo); 242*28004fb7SRayhan Faizel 243*28004fb7SRayhan Faizel /* Reset values according to BCM2835 Peripheral Documentation */ 244*28004fb7SRayhan Faizel s->cs = BCM2835_SPI_CS_TXD | BCM2835_SPI_CS_REN; 245*28004fb7SRayhan Faizel s->clk = 0; 246*28004fb7SRayhan Faizel s->dlen = 0; 247*28004fb7SRayhan Faizel s->ltoh = 0x1; 248*28004fb7SRayhan Faizel s->dc = 0x30201020; 249*28004fb7SRayhan Faizel } 250*28004fb7SRayhan Faizel 251*28004fb7SRayhan Faizel static const VMStateDescription vmstate_bcm2835_spi = { 252*28004fb7SRayhan Faizel .name = TYPE_BCM2835_SPI, 253*28004fb7SRayhan Faizel .version_id = 1, 254*28004fb7SRayhan Faizel .minimum_version_id = 1, 255*28004fb7SRayhan Faizel .fields = (const VMStateField[]) { 256*28004fb7SRayhan Faizel VMSTATE_FIFO8(tx_fifo, BCM2835SPIState), 257*28004fb7SRayhan Faizel VMSTATE_FIFO8(rx_fifo, BCM2835SPIState), 258*28004fb7SRayhan Faizel VMSTATE_UINT32(cs, BCM2835SPIState), 259*28004fb7SRayhan Faizel VMSTATE_UINT32(clk, BCM2835SPIState), 260*28004fb7SRayhan Faizel VMSTATE_UINT32(dlen, BCM2835SPIState), 261*28004fb7SRayhan Faizel VMSTATE_UINT32(ltoh, BCM2835SPIState), 262*28004fb7SRayhan Faizel VMSTATE_UINT32(dc, BCM2835SPIState), 263*28004fb7SRayhan Faizel VMSTATE_END_OF_LIST() 264*28004fb7SRayhan Faizel } 265*28004fb7SRayhan Faizel }; 266*28004fb7SRayhan Faizel 267*28004fb7SRayhan Faizel static void bcm2835_spi_class_init(ObjectClass *klass, void *data) 268*28004fb7SRayhan Faizel { 269*28004fb7SRayhan Faizel DeviceClass *dc = DEVICE_CLASS(klass); 270*28004fb7SRayhan Faizel 271*28004fb7SRayhan Faizel dc->reset = bcm2835_spi_reset; 272*28004fb7SRayhan Faizel dc->realize = bcm2835_spi_realize; 273*28004fb7SRayhan Faizel dc->vmsd = &vmstate_bcm2835_spi; 274*28004fb7SRayhan Faizel } 275*28004fb7SRayhan Faizel 276*28004fb7SRayhan Faizel static const TypeInfo bcm2835_spi_info = { 277*28004fb7SRayhan Faizel .name = TYPE_BCM2835_SPI, 278*28004fb7SRayhan Faizel .parent = TYPE_SYS_BUS_DEVICE, 279*28004fb7SRayhan Faizel .instance_size = sizeof(BCM2835SPIState), 280*28004fb7SRayhan Faizel .class_init = bcm2835_spi_class_init, 281*28004fb7SRayhan Faizel }; 282*28004fb7SRayhan Faizel 283*28004fb7SRayhan Faizel static void bcm2835_spi_register_types(void) 284*28004fb7SRayhan Faizel { 285*28004fb7SRayhan Faizel type_register_static(&bcm2835_spi_info); 286*28004fb7SRayhan Faizel } 287*28004fb7SRayhan Faizel 288*28004fb7SRayhan Faizel type_init(bcm2835_spi_register_types) 289