xref: /qemu/hw/ssi/bcm2835_spi.c (revision 28004fb741343b77b1044a0f01d27d8b36b59651)
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