Software APIs
dif_spi_host.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
7#include <assert.h>
8#include <stdalign.h>
9#include <stddef.h>
10
14
15#include "spi_host_regs.h" // Generated.
16
17// We create weak symbol aliases for the FIFO write and read functions so the
18// unit tests can provide mocks. The mocks provide for separate testing of
19// the FIFO functions and the overall transaction management functions.
21OT_ALIAS("dif_spi_host_fifo_write")
22dif_result_t spi_host_fifo_write_alias(const dif_spi_host_t *spi_host,
23 const void *src, uint16_t len);
24
26OT_ALIAS("dif_spi_host_fifo_read")
27dif_result_t spi_host_fifo_read_alias(const dif_spi_host_t *spi_host, void *dst,
28 uint16_t len);
29
30static void spi_host_reset(const dif_spi_host_t *spi_host) {
31 // Set the software reset request bit.
32 uint32_t reg =
33 mmio_region_read32(spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET);
34 mmio_region_write32(
35 spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET,
36 bitfield_bit32_write(reg, SPI_HOST_CONTROL_SW_RST_BIT, true));
37
38 // Wait for the spi host to go inactive.
39 bool active;
40 do {
41 uint32_t reg =
42 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
43 active = bitfield_bit32_read(reg, SPI_HOST_STATUS_ACTIVE_BIT);
44 } while (active);
45
46 // Wait for the spi host fifos to drain.
47 uint32_t txqd, rxqd;
48 do {
49 uint32_t reg =
50 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
51 txqd = bitfield_field32_read(reg, SPI_HOST_STATUS_TXQD_FIELD);
52 rxqd = bitfield_field32_read(reg, SPI_HOST_STATUS_RXQD_FIELD);
53 } while (txqd != 0 || rxqd != 0);
54
55 // Clear the software reset request bit.
56 mmio_region_write32(
57 spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET,
58 bitfield_bit32_write(0, SPI_HOST_CONTROL_SW_RST_BIT, false));
59}
60
61static void spi_host_enable(const dif_spi_host_t *spi_host, bool enable) {
62 uint32_t reg =
63 mmio_region_read32(spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET);
64 mmio_region_write32(
65 spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET,
66 bitfield_bit32_write(reg, SPI_HOST_CONTROL_SPIEN_BIT, enable));
67}
68
69dif_result_t dif_spi_host_configure(const dif_spi_host_t *spi_host,
70 dif_spi_host_config_t config) {
71 if (spi_host == NULL) {
72 return kDifBadArg;
73 }
74 if (config.peripheral_clock_freq_hz == 0 || config.spi_clock == 0) {
75 return kDifBadArg;
76 }
77
78 uint32_t divider =
79 ((config.peripheral_clock_freq_hz / config.spi_clock) / 2) - 1;
80 if (divider & ~(uint32_t)SPI_HOST_CONFIGOPTS_CLKDIV_MASK) {
81 return kDifBadArg;
82 }
83
84 spi_host_reset(spi_host);
85 uint32_t reg = 0;
86 reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CLKDIV_FIELD, divider);
87 reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CSNIDLE_FIELD,
88 config.chip_select.idle);
89 reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CSNTRAIL_FIELD,
90 config.chip_select.trail);
91 reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CSNLEAD_FIELD,
92 config.chip_select.lead);
93 reg = bitfield_bit32_write(reg, SPI_HOST_CONFIGOPTS_FULLCYC_BIT,
94 config.full_cycle);
95 reg = bitfield_bit32_write(reg, SPI_HOST_CONFIGOPTS_CPHA_BIT, config.cpha);
96 reg = bitfield_bit32_write(reg, SPI_HOST_CONFIGOPTS_CPOL_BIT, config.cpol);
97 mmio_region_write32(spi_host->base_addr, SPI_HOST_CONFIGOPTS_REG_OFFSET, reg);
98
99 reg = mmio_region_read32(spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET);
100 reg = bitfield_field32_write(reg, SPI_HOST_CONTROL_TX_WATERMARK_FIELD,
101 config.tx_watermark);
102 reg = bitfield_field32_write(reg, SPI_HOST_CONTROL_RX_WATERMARK_FIELD,
103 config.rx_watermark);
104 mmio_region_write32(spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET, reg);
105
106 spi_host_enable(spi_host, true);
107 return kDifOk;
108}
109
110dif_result_t dif_spi_host_output_set_enabled(const dif_spi_host_t *spi_host,
111 bool enabled) {
112 if (spi_host == NULL) {
113 return kDifBadArg;
114 }
115
116 uint32_t reg =
117 mmio_region_read32(spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET);
118 mmio_region_write32(
119 spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET,
120 bitfield_bit32_write(reg, SPI_HOST_CONTROL_OUTPUT_EN_BIT, enabled));
121
122 return kDifOk;
123}
124
125static void wait_ready(const dif_spi_host_t *spi_host) {
126 bool ready;
127 do {
128 uint32_t reg =
129 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
130 ready = bitfield_bit32_read(reg, SPI_HOST_STATUS_READY_BIT);
131 } while (!ready);
132}
133
134static void wait_tx_fifo(const dif_spi_host_t *spi_host) {
135 uint32_t txqd;
136 do {
137 uint32_t reg =
138 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
139 txqd = bitfield_field32_read(reg, SPI_HOST_STATUS_TXQD_FIELD);
140 } while (txqd == SPI_HOST_PARAM_TX_DEPTH);
141}
142
143static void wait_rx_fifo(const dif_spi_host_t *spi_host) {
144 uint32_t rxqd;
145 do {
146 uint32_t reg =
147 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
148 rxqd = bitfield_field32_read(reg, SPI_HOST_STATUS_RXQD_FIELD);
149 } while (rxqd == 0);
150}
151
152static inline void tx_fifo_write8(const dif_spi_host_t *spi_host,
153 uintptr_t srcaddr) {
154 uint8_t *src = (uint8_t *)srcaddr;
155 wait_tx_fifo(spi_host);
156 mmio_region_write8(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, *src);
157}
158
159static inline void tx_fifo_write32(const dif_spi_host_t *spi_host,
160 uintptr_t srcaddr) {
161 wait_tx_fifo(spi_host);
162 uint32_t val = read_32((const void *)srcaddr);
163 mmio_region_write32(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, val);
164}
165
166dif_result_t dif_spi_host_fifo_write(const dif_spi_host_t *spi_host,
167 const void *src, uint16_t len) {
168 uintptr_t ptr = (uintptr_t)src;
169 if (spi_host == NULL || (src == NULL && len > 0)) {
170 return kDifBadArg;
171 }
172
173 // If the pointer starts mis-aligned, write until we are aligned.
174 while (misalignment32_of(ptr) && len > 0) {
175 tx_fifo_write8(spi_host, ptr);
176 ptr += 1;
177 len -= 1;
178 }
179
180 // Write complete 32-bit words to the fifo.
181 while (len > 3) {
182 tx_fifo_write32(spi_host, ptr);
183 ptr += 4;
184 len -= 4;
185 }
186
187 // Clean up any leftover bytes.
188 while (len > 0) {
189 tx_fifo_write8(spi_host, ptr);
190 ptr += 1;
191 len -= 1;
192 }
193
194 return kDifOk;
195}
196
197typedef struct queue {
198 int32_t length;
199 uint8_t alignas(uint64_t) data[8];
200} queue_t;
201
202static void enqueue_byte(queue_t *queue, uint8_t data) {
203 queue->data[queue->length++] = data;
204}
205
206static void enqueue_word(queue_t *queue, uint32_t data) {
207 if (queue->length % (int32_t)sizeof(uint32_t) == 0) {
208 write_32(data, queue->data + queue->length);
209 queue->length += 4;
210 } else {
211 for (size_t i = 0; i < sizeof(uint32_t); ++i) {
212 enqueue_byte(queue, (uint8_t)data);
213 data >>= 8;
214 }
215 }
216}
217
218static uint8_t dequeue_byte(queue_t *queue) {
219 uint8_t val = queue->data[0];
220 uint64_t qword = read_64(queue->data);
221 write_64(qword >> 8, queue->data);
222 queue->length -= 1;
223 return val;
224}
225
226static uint32_t dequeue_word(queue_t *queue) {
227 uint32_t val = read_32(queue->data);
228 write_32(read_32(queue->data + sizeof(uint32_t)), queue->data);
229 queue->length -= 4;
230 return val;
231}
232
233dif_result_t dif_spi_host_fifo_read(const dif_spi_host_t *spi_host, void *dst,
234 uint16_t len) {
235 if (spi_host == NULL || (dst == NULL && len > 0)) {
236 return kDifBadArg;
237 }
238
239 uintptr_t ptr = (uintptr_t)dst;
240 // We always have to read from the RXFIFO as a 32-bit word. We use a
241 // two-word queue to handle destination and length mis-alignments.
242 queue_t queue = {0};
243
244 // If the buffer is misaligned, write a byte at a time until we reach
245 // alignment.
246 while (misalignment32_of(ptr) && len > 0) {
247 if (queue.length < 1) {
248 wait_rx_fifo(spi_host);
249 enqueue_word(&queue, mmio_region_read32(spi_host->base_addr,
250 SPI_HOST_RXDATA_REG_OFFSET));
251 }
252 uint8_t *p = (uint8_t *)ptr;
253 *p = dequeue_byte(&queue);
254 ptr += 1;
255 len -= 1;
256 }
257
258 // While we can write complete words to memory, operate on 4 bytes at a time.
259 while (len > 3) {
260 if (queue.length < 4) {
261 wait_rx_fifo(spi_host);
262 enqueue_word(&queue, mmio_region_read32(spi_host->base_addr,
263 SPI_HOST_RXDATA_REG_OFFSET));
264 }
265 write_32(dequeue_word(&queue), (void *)ptr);
266 ptr += 4;
267 len -= 4;
268 }
269
270 // Finish up any left over buffer a byte at a time.
271 while (len > 0) {
272 if (queue.length < 1) {
273 wait_rx_fifo(spi_host);
274 enqueue_word(&queue, mmio_region_read32(spi_host->base_addr,
275 SPI_HOST_RXDATA_REG_OFFSET));
276 }
277 uint8_t *p = (uint8_t *)ptr;
278 *p = dequeue_byte(&queue);
279 ptr += 1;
280 len -= 1;
281 }
282
283 return kDifOk;
284}
285
286static void write_command_reg(const dif_spi_host_t *spi_host, uint16_t length,
288 dif_spi_host_direction_t direction,
289 bool last_segment) {
290 uint32_t reg = 0;
291 reg = bitfield_field32_write(reg, SPI_HOST_COMMAND_LEN_FIELD, length - 1);
292 reg = bitfield_field32_write(reg, SPI_HOST_COMMAND_SPEED_FIELD, speed);
293 reg =
294 bitfield_field32_write(reg, SPI_HOST_COMMAND_DIRECTION_FIELD, direction);
295 reg = bitfield_bit32_write(reg, SPI_HOST_COMMAND_CSAAT_BIT, !last_segment);
296 mmio_region_write32(spi_host->base_addr, SPI_HOST_COMMAND_REG_OFFSET, reg);
297}
298
299static void issue_opcode(const dif_spi_host_t *spi_host,
300 dif_spi_host_segment_t *segment, bool last_segment) {
301 wait_tx_fifo(spi_host);
302 mmio_region_write8(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET,
303 segment->opcode.opcode);
304 write_command_reg(spi_host, 1, segment->opcode.width, kDifSpiHostDirectionTx,
305 last_segment);
306}
307
308static void issue_address(const dif_spi_host_t *spi_host,
309 dif_spi_host_segment_t *segment, bool last_segment) {
310 wait_tx_fifo(spi_host);
311 // The address appears on the wire in big-endian order.
312 uint32_t address = bitfield_byteswap32(segment->address.address);
313 uint16_t length;
314 if (segment->address.mode == kDifSpiHostAddrMode4b) {
315 length = 4;
316 mmio_region_write32(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET,
317 address);
318 } else {
319 length = 3;
320 address >>= 8;
321 mmio_region_write32(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET,
322 address);
323 }
324 write_command_reg(spi_host, length, segment->address.width,
325 kDifSpiHostDirectionTx, last_segment);
326}
327
328static void issue_dummy(const dif_spi_host_t *spi_host,
329 dif_spi_host_segment_t *segment, bool last_segment) {
330 if (segment->dummy.length > 0) {
331 // We only want to program a dummy segment if the number of cycles is
332 // greater than zero. Programming a zero to the hardware results in a
333 // dummy segment of 512 bits.
334 write_command_reg(spi_host, (uint16_t)segment->dummy.length,
335 segment->dummy.width, kDifSpiHostDirectionDummy,
336 last_segment);
337 }
338}
339
340static dif_result_t issue_data_phase(const dif_spi_host_t *spi_host,
341 dif_spi_host_segment_t *segment,
342 bool last_segment) {
343 switch (segment->type) {
345 write_command_reg(spi_host, (uint16_t)segment->tx.length,
346 segment->tx.width, kDifSpiHostDirectionTx,
347 last_segment);
348 spi_host_fifo_write_alias(spi_host, segment->tx.buf,
349 (uint16_t)segment->tx.length);
350 break;
352 write_command_reg(spi_host, (uint16_t)segment->bidir.length,
353 segment->bidir.width, kDifSpiHostDirectionBidirectional,
354 last_segment);
355 spi_host_fifo_write_alias(spi_host, segment->bidir.txbuf,
356 (uint16_t)segment->bidir.length);
357 break;
359 write_command_reg(spi_host, (uint16_t)segment->rx.length,
360 segment->rx.width, kDifSpiHostDirectionRx,
361 last_segment);
362 break;
363 default:
364 // Programming error (within this file). We should never get here.
365 // `issue_data_phase` should only get called for segment types which
366 // represent a data transfer.
367 return kDifBadArg;
368 }
369 return kDifOk;
370}
371
372dif_result_t dif_spi_host_start_transaction(const dif_spi_host_t *spi_host,
373 uint32_t csid,
374 dif_spi_host_segment_t *segments,
375 size_t length) {
376 if (spi_host == NULL || segments == NULL) {
377 return kDifBadArg;
378 }
379
380 // Write to chip select ID.
381 mmio_region_write32(spi_host->base_addr, SPI_HOST_CSID_REG_OFFSET, csid);
382
383 // For each segment, write the segment information to the
384 // COMMAND register and transmit FIFO.
385 for (size_t i = 0; i < length; ++i) {
386 bool last_segment = i == length - 1;
387 wait_ready(spi_host);
388 dif_spi_host_segment_t *segment = &segments[i];
389 switch (segment->type) {
391 issue_opcode(spi_host, segment, last_segment);
392 break;
394 issue_address(spi_host, segment, last_segment);
395 break;
397 issue_dummy(spi_host, segment, last_segment);
398 break;
402 dif_result_t error = issue_data_phase(spi_host, segment, last_segment);
403 if (error != kDifOk) {
404 return error;
405 }
406 break;
407 }
408 default:
409 return kDifBadArg;
410 }
411 }
412 return kDifOk;
413}
414
415dif_result_t dif_spi_host_transaction(const dif_spi_host_t *spi_host,
416 uint32_t csid,
417 dif_spi_host_segment_t *segments,
418 size_t length) {
420 dif_spi_host_start_transaction(spi_host, csid, segments, length));
421
422 // For each segment which receives data, read from the receive FIFO.
423 for (size_t i = 0; i < length; ++i) {
424 dif_spi_host_segment_t *segment = &segments[i];
425 switch (segment->type) {
427 spi_host_fifo_read_alias(spi_host, segment->rx.buf,
428 (uint16_t)segment->rx.length);
429 break;
431 spi_host_fifo_read_alias(spi_host, segment->bidir.rxbuf,
432 (uint16_t)segment->bidir.length);
433 break;
434 default:
435 /* do nothing */;
436 }
437 }
438 return kDifOk;
439}
440
441dif_result_t dif_spi_host_event_set_enabled(const dif_spi_host_t *spi_host,
443 bool enable) {
444 if (spi_host == NULL || (event & ~(uint32_t)kDifSpiHostEvtAll) != 0) {
445 return kDifBadArg;
446 }
447
448 uint32_t reg =
449 mmio_region_read32(spi_host->base_addr, SPI_HOST_EVENT_ENABLE_REG_OFFSET);
450 if (enable) {
451 reg |= event;
452 } else {
453 reg &= ~event;
454 }
455 mmio_region_write32(spi_host->base_addr, SPI_HOST_EVENT_ENABLE_REG_OFFSET,
456 reg);
457 return kDifOk;
458}
459
460dif_result_t dif_spi_host_event_get_enabled(const dif_spi_host_t *spi_host,
461 dif_spi_host_events_t *events) {
462 if (spi_host == NULL || events == NULL) {
463 return kDifBadArg;
464 }
465
466 *events =
467 mmio_region_read32(spi_host->base_addr, SPI_HOST_EVENT_ENABLE_REG_OFFSET);
468 return kDifOk;
469}
470
471dif_result_t dif_spi_host_get_status(const dif_spi_host_t *spi_host,
472 dif_spi_host_status_t *status) {
473 if (spi_host == NULL || status == NULL) {
474 return kDifBadArg;
475 }
476
477 uint32_t reg =
478 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
479
480 status->ready = bitfield_bit32_read(reg, SPI_HOST_STATUS_READY_BIT);
481 status->active = bitfield_bit32_read(reg, SPI_HOST_STATUS_ACTIVE_BIT);
482 status->tx_empty = bitfield_bit32_read(reg, SPI_HOST_STATUS_TXEMPTY_BIT);
483 status->rx_empty = bitfield_bit32_read(reg, SPI_HOST_STATUS_RXEMPTY_BIT);
484 status->tx_full = bitfield_bit32_read(reg, SPI_HOST_STATUS_TXFULL_BIT);
485 status->rx_full = bitfield_bit32_read(reg, SPI_HOST_STATUS_RXFULL_BIT);
486 status->tx_water_mark = bitfield_bit32_read(reg, SPI_HOST_STATUS_TXWM_BIT);
487 status->rx_water_mark = bitfield_bit32_read(reg, SPI_HOST_STATUS_RXWM_BIT);
488 status->tx_stall = bitfield_bit32_read(reg, SPI_HOST_STATUS_TXSTALL_BIT);
489 status->rx_stall = bitfield_bit32_read(reg, SPI_HOST_STATUS_RXSTALL_BIT);
490 status->least_significant_first =
491 bitfield_bit32_read(reg, SPI_HOST_STATUS_BYTEORDER_BIT);
492 status->tx_queue_depth =
493 bitfield_field32_read(reg, SPI_HOST_STATUS_TXQD_FIELD);
494 status->rx_queue_depth =
495 bitfield_field32_read(reg, SPI_HOST_STATUS_RXQD_FIELD);
496 status->cmd_queue_depth =
497 bitfield_field32_read(reg, SPI_HOST_STATUS_CMDQD_FIELD);
498
499 return kDifOk;
500}
501
502dif_result_t dif_spi_host_write_command(const dif_spi_host_t *spi_host,
503 uint16_t length,
505 dif_spi_host_direction_t direction,
506 bool last_segment) {
507 if (spi_host == NULL) {
508 return kDifBadArg;
509 }
510 write_command_reg(spi_host, length, speed, direction, last_segment);
511 return kDifOk;
512}
513
514dif_result_t dif_spi_host_error_set_enabled(const dif_spi_host_t *spi_host,
516 bool enable) {
517 if (spi_host == NULL || (error & ~(uint32_t)kDifSpiHostIrqErrorAll) != 0) {
518 return kDifBadArg;
519 }
520
521 uint32_t reg =
522 mmio_region_read32(spi_host->base_addr, SPI_HOST_ERROR_ENABLE_REG_OFFSET);
523 if (enable) {
524 reg |= error;
525 } else {
526 reg &= ~error;
527 }
528 mmio_region_write32(spi_host->base_addr, SPI_HOST_ERROR_ENABLE_REG_OFFSET,
529 reg);
530 return kDifOk;
531}
532
533dif_result_t dif_spi_host_error_get_enabled(const dif_spi_host_t *spi_host,
534 dif_spi_host_errors_t *errors) {
535 if (spi_host == NULL || errors == NULL) {
536 return kDifBadArg;
537 }
538
539 *errors =
540 mmio_region_read32(spi_host->base_addr, SPI_HOST_ERROR_ENABLE_REG_OFFSET);
541 return kDifOk;
542}
543
544dif_result_t dif_spi_host_get_error(const dif_spi_host_t *spi_host,
545 dif_spi_host_errors_t *error) {
546 if (spi_host == NULL || error == NULL) {
547 return kDifBadArg;
548 }
549
550 *error =
551 mmio_region_read32(spi_host->base_addr, SPI_HOST_ERROR_STATUS_REG_OFFSET);
552
553 return kDifOk;
554}
555
556dif_result_t dif_spi_host_wait_until_idle(const dif_spi_host_t *spi_host) {
557 if (spi_host == NULL) {
558 return kDifBadArg;
559 }
560
561 bool active;
562 do {
563 uint32_t reg =
564 mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET);
565 active = bitfield_bit32_read(reg, SPI_HOST_STATUS_ACTIVE_BIT);
566 } while (active);
567
568 return kDifOk;
569}