128004fb7SRayhan Faizel /* 228004fb7SRayhan Faizel * BCM2835 SPI Master Controller 328004fb7SRayhan Faizel * 428004fb7SRayhan Faizel * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com> 528004fb7SRayhan Faizel * 628004fb7SRayhan Faizel * Permission is hereby granted, free of charge, to any person obtaining a copy 728004fb7SRayhan Faizel * of this software and associated documentation files (the "Software"), to deal 828004fb7SRayhan Faizel * in the Software without restriction, including without limitation the rights 928004fb7SRayhan Faizel * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1028004fb7SRayhan Faizel * copies of the Software, and to permit persons to whom the Software is 1128004fb7SRayhan Faizel * furnished to do so, subject to the following conditions: 1228004fb7SRayhan Faizel * 1328004fb7SRayhan Faizel * The above copyright notice and this permission notice shall be included in 1428004fb7SRayhan Faizel * all copies or substantial portions of the Software. 1528004fb7SRayhan Faizel * 1628004fb7SRayhan Faizel * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1728004fb7SRayhan Faizel * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1828004fb7SRayhan Faizel * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 1928004fb7SRayhan Faizel * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 2028004fb7SRayhan Faizel * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 2128004fb7SRayhan Faizel * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 2228004fb7SRayhan Faizel * THE SOFTWARE. 2328004fb7SRayhan Faizel */ 2428004fb7SRayhan Faizel 2528004fb7SRayhan Faizel #include "qemu/osdep.h" 2628004fb7SRayhan Faizel #include "qemu/log.h" 2728004fb7SRayhan Faizel #include "qemu/fifo8.h" 2828004fb7SRayhan Faizel #include "hw/ssi/bcm2835_spi.h" 2928004fb7SRayhan Faizel #include "hw/irq.h" 3028004fb7SRayhan Faizel #include "migration/vmstate.h" 3128004fb7SRayhan Faizel 3228004fb7SRayhan Faizel static void bcm2835_spi_update_int(BCM2835SPIState *s) 3328004fb7SRayhan Faizel { 3428004fb7SRayhan Faizel int do_interrupt = 0; 3528004fb7SRayhan Faizel 3628004fb7SRayhan Faizel /* Interrupt on DONE */ 3728004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_INTD && s->cs & BCM2835_SPI_CS_DONE) { 3828004fb7SRayhan Faizel do_interrupt = 1; 3928004fb7SRayhan Faizel } 4028004fb7SRayhan Faizel /* Interrupt on RXR */ 4128004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_INTR && s->cs & BCM2835_SPI_CS_RXR) { 4228004fb7SRayhan Faizel do_interrupt = 1; 4328004fb7SRayhan Faizel } 4428004fb7SRayhan Faizel qemu_set_irq(s->irq, do_interrupt); 4528004fb7SRayhan Faizel } 4628004fb7SRayhan Faizel 4728004fb7SRayhan Faizel static void bcm2835_spi_update_rx_flags(BCM2835SPIState *s) 4828004fb7SRayhan Faizel { 4928004fb7SRayhan Faizel /* Set RXD if RX FIFO is non empty */ 5028004fb7SRayhan Faizel if (!fifo8_is_empty(&s->rx_fifo)) { 5128004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_RXD; 5228004fb7SRayhan Faizel } else { 5328004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_RXD; 5428004fb7SRayhan Faizel } 5528004fb7SRayhan Faizel 5628004fb7SRayhan Faizel /* Set RXF if RX FIFO is full */ 5728004fb7SRayhan Faizel if (fifo8_is_full(&s->rx_fifo)) { 5828004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_RXF; 5928004fb7SRayhan Faizel } else { 6028004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_RXF; 6128004fb7SRayhan Faizel } 6228004fb7SRayhan Faizel 6328004fb7SRayhan Faizel /* Set RXR if RX FIFO is 3/4th used or above */ 6428004fb7SRayhan Faizel if (fifo8_num_used(&s->rx_fifo) >= FIFO_SIZE_3_4) { 6528004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_RXR; 6628004fb7SRayhan Faizel } else { 6728004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_RXR; 6828004fb7SRayhan Faizel } 6928004fb7SRayhan Faizel } 7028004fb7SRayhan Faizel 7128004fb7SRayhan Faizel static void bcm2835_spi_update_tx_flags(BCM2835SPIState *s) 7228004fb7SRayhan Faizel { 7328004fb7SRayhan Faizel /* Set TXD if TX FIFO is not full */ 7428004fb7SRayhan Faizel if (fifo8_is_full(&s->tx_fifo)) { 7528004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_TXD; 7628004fb7SRayhan Faizel } else { 7728004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_TXD; 7828004fb7SRayhan Faizel } 7928004fb7SRayhan Faizel 8028004fb7SRayhan Faizel /* Set DONE if in TA mode and TX FIFO is empty */ 8128004fb7SRayhan Faizel if (fifo8_is_empty(&s->tx_fifo) && s->cs & BCM2835_SPI_CS_TA) { 8228004fb7SRayhan Faizel s->cs |= BCM2835_SPI_CS_DONE; 8328004fb7SRayhan Faizel } else { 8428004fb7SRayhan Faizel s->cs &= ~BCM2835_SPI_CS_DONE; 8528004fb7SRayhan Faizel } 8628004fb7SRayhan Faizel } 8728004fb7SRayhan Faizel 8828004fb7SRayhan Faizel static void bcm2835_spi_flush_tx_fifo(BCM2835SPIState *s) 8928004fb7SRayhan Faizel { 9028004fb7SRayhan Faizel uint8_t tx_byte, rx_byte; 9128004fb7SRayhan Faizel 9228004fb7SRayhan Faizel while (!fifo8_is_empty(&s->tx_fifo) && !fifo8_is_full(&s->rx_fifo)) { 9328004fb7SRayhan Faizel tx_byte = fifo8_pop(&s->tx_fifo); 9428004fb7SRayhan Faizel rx_byte = ssi_transfer(s->bus, tx_byte); 9528004fb7SRayhan Faizel fifo8_push(&s->rx_fifo, rx_byte); 9628004fb7SRayhan Faizel } 9728004fb7SRayhan Faizel 9828004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 9928004fb7SRayhan Faizel bcm2835_spi_update_rx_flags(s); 10028004fb7SRayhan Faizel } 10128004fb7SRayhan Faizel 10228004fb7SRayhan Faizel static uint64_t bcm2835_spi_read(void *opaque, hwaddr addr, unsigned size) 10328004fb7SRayhan Faizel { 10428004fb7SRayhan Faizel BCM2835SPIState *s = opaque; 10528004fb7SRayhan Faizel uint32_t readval = 0; 10628004fb7SRayhan Faizel 10728004fb7SRayhan Faizel switch (addr) { 10828004fb7SRayhan Faizel case BCM2835_SPI_CS: 10928004fb7SRayhan Faizel readval = s->cs & 0xffffffff; 11028004fb7SRayhan Faizel break; 11128004fb7SRayhan Faizel case BCM2835_SPI_FIFO: 11228004fb7SRayhan Faizel bcm2835_spi_flush_tx_fifo(s); 11328004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_RXD) { 11428004fb7SRayhan Faizel readval = fifo8_pop(&s->rx_fifo); 11528004fb7SRayhan Faizel bcm2835_spi_update_rx_flags(s); 11628004fb7SRayhan Faizel } 11728004fb7SRayhan Faizel 11828004fb7SRayhan Faizel bcm2835_spi_update_int(s); 11928004fb7SRayhan Faizel break; 12028004fb7SRayhan Faizel case BCM2835_SPI_CLK: 12128004fb7SRayhan Faizel readval = s->clk & 0xffff; 12228004fb7SRayhan Faizel break; 12328004fb7SRayhan Faizel case BCM2835_SPI_DLEN: 12428004fb7SRayhan Faizel readval = s->dlen & 0xffff; 12528004fb7SRayhan Faizel break; 12628004fb7SRayhan Faizel case BCM2835_SPI_LTOH: 12728004fb7SRayhan Faizel readval = s->ltoh & 0xf; 12828004fb7SRayhan Faizel break; 12928004fb7SRayhan Faizel case BCM2835_SPI_DC: 13028004fb7SRayhan Faizel readval = s->dc & 0xffffffff; 13128004fb7SRayhan Faizel break; 13228004fb7SRayhan Faizel default: 13328004fb7SRayhan Faizel qemu_log_mask(LOG_GUEST_ERROR, 13428004fb7SRayhan Faizel "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); 13528004fb7SRayhan Faizel } 13628004fb7SRayhan Faizel return readval; 13728004fb7SRayhan Faizel } 13828004fb7SRayhan Faizel 13928004fb7SRayhan Faizel static void bcm2835_spi_write(void *opaque, hwaddr addr, 14028004fb7SRayhan Faizel uint64_t value, unsigned int size) 14128004fb7SRayhan Faizel { 14228004fb7SRayhan Faizel BCM2835SPIState *s = opaque; 14328004fb7SRayhan Faizel 14428004fb7SRayhan Faizel switch (addr) { 14528004fb7SRayhan Faizel case BCM2835_SPI_CS: 14628004fb7SRayhan Faizel s->cs = (value & ~RO_MASK) | (s->cs & RO_MASK); 14728004fb7SRayhan Faizel if (!(s->cs & BCM2835_SPI_CS_TA)) { 14828004fb7SRayhan Faizel /* Clear DONE and RXR if TA is off */ 14928004fb7SRayhan Faizel s->cs &= ~(BCM2835_SPI_CS_DONE); 15028004fb7SRayhan Faizel s->cs &= ~(BCM2835_SPI_CS_RXR); 15128004fb7SRayhan Faizel } 15228004fb7SRayhan Faizel 15328004fb7SRayhan Faizel /* Clear RX FIFO */ 15428004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CLEAR_RX) { 15528004fb7SRayhan Faizel fifo8_reset(&s->rx_fifo); 15628004fb7SRayhan Faizel bcm2835_spi_update_rx_flags(s); 15728004fb7SRayhan Faizel } 15828004fb7SRayhan Faizel 15928004fb7SRayhan Faizel /* Clear TX FIFO*/ 16028004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CLEAR_TX) { 16128004fb7SRayhan Faizel fifo8_reset(&s->tx_fifo); 16228004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 16328004fb7SRayhan Faizel } 16428004fb7SRayhan Faizel 16528004fb7SRayhan Faizel /* Set Transfer Active */ 16628004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_TA) { 16728004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 16828004fb7SRayhan Faizel } 16928004fb7SRayhan Faizel 17028004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_DMAEN) { 17128004fb7SRayhan Faizel qemu_log_mask(LOG_UNIMP, "%s: " \ 17228004fb7SRayhan Faizel "DMA not supported\n", __func__); 17328004fb7SRayhan Faizel } 17428004fb7SRayhan Faizel 17528004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_LEN) { 17628004fb7SRayhan Faizel qemu_log_mask(LOG_UNIMP, "%s: " \ 17728004fb7SRayhan Faizel "LoSSI not supported\n", __func__); 17828004fb7SRayhan Faizel } 17928004fb7SRayhan Faizel 18028004fb7SRayhan Faizel bcm2835_spi_update_int(s); 18128004fb7SRayhan Faizel break; 18228004fb7SRayhan Faizel case BCM2835_SPI_FIFO: 18328004fb7SRayhan Faizel /* 18428004fb7SRayhan Faizel * According to documentation, writes to FIFO without TA controls 18528004fb7SRayhan Faizel * CS and DLEN registers. This is supposed to be used in DMA mode 18628004fb7SRayhan Faizel * which is currently unimplemented. Moreover, Linux does not make 18728004fb7SRayhan Faizel * use of this and directly modifies the CS and DLEN registers. 18828004fb7SRayhan Faizel */ 18928004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_TA) { 19028004fb7SRayhan Faizel if (s->cs & BCM2835_SPI_CS_TXD) { 19128004fb7SRayhan Faizel fifo8_push(&s->tx_fifo, value & 0xff); 19228004fb7SRayhan Faizel bcm2835_spi_update_tx_flags(s); 19328004fb7SRayhan Faizel } 19428004fb7SRayhan Faizel 19528004fb7SRayhan Faizel bcm2835_spi_flush_tx_fifo(s); 19628004fb7SRayhan Faizel bcm2835_spi_update_int(s); 19728004fb7SRayhan Faizel } 19828004fb7SRayhan Faizel break; 19928004fb7SRayhan Faizel case BCM2835_SPI_CLK: 20028004fb7SRayhan Faizel s->clk = value & 0xffff; 20128004fb7SRayhan Faizel break; 20228004fb7SRayhan Faizel case BCM2835_SPI_DLEN: 20328004fb7SRayhan Faizel s->dlen = value & 0xffff; 20428004fb7SRayhan Faizel break; 20528004fb7SRayhan Faizel case BCM2835_SPI_LTOH: 20628004fb7SRayhan Faizel s->ltoh = value & 0xf; 20728004fb7SRayhan Faizel break; 20828004fb7SRayhan Faizel case BCM2835_SPI_DC: 20928004fb7SRayhan Faizel s->dc = value & 0xffffffff; 21028004fb7SRayhan Faizel break; 21128004fb7SRayhan Faizel default: 21228004fb7SRayhan Faizel qemu_log_mask(LOG_GUEST_ERROR, 21328004fb7SRayhan Faizel "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr); 21428004fb7SRayhan Faizel } 21528004fb7SRayhan Faizel } 21628004fb7SRayhan Faizel 21728004fb7SRayhan Faizel static const MemoryRegionOps bcm2835_spi_ops = { 21828004fb7SRayhan Faizel .read = bcm2835_spi_read, 21928004fb7SRayhan Faizel .write = bcm2835_spi_write, 22028004fb7SRayhan Faizel .endianness = DEVICE_NATIVE_ENDIAN, 22128004fb7SRayhan Faizel }; 22228004fb7SRayhan Faizel 22328004fb7SRayhan Faizel static void bcm2835_spi_realize(DeviceState *dev, Error **errp) 22428004fb7SRayhan Faizel { 22528004fb7SRayhan Faizel BCM2835SPIState *s = BCM2835_SPI(dev); 22628004fb7SRayhan Faizel s->bus = ssi_create_bus(dev, "spi"); 22728004fb7SRayhan Faizel 22828004fb7SRayhan Faizel memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_spi_ops, s, 22928004fb7SRayhan Faizel TYPE_BCM2835_SPI, 0x18); 23028004fb7SRayhan Faizel sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); 23128004fb7SRayhan Faizel sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); 23228004fb7SRayhan Faizel 23328004fb7SRayhan Faizel fifo8_create(&s->tx_fifo, FIFO_SIZE); 23428004fb7SRayhan Faizel fifo8_create(&s->rx_fifo, FIFO_SIZE); 23528004fb7SRayhan Faizel } 23628004fb7SRayhan Faizel static void bcm2835_spi_reset(DeviceState *dev) 23728004fb7SRayhan Faizel { 23828004fb7SRayhan Faizel BCM2835SPIState *s = BCM2835_SPI(dev); 23928004fb7SRayhan Faizel 24028004fb7SRayhan Faizel fifo8_reset(&s->tx_fifo); 24128004fb7SRayhan Faizel fifo8_reset(&s->rx_fifo); 24228004fb7SRayhan Faizel 24328004fb7SRayhan Faizel /* Reset values according to BCM2835 Peripheral Documentation */ 24428004fb7SRayhan Faizel s->cs = BCM2835_SPI_CS_TXD | BCM2835_SPI_CS_REN; 24528004fb7SRayhan Faizel s->clk = 0; 24628004fb7SRayhan Faizel s->dlen = 0; 24728004fb7SRayhan Faizel s->ltoh = 0x1; 24828004fb7SRayhan Faizel s->dc = 0x30201020; 24928004fb7SRayhan Faizel } 25028004fb7SRayhan Faizel 25128004fb7SRayhan Faizel static const VMStateDescription vmstate_bcm2835_spi = { 25228004fb7SRayhan Faizel .name = TYPE_BCM2835_SPI, 25328004fb7SRayhan Faizel .version_id = 1, 25428004fb7SRayhan Faizel .minimum_version_id = 1, 25528004fb7SRayhan Faizel .fields = (const VMStateField[]) { 25628004fb7SRayhan Faizel VMSTATE_FIFO8(tx_fifo, BCM2835SPIState), 25728004fb7SRayhan Faizel VMSTATE_FIFO8(rx_fifo, BCM2835SPIState), 25828004fb7SRayhan Faizel VMSTATE_UINT32(cs, BCM2835SPIState), 25928004fb7SRayhan Faizel VMSTATE_UINT32(clk, BCM2835SPIState), 26028004fb7SRayhan Faizel VMSTATE_UINT32(dlen, BCM2835SPIState), 26128004fb7SRayhan Faizel VMSTATE_UINT32(ltoh, BCM2835SPIState), 26228004fb7SRayhan Faizel VMSTATE_UINT32(dc, BCM2835SPIState), 26328004fb7SRayhan Faizel VMSTATE_END_OF_LIST() 26428004fb7SRayhan Faizel } 26528004fb7SRayhan Faizel }; 26628004fb7SRayhan Faizel 267*12d1a768SPhilippe Mathieu-Daudé static void bcm2835_spi_class_init(ObjectClass *klass, const void *data) 26828004fb7SRayhan Faizel { 26928004fb7SRayhan Faizel DeviceClass *dc = DEVICE_CLASS(klass); 27028004fb7SRayhan Faizel 271e3d08143SPeter Maydell device_class_set_legacy_reset(dc, bcm2835_spi_reset); 27228004fb7SRayhan Faizel dc->realize = bcm2835_spi_realize; 27328004fb7SRayhan Faizel dc->vmsd = &vmstate_bcm2835_spi; 27428004fb7SRayhan Faizel } 27528004fb7SRayhan Faizel 27628004fb7SRayhan Faizel static const TypeInfo bcm2835_spi_info = { 27728004fb7SRayhan Faizel .name = TYPE_BCM2835_SPI, 27828004fb7SRayhan Faizel .parent = TYPE_SYS_BUS_DEVICE, 27928004fb7SRayhan Faizel .instance_size = sizeof(BCM2835SPIState), 28028004fb7SRayhan Faizel .class_init = bcm2835_spi_class_init, 28128004fb7SRayhan Faizel }; 28228004fb7SRayhan Faizel 28328004fb7SRayhan Faizel static void bcm2835_spi_register_types(void) 28428004fb7SRayhan Faizel { 28528004fb7SRayhan Faizel type_register_static(&bcm2835_spi_info); 28628004fb7SRayhan Faizel } 28728004fb7SRayhan Faizel 28828004fb7SRayhan Faizel type_init(bcm2835_spi_register_types) 289