1 /* Copyright 2018 SiFive, Inc */
2 /* SPDX-License-Identifier: Apache-2.0 */
4 #include <metal/machine/platform.h>
6 #ifdef METAL_SIFIVE_SPI0
7 #include <metal/drivers/sifive_spi0.h>
9 #include <metal/machine.h>
13 #define METAL_SPI_SCKDIV_MASK 0xFFF
15 #define METAL_SPI_SCKMODE_PHA_SHIFT 0
16 #define METAL_SPI_SCKMODE_POL_SHIFT 1
18 #define METAL_SPI_CSMODE_MASK 3
19 #define METAL_SPI_CSMODE_AUTO 0
20 #define METAL_SPI_CSMODE_HOLD 2
21 #define METAL_SPI_CSMODE_OFF 3
23 #define METAL_SPI_PROTO_MASK 3
24 #define METAL_SPI_PROTO_SINGLE 0
25 #define METAL_SPI_PROTO_DUAL 1
26 #define METAL_SPI_PROTO_QUAD 2
28 #define METAL_SPI_ENDIAN_LSB 4
30 #define METAL_SPI_DISABLE_RX 8
32 #define METAL_SPI_FRAME_LEN_SHIFT 16
33 #define METAL_SPI_FRAME_LEN_MASK (0xF << METAL_SPI_FRAME_LEN_SHIFT)
35 #define METAL_SPI_TXDATA_FULL (1 << 31)
36 #define METAL_SPI_RXDATA_EMPTY (1 << 31)
37 #define METAL_SPI_TXMARK_MASK 7
38 #define METAL_SPI_TXWM 1
39 #define METAL_SPI_TXRXDATA_MASK (0xFF)
41 #define METAL_SPI_INTERVAL_SHIFT 16
43 #define METAL_SPI_CONTROL_IO 0
44 #define METAL_SPI_CONTROL_MAPPED 1
46 #define METAL_SPI_REG(offset) (((unsigned long)control_base + offset))
47 #define METAL_SPI_REGB(offset) (__METAL_ACCESS_ONCE((__metal_io_u8 *)METAL_SPI_REG(offset)))
48 #define METAL_SPI_REGW(offset) (__METAL_ACCESS_ONCE((__metal_io_u32 *)METAL_SPI_REG(offset)))
50 #define METAL_SPI_RXDATA_TIMEOUT 1
52 static int configure_spi(struct __metal_driver_sifive_spi0 *spi, struct metal_spi_config *config)
54 long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)spi);
56 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_PROTO_MASK);
57 switch(config->protocol) {
58 case METAL_SPI_SINGLE:
59 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE;
62 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL;
65 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD;
68 /* Unsupported value */
73 if(config->polarity) {
74 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_PHA_SHIFT);
76 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_PHA_SHIFT);
81 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_POL_SHIFT);
83 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_POL_SHIFT);
87 if(config->little_endian) {
88 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_ENDIAN_LSB;
90 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_ENDIAN_LSB);
93 /* Always populate receive FIFO */
94 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_DISABLE_RX);
97 if(config->cs_active_high) {
98 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 0;
100 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 1;
103 /* Set frame length */
104 if((METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) & METAL_SPI_FRAME_LEN_MASK) != (8 << METAL_SPI_FRAME_LEN_SHIFT)) {
105 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_FRAME_LEN_MASK);
106 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= (8 << METAL_SPI_FRAME_LEN_SHIFT);
110 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSID) = config->csid;
112 /* Toggle off memory-mapped SPI flash mode, toggle on programmable IO mode
113 * It seems that with this line uncommented, the debugger cannot have access
114 * to the chip at all because it assumes the chip is in memory-mapped mode.
115 * I have to compile the code with this line commented and launch gdb,
116 * reset cores, reset $pc, set *((int *) 0x20004060) = 0, (set the flash
117 * interface control register to programmable I/O mode) and then continue
118 * Alternative, comment out the "flash" line in openocd.cfg */
119 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FCTRL) = METAL_SPI_CONTROL_IO;
124 int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi,
125 struct metal_spi_config *config,
130 struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
131 long control_base = __metal_driver_sifive_spi0_control_base(gspi);
134 rc = configure_spi(spi, config);
139 /* Hold the chip select line for all len transferred */
140 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
141 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) |= METAL_SPI_CSMODE_HOLD;
143 unsigned long rxdata;
145 /* Declare time_t variables to break out of infinite while loop */
148 for(int i = 0; i < len; i++) {
149 /* Master send bytes to the slave */
151 /* Wait for TXFIFO to not be full */
152 while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL);
154 /* Transfer byte by modifying the least significant byte in the TXDATA register */
156 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
158 /* Transfer a 0 byte if the sending buffer is NULL */
159 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
162 /* Master receives bytes from the RX FIFO */
164 /* Wait for RXFIFO to not be empty, but break the nested loops if timeout
165 * this timeout method needs refining, preferably taking into account
166 * the device specs */
167 endwait = time(NULL) + METAL_SPI_RXDATA_TIMEOUT;
169 while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) & METAL_SPI_RXDATA_EMPTY) {
170 if (time(NULL) > endwait) {
171 /* If timeout, deassert the CS */
172 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
174 /* If timeout, return error code 1 immediately */
179 /* Only store the dequeued byte if the receive_buffer is not NULL */
181 rx_buf[i] = (char) (rxdata & METAL_SPI_TXRXDATA_MASK);
185 /* On the last byte, set CSMODE to auto so that the chip select transitions back to high
186 * The reason that CS pin is not deasserted after transmitting out the byte buffer is timing.
187 * The code on the host side likely executes faster than the ability of FIFO to send out bytes.
188 * After the host iterates through the array, fifo is likely not cleared yet. If host deasserts
189 * the CS pin immediately, the following bytes in the output FIFO will not be sent consecutively.
190 * There needs to be a better way to handle this. */
191 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
196 int __metal_driver_sifive_spi0_get_baud_rate(struct metal_spi *gspi)
198 struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
199 return spi->baud_rate;
202 int __metal_driver_sifive_spi0_set_baud_rate(struct metal_spi *gspi, int baud_rate)
204 long control_base = __metal_driver_sifive_spi0_control_base(gspi);
205 struct metal_clock *clock = __metal_driver_sifive_spi0_clock(gspi);
206 struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
208 spi->baud_rate = baud_rate;
211 long clock_rate = clock->vtable->get_rate_hz(clock);
213 /* Calculate divider */
214 long div = (clock_rate / (2 * baud_rate)) - 1;
216 if(div > METAL_SPI_SCKDIV_MASK) {
217 /* The requested baud rate is lower than we can support at
218 * the current clock rate */
223 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKDIV) &= ~METAL_SPI_SCKDIV_MASK;
224 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKDIV) |= (div & METAL_SPI_SCKDIV_MASK);
230 static void pre_rate_change_callback(void *priv)
232 long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)priv);
234 /* Detect when the TXDATA is empty by setting the transmit watermark count
235 * to zero and waiting until an interrupt is pending */
236 METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXMARK) &= ~(METAL_SPI_TXMARK_MASK);
238 while((METAL_SPI_REGW(METAL_SIFIVE_SPI0_IP) & METAL_SPI_TXWM) == 0) ;
241 static void post_rate_change_callback(void *priv)
243 struct __metal_driver_sifive_spi0 *spi = priv;
244 metal_spi_set_baud_rate(&spi->spi, spi->baud_rate);
247 void __metal_driver_sifive_spi0_init(struct metal_spi *gspi, int baud_rate)
249 struct __metal_driver_sifive_spi0 *spi = (void *)(gspi);
250 struct metal_clock *clock = __metal_driver_sifive_spi0_clock(gspi);
251 struct __metal_driver_sifive_gpio0 *pinmux = __metal_driver_sifive_spi0_pinmux(gspi);
254 metal_clock_register_pre_rate_change_callback(clock, &pre_rate_change_callback, spi);
255 metal_clock_register_post_rate_change_callback(clock, &post_rate_change_callback, spi);
258 metal_spi_set_baud_rate(&(spi->spi), baud_rate);
260 if (pinmux != NULL) {
261 long pinmux_output_selector = __metal_driver_sifive_spi0_pinmux_output_selector(gspi);
262 long pinmux_source_selector = __metal_driver_sifive_spi0_pinmux_source_selector(gspi);
263 pinmux->gpio.vtable->enable_io(
264 (struct metal_gpio *) pinmux,
265 pinmux_output_selector,
266 pinmux_source_selector
271 __METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_spi0) = {
272 .spi.init = __metal_driver_sifive_spi0_init,
273 .spi.transfer = __metal_driver_sifive_spi0_transfer,
274 .spi.get_baud_rate = __metal_driver_sifive_spi0_get_baud_rate,
275 .spi.set_baud_rate = __metal_driver_sifive_spi0_set_baud_rate,
277 #endif /* METAL_SIFIVE_SPI0 */