2 * BCM2835 SPI Master Controller
4 * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 #include "qemu/osdep.h"
27 #include "qemu/fifo8.h"
28 #include "hw/ssi/bcm2835_spi.h"
30 #include "migration/vmstate.h"
32 static void bcm2835_spi_update_int(BCM2835SPIState
*s
)
36 /* Interrupt on DONE */
37 if (s
->cs
& BCM2835_SPI_CS_INTD
&& s
->cs
& BCM2835_SPI_CS_DONE
) {
40 /* Interrupt on RXR */
41 if (s
->cs
& BCM2835_SPI_CS_INTR
&& s
->cs
& BCM2835_SPI_CS_RXR
) {
44 qemu_set_irq(s
->irq
, do_interrupt
);
47 static void bcm2835_spi_update_rx_flags(BCM2835SPIState
*s
)
49 /* Set RXD if RX FIFO is non empty */
50 if (!fifo8_is_empty(&s
->rx_fifo
)) {
51 s
->cs
|= BCM2835_SPI_CS_RXD
;
53 s
->cs
&= ~BCM2835_SPI_CS_RXD
;
56 /* Set RXF if RX FIFO is full */
57 if (fifo8_is_full(&s
->rx_fifo
)) {
58 s
->cs
|= BCM2835_SPI_CS_RXF
;
60 s
->cs
&= ~BCM2835_SPI_CS_RXF
;
63 /* Set RXR if RX FIFO is 3/4th used or above */
64 if (fifo8_num_used(&s
->rx_fifo
) >= FIFO_SIZE_3_4
) {
65 s
->cs
|= BCM2835_SPI_CS_RXR
;
67 s
->cs
&= ~BCM2835_SPI_CS_RXR
;
71 static void bcm2835_spi_update_tx_flags(BCM2835SPIState
*s
)
73 /* Set TXD if TX FIFO is not full */
74 if (fifo8_is_full(&s
->tx_fifo
)) {
75 s
->cs
&= ~BCM2835_SPI_CS_TXD
;
77 s
->cs
|= BCM2835_SPI_CS_TXD
;
80 /* Set DONE if in TA mode and TX FIFO is empty */
81 if (fifo8_is_empty(&s
->tx_fifo
) && s
->cs
& BCM2835_SPI_CS_TA
) {
82 s
->cs
|= BCM2835_SPI_CS_DONE
;
84 s
->cs
&= ~BCM2835_SPI_CS_DONE
;
88 static void bcm2835_spi_flush_tx_fifo(BCM2835SPIState
*s
)
90 uint8_t tx_byte
, rx_byte
;
92 while (!fifo8_is_empty(&s
->tx_fifo
) && !fifo8_is_full(&s
->rx_fifo
)) {
93 tx_byte
= fifo8_pop(&s
->tx_fifo
);
94 rx_byte
= ssi_transfer(s
->bus
, tx_byte
);
95 fifo8_push(&s
->rx_fifo
, rx_byte
);
98 bcm2835_spi_update_tx_flags(s
);
99 bcm2835_spi_update_rx_flags(s
);
102 static uint64_t bcm2835_spi_read(void *opaque
, hwaddr addr
, unsigned size
)
104 BCM2835SPIState
*s
= opaque
;
105 uint32_t readval
= 0;
109 readval
= s
->cs
& 0xffffffff;
111 case BCM2835_SPI_FIFO
:
112 bcm2835_spi_flush_tx_fifo(s
);
113 if (s
->cs
& BCM2835_SPI_CS_RXD
) {
114 readval
= fifo8_pop(&s
->rx_fifo
);
115 bcm2835_spi_update_rx_flags(s
);
118 bcm2835_spi_update_int(s
);
120 case BCM2835_SPI_CLK
:
121 readval
= s
->clk
& 0xffff;
123 case BCM2835_SPI_DLEN
:
124 readval
= s
->dlen
& 0xffff;
126 case BCM2835_SPI_LTOH
:
127 readval
= s
->ltoh
& 0xf;
130 readval
= s
->dc
& 0xffffffff;
133 qemu_log_mask(LOG_GUEST_ERROR
,
134 "%s: Bad offset 0x%" HWADDR_PRIx
"\n", __func__
, addr
);
139 static void bcm2835_spi_write(void *opaque
, hwaddr addr
,
140 uint64_t value
, unsigned int size
)
142 BCM2835SPIState
*s
= opaque
;
146 s
->cs
= (value
& ~RO_MASK
) | (s
->cs
& RO_MASK
);
147 if (!(s
->cs
& BCM2835_SPI_CS_TA
)) {
148 /* Clear DONE and RXR if TA is off */
149 s
->cs
&= ~(BCM2835_SPI_CS_DONE
);
150 s
->cs
&= ~(BCM2835_SPI_CS_RXR
);
154 if (s
->cs
& BCM2835_SPI_CLEAR_RX
) {
155 fifo8_reset(&s
->rx_fifo
);
156 bcm2835_spi_update_rx_flags(s
);
160 if (s
->cs
& BCM2835_SPI_CLEAR_TX
) {
161 fifo8_reset(&s
->tx_fifo
);
162 bcm2835_spi_update_tx_flags(s
);
165 /* Set Transfer Active */
166 if (s
->cs
& BCM2835_SPI_CS_TA
) {
167 bcm2835_spi_update_tx_flags(s
);
170 if (s
->cs
& BCM2835_SPI_CS_DMAEN
) {
171 qemu_log_mask(LOG_UNIMP
, "%s: " \
172 "DMA not supported\n", __func__
);
175 if (s
->cs
& BCM2835_SPI_CS_LEN
) {
176 qemu_log_mask(LOG_UNIMP
, "%s: " \
177 "LoSSI not supported\n", __func__
);
180 bcm2835_spi_update_int(s
);
182 case BCM2835_SPI_FIFO
:
184 * According to documentation, writes to FIFO without TA controls
185 * CS and DLEN registers. This is supposed to be used in DMA mode
186 * which is currently unimplemented. Moreover, Linux does not make
187 * use of this and directly modifies the CS and DLEN registers.
189 if (s
->cs
& BCM2835_SPI_CS_TA
) {
190 if (s
->cs
& BCM2835_SPI_CS_TXD
) {
191 fifo8_push(&s
->tx_fifo
, value
& 0xff);
192 bcm2835_spi_update_tx_flags(s
);
195 bcm2835_spi_flush_tx_fifo(s
);
196 bcm2835_spi_update_int(s
);
199 case BCM2835_SPI_CLK
:
200 s
->clk
= value
& 0xffff;
202 case BCM2835_SPI_DLEN
:
203 s
->dlen
= value
& 0xffff;
205 case BCM2835_SPI_LTOH
:
206 s
->ltoh
= value
& 0xf;
209 s
->dc
= value
& 0xffffffff;
212 qemu_log_mask(LOG_GUEST_ERROR
,
213 "%s: Bad offset 0x%" HWADDR_PRIx
"\n", __func__
, addr
);
217 static const MemoryRegionOps bcm2835_spi_ops
= {
218 .read
= bcm2835_spi_read
,
219 .write
= bcm2835_spi_write
,
220 .endianness
= DEVICE_NATIVE_ENDIAN
,
223 static void bcm2835_spi_realize(DeviceState
*dev
, Error
**errp
)
225 BCM2835SPIState
*s
= BCM2835_SPI(dev
);
226 s
->bus
= ssi_create_bus(dev
, "spi");
228 memory_region_init_io(&s
->iomem
, OBJECT(dev
), &bcm2835_spi_ops
, s
,
229 TYPE_BCM2835_SPI
, 0x18);
230 sysbus_init_mmio(SYS_BUS_DEVICE(dev
), &s
->iomem
);
231 sysbus_init_irq(SYS_BUS_DEVICE(dev
), &s
->irq
);
233 fifo8_create(&s
->tx_fifo
, FIFO_SIZE
);
234 fifo8_create(&s
->rx_fifo
, FIFO_SIZE
);
236 static void bcm2835_spi_reset(DeviceState
*dev
)
238 BCM2835SPIState
*s
= BCM2835_SPI(dev
);
240 fifo8_reset(&s
->tx_fifo
);
241 fifo8_reset(&s
->rx_fifo
);
243 /* Reset values according to BCM2835 Peripheral Documentation */
244 s
->cs
= BCM2835_SPI_CS_TXD
| BCM2835_SPI_CS_REN
;
251 static const VMStateDescription vmstate_bcm2835_spi
= {
252 .name
= TYPE_BCM2835_SPI
,
254 .minimum_version_id
= 1,
255 .fields
= (const VMStateField
[]) {
256 VMSTATE_FIFO8(tx_fifo
, BCM2835SPIState
),
257 VMSTATE_FIFO8(rx_fifo
, BCM2835SPIState
),
258 VMSTATE_UINT32(cs
, BCM2835SPIState
),
259 VMSTATE_UINT32(clk
, BCM2835SPIState
),
260 VMSTATE_UINT32(dlen
, BCM2835SPIState
),
261 VMSTATE_UINT32(ltoh
, BCM2835SPIState
),
262 VMSTATE_UINT32(dc
, BCM2835SPIState
),
263 VMSTATE_END_OF_LIST()
267 static void bcm2835_spi_class_init(ObjectClass
*klass
, void *data
)
269 DeviceClass
*dc
= DEVICE_CLASS(klass
);
271 dc
->reset
= bcm2835_spi_reset
;
272 dc
->realize
= bcm2835_spi_realize
;
273 dc
->vmsd
= &vmstate_bcm2835_spi
;
276 static const TypeInfo bcm2835_spi_info
= {
277 .name
= TYPE_BCM2835_SPI
,
278 .parent
= TYPE_SYS_BUS_DEVICE
,
279 .instance_size
= sizeof(BCM2835SPIState
),
280 .class_init
= bcm2835_spi_class_init
,
283 static void bcm2835_spi_register_types(void)
285 type_register_static(&bcm2835_spi_info
);
288 type_init(bcm2835_spi_register_types
)