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
bcm2835_spi_update_int(BCM2835SPIState * s)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
bcm2835_spi_update_rx_flags(BCM2835SPIState * s)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
bcm2835_spi_update_tx_flags(BCM2835SPIState * s)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
bcm2835_spi_flush_tx_fifo(BCM2835SPIState * s)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
bcm2835_spi_read(void * opaque,hwaddr addr,unsigned size)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
bcm2835_spi_write(void * opaque,hwaddr addr,uint64_t value,unsigned int size)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
bcm2835_spi_realize(DeviceState * dev,Error ** errp)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 }
bcm2835_spi_reset(DeviceState * dev)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
bcm2835_spi_class_init(ObjectClass * klass,const void * data)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
bcm2835_spi_register_types(void)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