]> git.sur5r.net Git - freertos/blob - FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_spi0.c
fd9bc5e91f1c97bc1890a1a5b8eb8351240056f4
[freertos] / FreeRTOS / Demo / RISC-V_RV32_SiFive_HiFive1_FreedomStudio / freedom-metal / src / drivers / sifive_spi0.c
1 /* Copyright 2018 SiFive, Inc */
2 /* SPDX-License-Identifier: Apache-2.0 */
3
4 #include <metal/machine/platform.h>
5
6 #ifdef METAL_SIFIVE_SPI0
7 #include <metal/drivers/sifive_spi0.h>
8 #include <metal/io.h>
9 #include <metal/machine.h>
10 #include <time.h>
11
12 /* Register fields */
13 #define METAL_SPI_SCKDIV_MASK         0xFFF
14
15 #define METAL_SPI_SCKMODE_PHA_SHIFT   0
16 #define METAL_SPI_SCKMODE_POL_SHIFT   1
17
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
22
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
27
28 #define METAL_SPI_ENDIAN_LSB          4
29
30 #define METAL_SPI_DISABLE_RX          8
31
32 #define METAL_SPI_FRAME_LEN_SHIFT     16
33 #define METAL_SPI_FRAME_LEN_MASK      (0xF << METAL_SPI_FRAME_LEN_SHIFT)
34
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)
40
41 #define METAL_SPI_INTERVAL_SHIFT      16
42
43 #define METAL_SPI_CONTROL_IO          0
44 #define METAL_SPI_CONTROL_MAPPED      1
45
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)))
49
50 #define METAL_SPI_RXDATA_TIMEOUT      1
51
52 static int configure_spi(struct __metal_driver_sifive_spi0 *spi, struct metal_spi_config *config)
53 {
54     long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)spi);
55     /* Set protocol */
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;
60             break;
61         case METAL_SPI_DUAL:
62             METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL;
63             break;
64         case METAL_SPI_QUAD:
65             METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD;
66             break;
67         default:
68             /* Unsupported value */
69             return -1;
70     }
71
72     /* Set Polarity */
73     if(config->polarity) {
74         METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_PHA_SHIFT);
75     } else {
76         METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_PHA_SHIFT);
77     }
78
79     /* Set Phase */
80     if(config->phase) {
81         METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_POL_SHIFT);
82     } else {
83         METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_POL_SHIFT);
84     }
85
86     /* Set Endianness */
87     if(config->little_endian) {
88         METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_ENDIAN_LSB;
89     } else {
90         METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_ENDIAN_LSB);
91     }
92
93     /* Always populate receive FIFO */
94     METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_DISABLE_RX);
95
96     /* Set CS Active */
97     if(config->cs_active_high) {
98         METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 0;
99     } else {
100         METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 1;
101     }
102
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);
107     }
108
109     /* Set CS line */
110     METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSID) = config->csid;
111
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;
120
121     return 0;
122 }
123
124 int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi,
125                                       struct metal_spi_config *config,
126                                       size_t len,
127                                       char *tx_buf,
128                                       char *rx_buf)
129 {
130     struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
131     long control_base = __metal_driver_sifive_spi0_control_base(gspi);
132     int rc = 0;
133
134     rc = configure_spi(spi, config);
135     if(rc != 0) {
136         return rc;
137     }
138
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;
142
143     unsigned long rxdata;
144     
145     /* Declare time_t variables to break out of infinite while loop */
146     time_t endwait;
147
148     for(int i = 0; i < len; i++) {
149         /* Master send bytes to the slave */
150
151         /* Wait for TXFIFO to not be full */
152         while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL);
153     
154         /* Transfer byte by modifying the least significant byte in the TXDATA register */
155         if (tx_buf) {
156             METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
157         } else {
158             /* Transfer a 0 byte if the sending buffer is NULL */
159             METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
160         }
161
162         /* Master receives bytes from the RX FIFO */
163
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;
168
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);
173
174                 /* If timeout, return error code 1 immediately */
175                 return 1;
176             }
177         }
178
179         /* Only store the dequeued byte if the receive_buffer is not NULL */
180         if (rx_buf) {
181             rx_buf[i] = (char) (rxdata & METAL_SPI_TXRXDATA_MASK);
182         }
183     }
184
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);
192
193     return 0;
194 }
195
196 int __metal_driver_sifive_spi0_get_baud_rate(struct metal_spi *gspi)
197 {
198     struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
199     return spi->baud_rate;
200 }
201
202 int __metal_driver_sifive_spi0_set_baud_rate(struct metal_spi *gspi, int baud_rate)
203 {
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;
207
208     spi->baud_rate = baud_rate;
209
210     if (clock != NULL) {
211         long clock_rate = clock->vtable->get_rate_hz(clock);
212
213         /* Calculate divider */
214         long div = (clock_rate / (2 * baud_rate)) - 1;
215
216         if(div > METAL_SPI_SCKDIV_MASK) {
217             /* The requested baud rate is lower than we can support at
218              * the current clock rate */
219             return -1;
220         }
221
222         /* Set divider */
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);
225     }
226
227     return 0;
228 }
229
230 static void pre_rate_change_callback(void *priv)
231 {
232     long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)priv);
233
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);
237
238     while((METAL_SPI_REGW(METAL_SIFIVE_SPI0_IP) & METAL_SPI_TXWM) == 0) ;
239 }
240
241 static void post_rate_change_callback(void *priv)
242 {
243     struct __metal_driver_sifive_spi0 *spi = priv;
244     metal_spi_set_baud_rate(&spi->spi, spi->baud_rate);
245 }
246
247 void __metal_driver_sifive_spi0_init(struct metal_spi *gspi, int baud_rate)
248 {
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);
252
253     if(clock != NULL) {
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);
256     }
257
258     metal_spi_set_baud_rate(&(spi->spi), baud_rate);
259
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
267         );
268     }
269 }
270
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,
276 };
277 #endif /* METAL_SIFIVE_SPI0 */