blob: 2d3ce22ba5800e46471127287288e05f08065922 [file] [log] [blame]
Miklós Márton324929c2019-08-01 19:14:10 +02001/*
2 * This file is part of the flashrom project.
3 *
4 * Copyright (C) 2019 Miklós Márton martonmiklosqdev@gmail.com
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 */
17
18/*
19 * Driver for programming SPI flash chips using the SPI port
20 * of the STMicroelectronics's STLINK-V3 programmer/debugger.
21 *
22 * The implementation is inspired by the ST's STLINK-V3-BRIDGE C++ API:
23 * https://www.st.com/en/development-tools/stlink-v3-bridge.html
24 */
25
26#include "flash.h"
27#include "programmer.h"
28#include "spi.h"
29
30#include <libusb.h>
31#include <limits.h>
32#include <stdint.h>
33#include <stdlib.h>
34#include <string.h>
35
36enum fw_version_check_result {
37 FW_VERSION_OK,
38 FW_VERSION_OLD,
39};
40
41enum spi_prescaler {
42 SPI_BAUDRATEPRESCALER_2 = 0,
43 SPI_BAUDRATEPRESCALER_4 = 1,
44 SPI_BAUDRATEPRESCALER_8 = 2,
45 SPI_BAUDRATEPRESCALER_16 = 3,
46 SPI_BAUDRATEPRESCALER_32 = 4,
47 SPI_BAUDRATEPRESCALER_64 = 5,
48 SPI_BAUDRATEPRESCALER_128 = 6,
49 SPI_BAUDRATEPRESCALER_256 = 7
50};
51
52enum spi_dir {
53 SPI_DIRECTION_2LINES_FULLDUPLEX = 0,
54 SPI_DIRECTION_2LINES_RXONLY = 1,
55 SPI_DIRECTION_1LINE_RX = 2,
56 SPI_DIRECTION_1LINE_TX = 3
57};
58
59enum spi_mode {
60 SPI_MODE_SLAVE = 0,
61 SPI_MODE_MASTER = 1
62};
63
64enum spi_datasize {
65 SPI_DATASIZE_16B = 0,
66 SPI_DATASIZE_8B = 1
67};
68
69enum spi_cpol {
70 SPI_CPOL_LOW = 0,
71 SPI_CPOL_HIGH = 1
72};
73
74enum spi_cpha {
75 SPI_CPHA_1EDGE = 0,
76 SPI_CPHA_2EDGE = 1
77};
78
79enum spi_firstbit {
80 SPI_FIRSTBIT_LSB = 0,
81 SPI_FIRSTBIT_MSB = 1
82};
83
84// ST calls the Chip select (CS) NSS == Negated Slave Select
85enum spi_nss {
86 SPI_NSS_SOFT = 0,
87 SPI_NSS_HARD = 1
88};
89
90enum spi_nss_level {
91 SPI_NSS_LOW = 0,
92 SPI_NSS_HIGH = 1
93};
94
95#define ST_GETVERSION_EXT 0xFB
96
97#define STLINK_BRIDGE_COMMAND 0xFC
98#define STLINK_BRIDGE_CLOSE 0x01
99#define STLINK_BRIDGE_GET_RWCMD_STATUS 0x02
100#define STLINK_BRIDGE_GET_CLOCK 0x03
101#define STLINK_BRIDGE_INIT_SPI 0x20
102#define STLINK_BRIDGE_WRITE_SPI 0x21
103#define STLINK_BRIDGE_READ_SPI 0x22
104#define STLINK_BRIDGE_CS_SPI 0x23
105
106#define STLINK_BRIDGE_SPI_ERROR 0x02
107
108#define STLINK_SPI_COM 0x02
109
110#define STLINK_EP_OUT 0x06
111#define STLINK_EP_IN 0x86
112
113#define FIRST_COMPATIBLE_BRIDGE_FW_VERSION 3
114
115#define USB_TIMEOUT_IN_MS 5000
116
Thomas Heijligencc853d82021-05-04 15:32:17 +0200117static const struct dev_entry devs_stlinkv3_spi[] = {
Miklós Márton9beddb62022-06-21 23:34:35 +0200118 {0x0483, 0x374E, NT, "STMicroelectronics", "STLINK-V3E"},
119 {0x0483, 0x374F, OK, "STMicroelectronics", "STLINK-V3S"},
120 {0x0483, 0x3753, OK, "STMicroelectronics", "STLINK-V3 dual VCP"},
121 {0x0483, 0x3754, NT, "STMicroelectronics", "STLINK-V3 no MSD"},
Miklós Márton324929c2019-08-01 19:14:10 +0200122 {0}
123};
124
125static struct libusb_context *usb_ctx;
126static libusb_device_handle *stlinkv3_handle;
127
128static int stlinkv3_command(uint8_t *command, size_t command_length,
129 uint8_t *answer, size_t answer_length, const char *command_name)
130{
131 int actual_length = 0;
132 int rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_OUT,
133 command, command_length,
134 &actual_length, USB_TIMEOUT_IN_MS);
135 if (rc != LIBUSB_TRANSFER_COMPLETED || (size_t)actual_length != command_length) {
136 msg_perr("Failed to issue the %s command: '%s'\n",
137 command_name,
138 libusb_error_name(rc));
139 return -1;
140 }
141
142 rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_IN,
143 answer, answer_length,
144 &actual_length, USB_TIMEOUT_IN_MS);
145 if (rc != LIBUSB_TRANSFER_COMPLETED || (size_t)actual_length != answer_length) {
146 msg_perr("Failed to get %s answer: '%s'\n",
147 command_name,
148 libusb_error_name(rc));
149 return -1;
150 }
151 return 0;
152}
153
154/**
155 * @param[out] bridge_input_clk Current input frequency in kHz of the given com.
156 */
157static int stlinkv3_get_clk(uint32_t *bridge_input_clk)
158{
Angel Pons7e134562021-06-07 13:29:13 +0200159 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200160 uint8_t answer[12];
161
162 if (bridge_input_clk == NULL)
163 return -1;
164
Miklós Márton324929c2019-08-01 19:14:10 +0200165 command[0] = STLINK_BRIDGE_COMMAND;
166 command[1] = STLINK_BRIDGE_GET_CLOCK;
167 command[2] = STLINK_SPI_COM;
168
169 if (stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_GET_CLOCK") != 0)
170 return -1;
171
172 *bridge_input_clk = (uint32_t)answer[4]
173 | (uint32_t)answer[5]<<8
174 | (uint32_t)answer[6]<<16
175 | (uint32_t)answer[7]<<24;
176 return 0;
177
178}
179
180static int stlinkv3_spi_calc_prescaler(uint16_t reqested_freq_in_kHz,
181 enum spi_prescaler *prescaler,
182 uint16_t *calculated_freq_in_kHz)
183{
184 uint32_t bridge_clk_in_kHz;
185 uint32_t calculated_prescaler = 1;
186 uint16_t prescaler_value;
187
188 if (stlinkv3_get_clk(&bridge_clk_in_kHz))
189 return -1;
190
191 calculated_prescaler = bridge_clk_in_kHz/reqested_freq_in_kHz;
192 // Apply a smaller frequency if not exact
193 if (calculated_prescaler <= 2) {
194 *prescaler = SPI_BAUDRATEPRESCALER_2;
195 prescaler_value = 2;
196 } else if (calculated_prescaler <= 4) {
197 *prescaler = SPI_BAUDRATEPRESCALER_4;
198 prescaler_value = 4;
199 } else if (calculated_prescaler <= 8) {
200 *prescaler = SPI_BAUDRATEPRESCALER_8;
201 prescaler_value = 8;
202 } else if (calculated_prescaler <= 16) {
203 *prescaler = SPI_BAUDRATEPRESCALER_16;
204 prescaler_value = 16;
205 } else if (calculated_prescaler <= 32) {
206 *prescaler = SPI_BAUDRATEPRESCALER_32;
207 prescaler_value = 32;
208 } else if (calculated_prescaler <= 64) {
209 *prescaler = SPI_BAUDRATEPRESCALER_64;
210 prescaler_value = 64;
211 } else if (calculated_prescaler <= 128) {
212 *prescaler = SPI_BAUDRATEPRESCALER_128;
213 prescaler_value = 128;
214 } else if (calculated_prescaler <= 256) {
215 *prescaler = SPI_BAUDRATEPRESCALER_256;
216 prescaler_value = 256;
217 } else {
218 // smaller frequency not possible
219 *prescaler = SPI_BAUDRATEPRESCALER_256;
220 prescaler_value = 256;
221 }
222
223 *calculated_freq_in_kHz = bridge_clk_in_kHz / prescaler_value;
224
225 return 0;
226}
227
228static int stlinkv3_check_version(enum fw_version_check_result *result)
229{
230 uint8_t answer[12];
Angel Pons7e134562021-06-07 13:29:13 +0200231 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200232
233 command[0] = ST_GETVERSION_EXT;
234 command[1] = 0x80;
235
236 if (stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "ST_GETVERSION_EXT") != 0)
237 return -1;
238
239 msg_pinfo("Connected to STLink V3 with bridge FW version: %d\n", answer[4]);
240 *result = answer[4] >= FIRST_COMPATIBLE_BRIDGE_FW_VERSION
241 ? FW_VERSION_OK
242 : FW_VERSION_OLD;
243 return 0;
244}
245
246static int stlinkv3_spi_open(uint16_t reqested_freq_in_kHz)
247{
Angel Pons7e134562021-06-07 13:29:13 +0200248 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200249 uint8_t answer[2];
250 uint16_t SCK_freq_in_kHz;
251 enum spi_prescaler prescaler;
252 enum fw_version_check_result fw_check_result;
253
254 if (stlinkv3_check_version(&fw_check_result)) {
Miklós Márton5c639b32020-07-26 10:40:46 +0200255 msg_perr("Failed to query FW version\n");
Miklós Márton324929c2019-08-01 19:14:10 +0200256 return -1;
257 }
258
259 if (fw_check_result != FW_VERSION_OK) {
Miklós Márton5c639b32020-07-26 10:40:46 +0200260 msg_pinfo("Your STLink V3 has a too old version of the bridge interface\n"
261 "Please update the firmware to version 2.33.25 or newer of the STSW-LINK007\n"
262 "which can be downloaded from here:\n"
263 "https://www.st.com/en/development-tools/stsw-link007.html\n");
Miklós Márton324929c2019-08-01 19:14:10 +0200264 return -1;
265 }
266
267 if (stlinkv3_spi_calc_prescaler(reqested_freq_in_kHz,
268 &prescaler,
269 &SCK_freq_in_kHz)) {
Miklós Márton5c639b32020-07-26 10:40:46 +0200270 msg_perr("Failed to calculate SPI clock prescaler\n");
Miklós Márton324929c2019-08-01 19:14:10 +0200271 return -1;
272 }
273 msg_pinfo("SCK frequency set to %d kHz\n", SCK_freq_in_kHz);
274
Miklós Márton324929c2019-08-01 19:14:10 +0200275 command[0] = STLINK_BRIDGE_COMMAND;
276 command[1] = STLINK_BRIDGE_INIT_SPI;
277 command[2] = SPI_DIRECTION_2LINES_FULLDUPLEX;
278 command[3] = (SPI_MODE_MASTER
279 | (SPI_CPHA_1EDGE << 1)
280 | (SPI_CPOL_LOW << 2)
281 | (SPI_FIRSTBIT_MSB << 3));
282 command[4] = SPI_DATASIZE_8B;
283 command[5] = SPI_NSS_SOFT;
284 command[6] = (uint8_t)prescaler;
285
286 return stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_INIT_SPI");
287}
288
289static int stlinkv3_get_last_readwrite_status(uint32_t *status)
290{
Angel Pons7e134562021-06-07 13:29:13 +0200291 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200292 uint16_t answer[4];
293
Miklós Márton324929c2019-08-01 19:14:10 +0200294 command[0] = STLINK_BRIDGE_COMMAND;
295 command[1] = STLINK_BRIDGE_GET_RWCMD_STATUS;
296
297 if (stlinkv3_command(command, sizeof(command),
298 (uint8_t *)answer, sizeof(answer),
299 "STLINK_BRIDGE_GET_RWCMD_STATUS") != 0)
300 return -1;
301
302 *status = (uint32_t)answer[2] | (uint32_t)answer[3]<<16;
303 return 0;
304}
305
306static int stlinkv3_spi_set_SPI_NSS(enum spi_nss_level nss_level)
307{
Angel Pons7e134562021-06-07 13:29:13 +0200308 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200309 uint8_t answer[2];
310
Miklós Márton324929c2019-08-01 19:14:10 +0200311 command[0] = STLINK_BRIDGE_COMMAND;
312 command[1] = STLINK_BRIDGE_CS_SPI;
313 command[2] = (uint8_t) (nss_level);
314
315 if (stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_CS_SPI") != 0)
316 return -1;
317 return 0;
318}
319
Edward O'Callaghan5eca4272020-04-12 17:27:53 +1000320static int stlinkv3_spi_transmit(const struct flashctx *flash,
Miklós Márton324929c2019-08-01 19:14:10 +0200321 unsigned int write_cnt,
322 unsigned int read_cnt,
323 const unsigned char *write_arr,
324 unsigned char *read_arr)
325{
Angel Pons7e134562021-06-07 13:29:13 +0200326 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200327 int rc = 0;
328 int actual_length = 0;
329 uint32_t rw_status = 0;
Nico Huber370a9f32019-12-31 18:22:02 +0100330 unsigned int i;
Miklós Márton324929c2019-08-01 19:14:10 +0200331
332 if (stlinkv3_spi_set_SPI_NSS(SPI_NSS_LOW)) {
333 msg_perr("Failed to set the NSS pin to low\n");
334 return -1;
335 }
336
Miklós Márton324929c2019-08-01 19:14:10 +0200337 command[0] = STLINK_BRIDGE_COMMAND;
338 command[1] = STLINK_BRIDGE_WRITE_SPI;
339 command[2] = (uint8_t)write_cnt;
340 command[3] = (uint8_t)(write_cnt >> 8);
341
Nico Huber370a9f32019-12-31 18:22:02 +0100342 for (i = 0; (i < 8) && (i < write_cnt); i++)
Miklós Márton324929c2019-08-01 19:14:10 +0200343 command[4+i] = write_arr[i];
344
345 rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_OUT,
346 command, sizeof(command),
347 &actual_length, USB_TIMEOUT_IN_MS);
348 if (rc != LIBUSB_TRANSFER_COMPLETED || actual_length != sizeof(command)) {
349 msg_perr("Failed to issue the STLINK_BRIDGE_WRITE_SPI command: '%s'\n",
350 libusb_error_name(rc));
351 goto transmit_err;
352 }
353
354 if (write_cnt > 8) {
355 rc = libusb_bulk_transfer(stlinkv3_handle,
356 STLINK_EP_OUT,
357 (unsigned char *)&write_arr[8],
358 (unsigned int)(write_cnt - 8),
359 &actual_length,
360 USB_TIMEOUT_IN_MS);
361 if (rc != LIBUSB_TRANSFER_COMPLETED || (unsigned int)actual_length != (write_cnt - 8)) {
362 msg_perr("Failed to send the data after the STLINK_BRIDGE_WRITE_SPI command: '%s'\n",
363 libusb_error_name(rc));
364 goto transmit_err;
365 }
366 }
367
368 if (stlinkv3_get_last_readwrite_status(&rw_status))
369 return -1;
370
371 if (rw_status != 0) {
372 msg_perr("SPI read/write failure: %d\n", rw_status);
373 goto transmit_err;
374 }
375
376 if (read_cnt) {
377 command[1] = STLINK_BRIDGE_READ_SPI;
378 command[2] = (uint8_t)read_cnt;
379 command[3] = (uint8_t)(read_cnt >> 8);
380
381 rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_OUT,
382 command, sizeof(command),
383 &actual_length, USB_TIMEOUT_IN_MS);
384 if (rc != LIBUSB_TRANSFER_COMPLETED || (unsigned int)actual_length != sizeof(command)) {
385 msg_perr("Failed to issue the STLINK_BRIDGE_READ_SPI command: '%s'\n",
386 libusb_error_name(rc));
387 goto transmit_err;
388 }
389
390 rc = libusb_bulk_transfer(stlinkv3_handle,
391 STLINK_EP_IN,
392 (unsigned char *)read_arr,
393 (int)read_cnt,
394 &actual_length,
395 USB_TIMEOUT_IN_MS);
396 if (rc != LIBUSB_TRANSFER_COMPLETED || (unsigned int)actual_length != read_cnt) {
Martin Rothf6c1cb12022-03-15 10:55:25 -0600397 msg_perr("Failed to retrieve the STLINK_BRIDGE_READ_SPI answer: '%s'\n",
Miklós Márton324929c2019-08-01 19:14:10 +0200398 libusb_error_name(rc));
399 goto transmit_err;
400 }
401 }
402
403 if (stlinkv3_get_last_readwrite_status(&rw_status))
404 goto transmit_err;
405
406 if (rw_status != 0) {
407 msg_perr("SPI read/write failure: %d\n", rw_status);
408 goto transmit_err;
409 }
410
411 if (stlinkv3_spi_set_SPI_NSS(SPI_NSS_HIGH)) {
412 msg_perr("Failed to set the NSS pin to high\n");
413 return -1;
414 }
415 return 0;
416
417transmit_err:
418 if (stlinkv3_spi_set_SPI_NSS(SPI_NSS_HIGH))
419 msg_perr("Failed to set the NSS pin to high\n");
420 return -1;
421}
422
423static int stlinkv3_spi_shutdown(void *data)
424{
Angel Pons7e134562021-06-07 13:29:13 +0200425 uint8_t command[16] = { 0 };
Miklós Márton324929c2019-08-01 19:14:10 +0200426 uint8_t answer[2];
427
Miklós Márton324929c2019-08-01 19:14:10 +0200428 command[0] = STLINK_BRIDGE_COMMAND;
429 command[1] = STLINK_BRIDGE_CLOSE;
430 command[2] = STLINK_SPI_COM;
431
432 stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_CLOSE");
433
434 libusb_close(stlinkv3_handle);
435 libusb_exit(usb_ctx);
436
437 return 0;
438}
439
440static const struct spi_master spi_programmer_stlinkv3 = {
Thomas Heijligen43040f22022-06-23 14:38:35 +0200441 .max_data_read = UINT16_MAX,
442 .max_data_write = UINT16_MAX,
443 .command = stlinkv3_spi_transmit,
444 .multicommand = default_spi_send_multicommand,
445 .read = default_spi_read,
446 .write_256 = default_spi_write_256,
447 .write_aai = default_spi_write_aai,
Miklós Márton324929c2019-08-01 19:14:10 +0200448};
449
Thomas Heijligencc853d82021-05-04 15:32:17 +0200450static int stlinkv3_spi_init(void)
Miklós Márton324929c2019-08-01 19:14:10 +0200451{
452 uint16_t sck_freq_kHz = 1000; // selecting 1 MHz SCK is a good bet
453 char *speed_str = NULL;
454 char *serialno = NULL;
455 char *endptr = NULL;
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000456 int ret = 1;
Miklós Márton9beddb62022-06-21 23:34:35 +0200457 int devIndex = 0;
Miklós Márton324929c2019-08-01 19:14:10 +0200458
459 libusb_init(&usb_ctx);
460 if (!usb_ctx) {
461 msg_perr("Could not initialize libusb!\n");
462 return 1;
463 }
464
465 serialno = extract_programmer_param("serial");
466 if (serialno)
467 msg_pdbg("Opening STLINK-V3 with serial: %s\n", serialno);
Miklós Márton9beddb62022-06-21 23:34:35 +0200468
469
470 while (devs_stlinkv3_spi[devIndex].vendor_id != 0) {
471 stlinkv3_handle = usb_dev_get_by_vid_pid_serial(usb_ctx,
472 devs_stlinkv3_spi[devIndex].vendor_id,
473 devs_stlinkv3_spi[devIndex].device_id,
474 serialno);
475 if (stlinkv3_handle)
476 break;
477 devIndex++;
478 }
Miklós Márton324929c2019-08-01 19:14:10 +0200479
480 if (!stlinkv3_handle) {
481 if (serialno)
482 msg_perr("No STLINK-V3 seems to be connected with serial %s\n", serialno);
483 else
484 msg_perr("Could not find any connected STLINK-V3\n");
485 free(serialno);
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000486 goto init_err_exit;
Miklós Márton324929c2019-08-01 19:14:10 +0200487 }
488 free(serialno);
489
490 speed_str = extract_programmer_param("spispeed");
491 if (speed_str) {
492 sck_freq_kHz = strtoul(speed_str, &endptr, 0);
Patrick Georgi355a1df2020-04-23 09:36:12 +0200493 if (*endptr || sck_freq_kHz == 0) {
Miklós Márton324929c2019-08-01 19:14:10 +0200494 msg_perr("The spispeed parameter passed with invalid format: %s\n",
495 speed_str);
Patrick Georgi355a1df2020-04-23 09:36:12 +0200496 msg_perr("Please pass the parameter "
497 "with a simple non-zero number in kHz\n");
Patrick Georgi739899a2020-04-23 09:35:06 +0200498 free(speed_str);
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000499 ret = -1;
500 goto init_err_exit;
Miklós Márton324929c2019-08-01 19:14:10 +0200501 }
502 free(speed_str);
503 }
504
505 if (stlinkv3_spi_open(sck_freq_kHz))
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000506 goto init_err_exit;
Miklós Márton324929c2019-08-01 19:14:10 +0200507
508 if (register_shutdown(stlinkv3_spi_shutdown, NULL))
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000509 goto init_err_cleanup_exit;
Miklós Márton324929c2019-08-01 19:14:10 +0200510
511 if (register_spi_master(&spi_programmer_stlinkv3))
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000512 return 1; /* shutdown function does cleanup */
Miklós Márton324929c2019-08-01 19:14:10 +0200513
514 return 0;
515
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000516init_err_cleanup_exit:
517 stlinkv3_spi_shutdown(NULL);
Miklós Márton324929c2019-08-01 19:14:10 +0200518 return 1;
Anastasia Klimchuk67aaed72021-05-14 14:01:34 +1000519
520init_err_exit:
521 if (stlinkv3_handle)
522 libusb_close(stlinkv3_handle);
523 libusb_exit(usb_ctx);
524 return ret;
Miklós Márton324929c2019-08-01 19:14:10 +0200525}
Thomas Heijligencc853d82021-05-04 15:32:17 +0200526
527const struct programmer_entry programmer_stlinkv3_spi = {
528 .name = "stlinkv3_spi",
529 .type = USB,
530 .devs.dev = devs_stlinkv3_spi,
531 .init = stlinkv3_spi_init,
532 .map_flash_region = fallback_map,
533 .unmap_flash_region = fallback_unmap,
534 .delay = internal_delay,
535};