Software APIs
spi_device_testutils.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
5#include "sw/device/lib/testing/spi_device_testutils.h"
6
7#include "dt/dt_spi_device.h"
9#include "sw/device/lib/testing/test_framework/check.h"
10
11#define MODULE_ID MAKE_MODULE_ID('s', 'd', 't')
12
13/*
14 * The SPI device pins for which `spi_device_testutils_configure_pad_attrs()`
15 * configures the pad attributes.
16 */
17static const dt_pad_t kSpiDeviceDirectPads[4] = {
18 kDtPadSpiDeviceSd3, // sio[3]
19 kDtPadSpiDeviceSd2, // sio[2]
20 kDtPadSpiDeviceSd1, // sio[1]
21 kDtPadSpiDeviceSd0 // sio[0]
22};
23
24status_t spi_device_testutils_configure_passthrough(
25 dif_spi_device_handle_t *spi_device, uint32_t filters,
26 bool upload_write_commands) {
27 dif_spi_device_config_t spi_device_config = {
30 .device_mode = kDifSpiDeviceModePassthrough,
31 };
32 TRY(dif_spi_device_configure(spi_device, spi_device_config));
33
35 .status = upload_write_commands,
36 .jedec_id = false,
37 .sfdp = false,
38 .mailbox = false,
39 };
40 TRY(dif_spi_device_set_passthrough_intercept_config(spi_device,
41 intercept_config));
42
43 // Set up passthrough filter to allow all commands, initially.
44 TRY(dif_spi_device_set_all_passthrough_command_filters(spi_device,
46
47 dif_spi_device_flash_command_t read_commands[] = {
48 {
49 // Slot 0: ReadStatus1
50 .opcode = kSpiDeviceFlashOpReadStatus1,
51 .address_type = kDifSpiDeviceFlashAddrDisabled,
52 .dummy_cycles = 0,
53 .payload_io_type = kDifSpiDevicePayloadIoSingle,
54 .payload_dir_to_host = true,
55 },
56 {
57 // Slot 1: ReadStatus2
58 .opcode = kSpiDeviceFlashOpReadStatus2,
59 .address_type = kDifSpiDeviceFlashAddrDisabled,
60 .dummy_cycles = 0,
61 .payload_io_type = kDifSpiDevicePayloadIoSingle,
62 .payload_dir_to_host = true,
63 },
64 {
65 // Slot 2: ReadStatus3
66 .opcode = kSpiDeviceFlashOpReadStatus3,
67 .address_type = kDifSpiDeviceFlashAddrDisabled,
68 .dummy_cycles = 0,
69 .payload_io_type = kDifSpiDevicePayloadIoSingle,
70 .payload_dir_to_host = true,
71 },
72 {
73 // Slot 3: ReadJedecID
74 .opcode = kSpiDeviceFlashOpReadJedec,
75 .address_type = kDifSpiDeviceFlashAddrDisabled,
76 .dummy_cycles = 0,
77 .payload_io_type = kDifSpiDevicePayloadIoSingle,
78 .payload_dir_to_host = true,
79 },
80 {
81 // Slot 4: ReadSfdp
82 .opcode = kSpiDeviceFlashOpReadSfdp,
83 .address_type = kDifSpiDeviceFlashAddr3Byte,
84 .dummy_cycles = 8,
85 .payload_io_type = kDifSpiDevicePayloadIoSingle,
86 .payload_dir_to_host = true,
87 },
88 {
89 // Slot 5: ReadNormal
90 .opcode = kSpiDeviceFlashOpReadNormal,
91 .address_type = kDifSpiDeviceFlashAddrCfg,
92 .passthrough_swap_address = true,
93 .dummy_cycles = 0,
94 .payload_io_type = kDifSpiDevicePayloadIoSingle,
95 .payload_dir_to_host = true,
96 },
97 {
98 // Slot 6: ReadFast
99 .opcode = kSpiDeviceFlashOpReadFast,
100 .address_type = kDifSpiDeviceFlashAddrCfg,
101 .passthrough_swap_address = true,
102 .dummy_cycles = 8,
103 .payload_io_type = kDifSpiDevicePayloadIoSingle,
104 .payload_dir_to_host = true,
105 },
106 {
107 // Slot 7: ReadDual
108 .opcode = kSpiDeviceFlashOpReadDual,
109 .address_type = kDifSpiDeviceFlashAddrCfg,
110 .passthrough_swap_address = true,
111 .dummy_cycles = 8,
112 .payload_io_type = kDifSpiDevicePayloadIoDual,
113 .payload_dir_to_host = true,
114 },
115 {
116 // Slot 8: ReadQuad
117 .opcode = kSpiDeviceFlashOpReadQuad,
118 .address_type = kDifSpiDeviceFlashAddrCfg,
119 .passthrough_swap_address = true,
120 .dummy_cycles = 8,
121 .payload_io_type = kDifSpiDevicePayloadIoQuad,
122 .payload_dir_to_host = true,
123 },
124 {
125 // Slot 9: Read4b
126 .opcode = kSpiDeviceFlashOpRead4b,
127 .address_type = kDifSpiDeviceFlashAddr4Byte,
128 .passthrough_swap_address = true,
129 .dummy_cycles = 0,
130 .payload_io_type = kDifSpiDevicePayloadIoSingle,
131 .payload_dir_to_host = true,
132 },
133 {
134 // Slot 10: ReadFast4b
135 .opcode = kSpiDeviceFlashOpReadFast4b,
136 .address_type = kDifSpiDeviceFlashAddr4Byte,
137 .passthrough_swap_address = true,
138 .dummy_cycles = 8,
139 .payload_io_type = kDifSpiDevicePayloadIoSingle,
140 .payload_dir_to_host = true,
141 },
142 };
143 static_assert(ARRAYSIZE(read_commands) <= UINT8_MAX,
144 "Length of read_commands must fit in uint8_t or we must change "
145 "the type of i.");
146 for (uint8_t i = 0; i < ARRAYSIZE(read_commands); ++i) {
147 uint8_t slot = i + kSpiDeviceReadCommandSlotBase;
148 if (bitfield_bit32_read(filters, slot)) {
149 TRY(dif_spi_device_set_passthrough_command_filter(
150 spi_device, read_commands[i].opcode, kDifToggleEnabled));
151 }
152 TRY(dif_spi_device_set_flash_command_slot(
153 spi_device, slot, kDifToggleEnabled, read_commands[i]));
154 }
155 dif_spi_device_flash_command_t write_commands[] = {
156 {
157 // Slot 11: WriteStatus1
158 .opcode = kSpiDeviceFlashOpWriteStatus1,
159 .address_type = kDifSpiDeviceFlashAddrDisabled,
160 .payload_io_type = kDifSpiDevicePayloadIoSingle,
161 .payload_dir_to_host = false,
162 .upload = upload_write_commands,
163 .set_busy_status = upload_write_commands,
164 },
165 {
166 // Slot 12: WriteStatus2
167 .opcode = kSpiDeviceFlashOpWriteStatus2,
168 .address_type = kDifSpiDeviceFlashAddrDisabled,
169 .payload_io_type = kDifSpiDevicePayloadIoSingle,
170 .payload_dir_to_host = false,
171 .upload = upload_write_commands,
172 .set_busy_status = upload_write_commands,
173 },
174 {
175 // Slot 13: WriteStatus3
176 .opcode = kSpiDeviceFlashOpWriteStatus3,
177 .address_type = kDifSpiDeviceFlashAddrDisabled,
178 .payload_io_type = kDifSpiDevicePayloadIoSingle,
179 .payload_dir_to_host = false,
180 .upload = upload_write_commands,
181 .set_busy_status = upload_write_commands,
182 },
183 {
184 // Slot 14: ChipErase
185 .opcode = kSpiDeviceFlashOpChipErase,
186 .address_type = kDifSpiDeviceFlashAddrDisabled,
187 .payload_io_type = kDifSpiDevicePayloadIoNone,
188 .upload = upload_write_commands,
189 .set_busy_status = upload_write_commands,
190 },
191 {
192 // Slot 15: SectorErase
193 .opcode = kSpiDeviceFlashOpSectorErase,
194 .address_type = kDifSpiDeviceFlashAddrCfg,
195 .payload_io_type = kDifSpiDevicePayloadIoNone,
196 .upload = upload_write_commands,
197 .set_busy_status = upload_write_commands,
198 },
199 {
200 // Slot 16: BlockErase32k
201 .opcode = kSpiDeviceFlashOpBlockErase32k,
202 .address_type = kDifSpiDeviceFlashAddrCfg,
203 .payload_io_type = kDifSpiDevicePayloadIoNone,
204 .upload = upload_write_commands,
205 .set_busy_status = upload_write_commands,
206 },
207 {
208 // Slot 17: BlockErase64k
209 .opcode = kSpiDeviceFlashOpBlockErase64k,
210 .address_type = kDifSpiDeviceFlashAddrCfg,
211 .payload_io_type = kDifSpiDevicePayloadIoNone,
212 .upload = upload_write_commands,
213 .set_busy_status = upload_write_commands,
214 },
215 {
216 // Slot 18: PageProgram
217 .opcode = kSpiDeviceFlashOpPageProgram,
218 .address_type = kDifSpiDeviceFlashAddrCfg,
219 .payload_io_type = kDifSpiDevicePayloadIoSingle,
220 .payload_dir_to_host = false,
221 .upload = upload_write_commands,
222 .set_busy_status = upload_write_commands,
223 },
224 {
225 // Slot 19: SectorErase4b
226 .opcode = kSpiDeviceFlashOpSectorErase4b,
227 .address_type = kDifSpiDeviceFlashAddr4Byte,
228 .payload_io_type = kDifSpiDevicePayloadIoNone,
229 .upload = upload_write_commands,
230 .set_busy_status = upload_write_commands,
231 },
232 {
233 // Slot 20: BlockErase32k4b
234 .opcode = kSpiDeviceFlashOpBlockErase32k4b,
235 .address_type = kDifSpiDeviceFlashAddr4Byte,
236 .payload_io_type = kDifSpiDevicePayloadIoNone,
237 .upload = upload_write_commands,
238 .set_busy_status = upload_write_commands,
239 },
240 {
241 // Slot 21: BlockErase64k4b
242 .opcode = kSpiDeviceFlashOpBlockErase64k4b,
243 .address_type = kDifSpiDeviceFlashAddr4Byte,
244 .payload_io_type = kDifSpiDevicePayloadIoNone,
245 .upload = upload_write_commands,
246 .set_busy_status = upload_write_commands,
247 },
248 {
249 // Slot 22: PageProgram4b
250 .opcode = kSpiDeviceFlashOpPageProgram4b,
251 .address_type = kDifSpiDeviceFlashAddr4Byte,
252 .payload_io_type = kDifSpiDevicePayloadIoSingle,
253 .payload_dir_to_host = false,
254 .upload = upload_write_commands,
255 .set_busy_status = upload_write_commands,
256 },
257 {
258 // Slot 23: Reset
259 .opcode = kSpiDeviceFlashOpReset,
260 .address_type = kDifSpiDeviceFlashAddrDisabled,
261 .payload_io_type = kDifSpiDevicePayloadIoNone,
262 .upload = upload_write_commands,
263 .set_busy_status = upload_write_commands,
264 },
265
266 };
267 static_assert(ARRAYSIZE(write_commands) <= UINT8_MAX,
268 "Length of write_commands must fit into uint8_t");
269 for (uint8_t i = 0; i < ARRAYSIZE(write_commands); ++i) {
270 uint8_t slot = i + (uint8_t)kSpiDeviceWriteCommandSlotBase;
271 if (bitfield_bit32_read(filters, slot) || upload_write_commands) {
272 TRY(dif_spi_device_set_passthrough_command_filter(
273 spi_device, write_commands[i].opcode, kDifToggleEnabled));
274 }
275 TRY(dif_spi_device_set_flash_command_slot(
276 spi_device, slot, kDifToggleEnabled, write_commands[i]));
277 }
278 // This configuration for these commands does not guard against misbehaved
279 // hosts. The timing of any of these commands relative to an uploaded command
280 // cannot be determined.
281 TRY(dif_spi_device_configure_flash_wren_command(
282 spi_device, kDifToggleEnabled, kSpiDeviceFlashOpWriteEnable));
283 TRY(dif_spi_device_configure_flash_wrdi_command(
284 spi_device, kDifToggleEnabled, kSpiDeviceFlashOpWriteDisable));
285 TRY(dif_spi_device_configure_flash_en4b_command(
286 spi_device, kDifToggleEnabled, kSpiDeviceFlashOpEnter4bAddr));
287 TRY(dif_spi_device_configure_flash_ex4b_command(spi_device, kDifToggleEnabled,
288 kSpiDeviceFlashOpExit4bAddr));
289
290 if (upload_write_commands) {
291 TRY(dif_spi_device_set_passthrough_command_filter(
292 spi_device, kSpiDeviceFlashOpReadStatus1, kDifToggleEnabled));
293 TRY(dif_spi_device_set_passthrough_command_filter(
294 spi_device, kSpiDeviceFlashOpReadStatus2, kDifToggleEnabled));
295 TRY(dif_spi_device_set_passthrough_command_filter(
296 spi_device, kSpiDeviceFlashOpReadStatus3, kDifToggleEnabled));
297 TRY(dif_spi_device_set_passthrough_command_filter(
298 spi_device, kSpiDeviceFlashOpWriteEnable, kDifToggleEnabled));
299 TRY(dif_spi_device_set_passthrough_command_filter(
300 spi_device, kSpiDeviceFlashOpWriteDisable, kDifToggleEnabled));
301 }
302 return OK_STATUS();
303}
304
305status_t spi_device_testutils_configure_read_pipeline(
306 dif_spi_device_handle_t *spi_device,
307 dif_spi_device_read_pipeline_mode_t dual_mode,
308 dif_spi_device_read_pipeline_mode_t quad_mode) {
309 if (spi_device == NULL || dual_mode >= kDifSpiDeviceReadPipelineModeCount ||
310 quad_mode >= kDifSpiDeviceReadPipelineModeCount) {
311 return INVALID_ARGUMENT();
312 }
313
314 const dif_spi_device_flash_command_t dual_read_cmd = {
315 // Slot 7: ReadDual
316 .opcode = kSpiDeviceFlashOpReadDual,
317 .address_type = kDifSpiDeviceFlashAddrCfg,
318 .passthrough_swap_address = true,
319 .dummy_cycles = 8,
320 .payload_io_type = kDifSpiDevicePayloadIoDual,
321 .payload_dir_to_host = true,
322 .read_pipeline_mode = dual_mode,
323 };
324 const dif_spi_device_flash_command_t quad_read_cmd = {
325 // Slot 8: ReadQuad
326 .opcode = kSpiDeviceFlashOpReadQuad,
327 .address_type = kDifSpiDeviceFlashAddrCfg,
328 .passthrough_swap_address = true,
329 .dummy_cycles = 8,
330 .payload_io_type = kDifSpiDevicePayloadIoQuad,
331 .payload_dir_to_host = true,
332 .read_pipeline_mode = quad_mode,
333 };
334 TRY(dif_spi_device_set_flash_command_slot(spi_device, /*slot=*/7,
335 kDifToggleEnabled, dual_read_cmd));
336 TRY(dif_spi_device_set_flash_command_slot(spi_device, /*slot=*/8,
337 kDifToggleEnabled, quad_read_cmd));
338 return OK_STATUS();
339}
340
341status_t spi_device_testutils_configure_pad_attrs(dif_pinmux_t *pinmux) {
342 dif_pinmux_pad_attr_t out_attr;
343 dif_pinmux_pad_attr_t in_attr = {.slew_rate = 1, .drive_strength = 3};
344 dif_result_t res;
345 for (uint32_t i = 0; i < ARRAYSIZE(kSpiDeviceDirectPads); ++i) {
346 dt_pad_t pad = kSpiDeviceDirectPads[i];
347 res = dif_pinmux_pad_write_attrs_dt(pinmux, pad, in_attr, &out_attr);
348 if (res == kDifError) {
349 // Some target platforms may not support the specified value for slew rate
350 // and drive strength. If that's the case, use the values actually
351 // supported.
352 if (out_attr.slew_rate != in_attr.slew_rate) {
353 LOG_INFO(
354 "Specified slew rate not supported, trying supported slew rate");
355 in_attr.slew_rate = out_attr.slew_rate;
356 }
357 if (out_attr.drive_strength != in_attr.drive_strength) {
358 LOG_INFO(
359 "Specified drive strength not supported, trying supported drive "
360 "strength");
361 in_attr.drive_strength = out_attr.drive_strength;
362 }
363 TRY(dif_pinmux_pad_write_attrs_dt(pinmux, pad, in_attr, &out_attr));
364 // Note: fallthrough with the modified `in_attr` so that the same
365 // attributes are used for all pads.
366 }
367 }
368 return OK_STATUS();
369}
370
371status_t spi_device_testutils_wait_for_upload(dif_spi_device_handle_t *spid,
372 upload_info_t *info) {
373 // Wait for a SPI transaction cause an upload.
374 bool upload_pending;
375 do {
376 // The UploadCmdfifoNotEmpty interrupt status is updated after the SPI
377 // transaction completes.
378 TRY(dif_spi_device_irq_is_pending(
379 &spid->dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &upload_pending));
380 } while (!upload_pending);
381
382 uint8_t occupancy;
383
384 // Get the SPI opcode.
385 TRY(dif_spi_device_get_flash_command_fifo_occupancy(spid, &occupancy));
386 if (occupancy != 1) {
387 // Cannot have an uploaded command without an opcode.
388 return INTERNAL();
389 }
390 TRY(dif_spi_device_pop_flash_command_fifo(spid, &info->opcode));
391 // Get the flash_status register.
392 TRY(dif_spi_device_get_flash_status_registers(spid, &info->flash_status));
393
394 // Get the SPI address (if available).
395 TRY(dif_spi_device_get_flash_address_fifo_occupancy(spid, &occupancy));
396 if (occupancy) {
397 dif_toggle_t addr_4b;
398 TRY(dif_spi_device_get_4b_address_mode(spid, &addr_4b));
399 info->addr_4b = addr_4b;
400 TRY(dif_spi_device_pop_flash_address_fifo(spid, &info->address));
401 info->has_address = true;
402 }
403
404 // Get the SPI data payload (if available).
405 uint32_t start;
406 TRY(dif_spi_device_get_flash_payload_fifo_occupancy(spid, &info->data_len,
407 &start));
408 if (info->data_len) {
409 if (info->data_len > sizeof(info->data)) {
410 // We aren't expecting more than 256 bytes of data.
411 return INVALID_ARGUMENT();
412 }
413 TRY(dif_spi_device_read_flash_payload_buffer(spid, start, info->data_len,
414 info->data));
415 }
416
417 // Finished: ack the IRQ.
418 TRY(dif_spi_device_irq_acknowledge(&spid->dev,
420 return OK_STATUS();
421}