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