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>
10 #include <metal/time.h>
14 #define METAL_SPI_SCKDIV_MASK 0xFFF
16 #define METAL_SPI_SCKMODE_PHA_SHIFT 0
17 #define METAL_SPI_SCKMODE_POL_SHIFT 1
19 #define METAL_SPI_CSMODE_MASK 3
20 #define METAL_SPI_CSMODE_AUTO 0
21 #define METAL_SPI_CSMODE_HOLD 2
22 #define METAL_SPI_CSMODE_OFF 3
24 #define METAL_SPI_PROTO_MASK 3
25 #define METAL_SPI_PROTO_SINGLE 0
26 #define METAL_SPI_PROTO_DUAL 1
27 #define METAL_SPI_PROTO_QUAD 2
29 #define METAL_SPI_ENDIAN_LSB 4
31 #define METAL_SPI_DISABLE_RX 8
33 #define METAL_SPI_FRAME_LEN_SHIFT 16
34 #define METAL_SPI_FRAME_LEN_MASK (0xF << METAL_SPI_FRAME_LEN_SHIFT)
36 #define METAL_SPI_TXDATA_FULL (1 << 31)
37 #define METAL_SPI_RXDATA_EMPTY (1 << 31)
38 #define METAL_SPI_TXMARK_MASK 7
39 #define METAL_SPI_TXWM 1
40 #define METAL_SPI_TXRXDATA_MASK (0xFF)
42 #define METAL_SPI_INTERVAL_SHIFT 16
44 #define METAL_SPI_CONTROL_IO 0
45 #define METAL_SPI_CONTROL_MAPPED 1
47 #define METAL_SPI_REG(offset) (((unsigned long)control_base + offset))
48 #define METAL_SPI_REGB(offset) (__METAL_ACCESS_ONCE((__metal_io_u8 *)METAL_SPI_REG(offset)))
49 #define METAL_SPI_REGW(offset) (__METAL_ACCESS_ONCE((__metal_io_u32 *)METAL_SPI_REG(offset)))
51 #define METAL_SPI_RXDATA_TIMEOUT 1
53 static int configure_spi(struct __metal_driver_sifive_spi0 *spi, struct metal_spi_config *config)
55 long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)spi);
57 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_PROTO_MASK);
58 switch (config->protocol) {
59 case METAL_SPI_SINGLE:
60 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE;
63 if (config->multi_wire == MULTI_WIRE_ALL)
64 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL;
66 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE;
69 if (config->multi_wire == MULTI_WIRE_ALL)
70 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD;
72 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE;
75 /* Unsupported value */
80 if(config->polarity) {
81 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_PHA_SHIFT);
83 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_PHA_SHIFT);
88 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_POL_SHIFT);
90 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_POL_SHIFT);
94 if(config->little_endian) {
95 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_ENDIAN_LSB;
97 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_ENDIAN_LSB);
100 /* Always populate receive FIFO */
101 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_DISABLE_RX);
104 if(config->cs_active_high) {
105 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 0;
107 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 1;
110 /* Set frame length */
111 if((METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) & METAL_SPI_FRAME_LEN_MASK) != (8 << METAL_SPI_FRAME_LEN_SHIFT)) {
112 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_FRAME_LEN_MASK);
113 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= (8 << METAL_SPI_FRAME_LEN_SHIFT);
117 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSID) = 1 << (config->csid);
119 /* Toggle off memory-mapped SPI flash mode, toggle on programmable IO mode
120 * It seems that with this line uncommented, the debugger cannot have access
121 * to the chip at all because it assumes the chip is in memory-mapped mode.
122 * I have to compile the code with this line commented and launch gdb,
123 * reset cores, reset $pc, set *((int *) 0x20004060) = 0, (set the flash
124 * interface control register to programmable I/O mode) and then continue
125 * Alternative, comment out the "flash" line in openocd.cfg */
126 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FCTRL) = METAL_SPI_CONTROL_IO;
131 static void spi_mode_switch(struct __metal_driver_sifive_spi0 *spi,
132 struct metal_spi_config *config,
133 unsigned int trans_stage) {
135 __metal_driver_sifive_spi0_control_base((struct metal_spi *)spi);
137 if (config->multi_wire == trans_stage) {
138 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_PROTO_MASK);
139 switch (config->protocol) {
141 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL;
144 METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD;
147 /* Unsupported value */
153 int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi,
154 struct metal_spi_config *config,
159 struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
160 long control_base = __metal_driver_sifive_spi0_control_base(gspi);
164 rc = configure_spi(spi, config);
169 /* Hold the chip select line for all len transferred */
170 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
171 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) |= METAL_SPI_CSMODE_HOLD;
173 unsigned long rxdata;
175 /* Declare time_t variables to break out of infinite while loop */
178 for (i = 0; i < config->cmd_num; i++) {
180 while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL)
184 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
186 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
189 endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT;
191 while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) &
192 METAL_SPI_RXDATA_EMPTY) {
193 if (metal_time() > endwait) {
194 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &=
195 ~(METAL_SPI_CSMODE_MASK);
202 rx_buf[i] = (char)(rxdata & METAL_SPI_TXRXDATA_MASK);
206 /* switch to Dual/Quad mode */
207 spi_mode_switch(spi, config, MULTI_WIRE_ADDR_DATA);
210 for (; i < (config->cmd_num + config->addr_num); i++) {
212 while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL)
216 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
218 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
221 endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT;
223 while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) &
224 METAL_SPI_RXDATA_EMPTY) {
225 if (metal_time() > endwait) {
226 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &=
227 ~(METAL_SPI_CSMODE_MASK);
234 rx_buf[i] = (char)(rxdata & METAL_SPI_TXRXDATA_MASK);
238 /* Send Dummy data */
239 for (; i < (config->cmd_num + config->addr_num + config->dummy_num); i++) {
241 while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL)
245 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
247 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
250 endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT;
252 while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) &
253 METAL_SPI_RXDATA_EMPTY) {
254 if (metal_time() > endwait) {
255 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &=
256 ~(METAL_SPI_CSMODE_MASK);
261 rx_buf[i] = (char)(rxdata & METAL_SPI_TXRXDATA_MASK);
265 /* switch to Dual/Quad mode */
266 spi_mode_switch(spi, config, MULTI_WIRE_DATA_ONLY);
268 for (; i < len; i++) {
269 /* Master send bytes to the slave */
271 /* Wait for TXFIFO to not be full */
272 while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL);
274 /* Transfer byte by modifying the least significant byte in the TXDATA register */
276 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
278 /* Transfer a 0 byte if the sending buffer is NULL */
279 METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
282 /* Master receives bytes from the RX FIFO */
284 /* Wait for RXFIFO to not be empty, but break the nested loops if timeout
285 * this timeout method needs refining, preferably taking into account
286 * the device specs */
287 endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT;
289 while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) & METAL_SPI_RXDATA_EMPTY) {
290 if (metal_time() > endwait) {
291 /* If timeout, deassert the CS */
292 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
294 /* If timeout, return error code 1 immediately */
299 /* Only store the dequeued byte if the receive_buffer is not NULL */
301 rx_buf[i] = (char) (rxdata & METAL_SPI_TXRXDATA_MASK);
305 /* On the last byte, set CSMODE to auto so that the chip select transitions back to high
306 * The reason that CS pin is not deasserted after transmitting out the byte buffer is timing.
307 * The code on the host side likely executes faster than the ability of FIFO to send out bytes.
308 * After the host iterates through the array, fifo is likely not cleared yet. If host deasserts
309 * the CS pin immediately, the following bytes in the output FIFO will not be sent consecutively.
310 * There needs to be a better way to handle this. */
311 METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
316 int __metal_driver_sifive_spi0_get_baud_rate(struct metal_spi *gspi)
318 struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
319 return spi->baud_rate;
322 int __metal_driver_sifive_spi0_set_baud_rate(struct metal_spi *gspi, int baud_rate)
324 long control_base = __metal_driver_sifive_spi0_control_base(gspi);
325 struct metal_clock *clock = __metal_driver_sifive_spi0_clock(gspi);
326 struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
328 spi->baud_rate = baud_rate;
331 long clock_rate = clock->vtable->get_rate_hz(clock);
333 /* Calculate divider */
334 long div = (clock_rate / (2 * baud_rate)) - 1;
336 if(div > METAL_SPI_SCKDIV_MASK) {
337 /* The requested baud rate is lower than we can support at
338 * the current clock rate */
343 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKDIV) &= ~METAL_SPI_SCKDIV_MASK;
344 METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKDIV) |= (div & METAL_SPI_SCKDIV_MASK);
350 static void pre_rate_change_callback_func(void *priv)
352 long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)priv);
354 /* Detect when the TXDATA is empty by setting the transmit watermark count
355 * to one and waiting until an interrupt is pending (indicating an empty TXFIFO) */
356 METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXMARK) &= ~(METAL_SPI_TXMARK_MASK);
357 METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXMARK) |= (METAL_SPI_TXMARK_MASK & 1);
359 while((METAL_SPI_REGW(METAL_SIFIVE_SPI0_IP) & METAL_SPI_TXWM) == 0) ;
362 static void post_rate_change_callback_func(void *priv)
364 struct __metal_driver_sifive_spi0 *spi = priv;
365 metal_spi_set_baud_rate(&spi->spi, spi->baud_rate);
368 void __metal_driver_sifive_spi0_init(struct metal_spi *gspi, int baud_rate)
370 struct __metal_driver_sifive_spi0 *spi = (void *)(gspi);
371 struct metal_clock *clock = __metal_driver_sifive_spi0_clock(gspi);
372 struct __metal_driver_sifive_gpio0 *pinmux = __metal_driver_sifive_spi0_pinmux(gspi);
375 spi->pre_rate_change_callback.callback = &pre_rate_change_callback_func;
376 spi->pre_rate_change_callback.priv = spi;
377 metal_clock_register_pre_rate_change_callback(clock, &(spi->pre_rate_change_callback));
379 spi->post_rate_change_callback.callback = &post_rate_change_callback_func;
380 spi->post_rate_change_callback.priv = spi;
381 metal_clock_register_post_rate_change_callback(clock, &(spi->post_rate_change_callback));
384 metal_spi_set_baud_rate(&(spi->spi), baud_rate);
386 if (pinmux != NULL) {
387 long pinmux_output_selector = __metal_driver_sifive_spi0_pinmux_output_selector(gspi);
388 long pinmux_source_selector = __metal_driver_sifive_spi0_pinmux_source_selector(gspi);
389 pinmux->gpio.vtable->enable_io(
390 (struct metal_gpio *) pinmux,
391 pinmux_output_selector,
392 pinmux_source_selector
397 __METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_spi0) = {
398 .spi.init = __metal_driver_sifive_spi0_init,
399 .spi.transfer = __metal_driver_sifive_spi0_transfer,
400 .spi.get_baud_rate = __metal_driver_sifive_spi0_get_baud_rate,
401 .spi.set_baud_rate = __metal_driver_sifive_spi0_set_baud_rate,
403 #endif /* METAL_SIFIVE_SPI0 */
405 typedef int no_empty_translation_units;