Patrick Georgi | ac95903 | 2020-05-05 22:49:26 +0200 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 2 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 3 | #include <assert.h> |
Elyes HAOUAS | 0edf6a5 | 2019-10-26 18:41:47 +0200 | [diff] [blame] | 4 | #include <boot/coreboot_tables.h> |
Elyes HAOUAS | f7a7e65 | 2021-02-01 19:39:07 +0100 | [diff] [blame] | 5 | #include <commonlib/region.h> |
Elyes HAOUAS | 20eaef0 | 2019-03-29 17:45:28 +0100 | [diff] [blame] | 6 | #include <console/console.h> |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 7 | #include <string.h> |
Zheng Bao | 600784e | 2013-02-07 17:30:23 +0800 | [diff] [blame] | 8 | #include <spi-generic.h> |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 9 | #include <spi_flash.h> |
Elyes HAOUAS | bf0970e | 2019-03-21 11:10:03 +0100 | [diff] [blame] | 10 | #include <timer.h> |
Elyes HAOUAS | ede8dd0 | 2019-06-23 06:57:53 +0200 | [diff] [blame] | 11 | #include <types.h> |
Edward O'Callaghan | c4561e2 | 2014-06-26 15:02:40 +1000 | [diff] [blame] | 12 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 13 | #include "spi_flash_internal.h" |
| 14 | |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 15 | #if CONFIG(SPI_FLASH_FORCE_4_BYTE_ADDR_MODE) |
| 16 | #define ADDR_MOD 1 |
| 17 | #else |
| 18 | #define ADDR_MOD 0 |
| 19 | #endif |
| 20 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 21 | static void spi_flash_addr(u32 addr, u8 *cmd) |
| 22 | { |
| 23 | /* cmd[0] is actual command */ |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 24 | if (CONFIG(SPI_FLASH_FORCE_4_BYTE_ADDR_MODE)) { |
| 25 | cmd[1] = addr >> 24; |
| 26 | cmd[2] = addr >> 16; |
| 27 | cmd[3] = addr >> 8; |
| 28 | cmd[4] = addr >> 0; |
| 29 | } else { |
| 30 | cmd[1] = addr >> 16; |
| 31 | cmd[2] = addr >> 8; |
| 32 | cmd[3] = addr >> 0; |
| 33 | } |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 34 | } |
| 35 | |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 36 | static int do_spi_flash_cmd(const struct spi_slave *spi, const u8 *dout, |
Furquan Shaikh | 0dba025 | 2016-11-30 04:34:22 -0800 | [diff] [blame] | 37 | size_t bytes_out, void *din, size_t bytes_in) |
David Hendricks | f101bbe | 2014-03-21 19:13:34 -0700 | [diff] [blame] | 38 | { |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 39 | int ret; |
Furquan Shaikh | c2973d1 | 2016-11-29 22:07:42 -0800 | [diff] [blame] | 40 | /* |
| 41 | * SPI flash requires command-response kind of behavior. Thus, two |
| 42 | * separate SPI vectors are required -- first to transmit dout and other |
| 43 | * to receive in din. If some specialized SPI flash controllers |
| 44 | * (e.g. x86) can perform both command and response together, it should |
| 45 | * be handled at SPI flash controller driver level. |
| 46 | */ |
| 47 | struct spi_op vectors[] = { |
| 48 | [0] = { .dout = dout, .bytesout = bytes_out, |
| 49 | .din = NULL, .bytesin = 0, }, |
| 50 | [1] = { .dout = NULL, .bytesout = 0, |
| 51 | .din = din, .bytesin = bytes_in }, |
| 52 | }; |
Kyösti Mälkki | 85b2b27 | 2017-01-10 06:25:57 +0200 | [diff] [blame] | 53 | size_t count = ARRAY_SIZE(vectors); |
| 54 | if (!bytes_in) |
| 55 | count = 1; |
David Hendricks | f101bbe | 2014-03-21 19:13:34 -0700 | [diff] [blame] | 56 | |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 57 | ret = spi_claim_bus(spi); |
| 58 | if (ret) |
David Hendricks | 032c843 | 2014-04-11 19:48:55 -0700 | [diff] [blame] | 59 | return ret; |
| 60 | |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 61 | ret = spi_xfer_vector(spi, vectors, count); |
David Hendricks | f101bbe | 2014-03-21 19:13:34 -0700 | [diff] [blame] | 62 | |
David Hendricks | 032c843 | 2014-04-11 19:48:55 -0700 | [diff] [blame] | 63 | spi_release_bus(spi); |
David Hendricks | f101bbe | 2014-03-21 19:13:34 -0700 | [diff] [blame] | 64 | return ret; |
| 65 | } |
| 66 | |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 67 | static int do_dual_output_cmd(const struct spi_slave *spi, const u8 *dout, |
| 68 | size_t bytes_out, void *din, size_t bytes_in) |
Julius Werner | 99e45ce | 2019-06-06 17:03:44 -0700 | [diff] [blame] | 69 | { |
| 70 | int ret; |
| 71 | |
| 72 | /* |
| 73 | * spi_xfer_vector() will automatically fall back to .xfer() if |
| 74 | * .xfer_vector() is unimplemented. So using vector API here is more |
| 75 | * flexible, even though a controller that implements .xfer_vector() |
| 76 | * and (the non-vector based) .xfer_dual() but not .xfer() would be |
| 77 | * pretty odd. |
| 78 | */ |
| 79 | struct spi_op vector = { .dout = dout, .bytesout = bytes_out, |
| 80 | .din = NULL, .bytesin = 0 }; |
| 81 | |
| 82 | ret = spi_claim_bus(spi); |
| 83 | if (ret) |
| 84 | return ret; |
| 85 | |
| 86 | ret = spi_xfer_vector(spi, &vector, 1); |
| 87 | |
| 88 | if (!ret) |
| 89 | ret = spi->ctrlr->xfer_dual(spi, NULL, 0, din, bytes_in); |
| 90 | |
| 91 | spi_release_bus(spi); |
| 92 | return ret; |
| 93 | } |
| 94 | |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 95 | static int do_dual_io_cmd(const struct spi_slave *spi, const u8 *dout, |
| 96 | size_t bytes_out, void *din, size_t bytes_in) |
| 97 | { |
| 98 | int ret; |
| 99 | |
| 100 | /* Only the very first byte (opcode) is transferred in "single" mode. */ |
| 101 | struct spi_op vector = { .dout = dout, .bytesout = 1, |
| 102 | .din = NULL, .bytesin = 0 }; |
| 103 | |
| 104 | ret = spi_claim_bus(spi); |
| 105 | if (ret) |
| 106 | return ret; |
| 107 | |
| 108 | ret = spi_xfer_vector(spi, &vector, 1); |
| 109 | |
| 110 | if (!ret) |
| 111 | ret = spi->ctrlr->xfer_dual(spi, &dout[1], bytes_out - 1, NULL, 0); |
| 112 | |
| 113 | if (!ret) |
| 114 | ret = spi->ctrlr->xfer_dual(spi, NULL, 0, din, bytes_in); |
| 115 | |
| 116 | spi_release_bus(spi); |
| 117 | return ret; |
| 118 | } |
| 119 | |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 120 | int spi_flash_cmd(const struct spi_slave *spi, u8 cmd, void *response, size_t len) |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 121 | { |
David Hendricks | f101bbe | 2014-03-21 19:13:34 -0700 | [diff] [blame] | 122 | int ret = do_spi_flash_cmd(spi, &cmd, sizeof(cmd), response, len); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 123 | if (ret) |
| 124 | printk(BIOS_WARNING, "SF: Failed to send command %02x: %d\n", cmd, ret); |
| 125 | |
| 126 | return ret; |
| 127 | } |
| 128 | |
Angel Pons | df40ca9 | 2021-04-16 13:57:07 +0000 | [diff] [blame] | 129 | /* TODO: This code is quite possibly broken and overflowing stacks. Fix ASAP! */ |
| 130 | #pragma GCC diagnostic push |
| 131 | #if defined(__GNUC__) && !defined(__clang__) |
| 132 | #pragma GCC diagnostic ignored "-Wstack-usage=" |
| 133 | #endif |
| 134 | #pragma GCC diagnostic ignored "-Wvla" |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 135 | int spi_flash_cmd_write(const struct spi_slave *spi, const u8 *cmd, |
| 136 | size_t cmd_len, const void *data, size_t data_len) |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 137 | { |
Patrick Georgi | 2415196 | 2018-08-24 22:45:50 +0000 | [diff] [blame] | 138 | int ret; |
Angel Pons | df40ca9 | 2021-04-16 13:57:07 +0000 | [diff] [blame] | 139 | u8 buff[cmd_len + data_len]; |
Patrick Georgi | 2415196 | 2018-08-24 22:45:50 +0000 | [diff] [blame] | 140 | memcpy(buff, cmd, cmd_len); |
| 141 | memcpy(buff + cmd_len, data, data_len); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 142 | |
Patrick Georgi | 2415196 | 2018-08-24 22:45:50 +0000 | [diff] [blame] | 143 | ret = do_spi_flash_cmd(spi, buff, cmd_len + data_len, NULL, 0); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 144 | if (ret) { |
| 145 | printk(BIOS_WARNING, "SF: Failed to send write command (%zu bytes): %d\n", |
| 146 | data_len, ret); |
| 147 | } |
| 148 | |
| 149 | return ret; |
| 150 | } |
Angel Pons | df40ca9 | 2021-04-16 13:57:07 +0000 | [diff] [blame] | 151 | #pragma GCC diagnostic pop |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 152 | |
Aaron Durbin | 3a649ee | 2018-01-25 17:30:45 -0700 | [diff] [blame] | 153 | /* Perform the read operation honoring spi controller fifo size, reissuing |
| 154 | * the read command until the full request completed. */ |
Aaron Durbin | cb01aa5 | 2020-01-11 11:44:47 -0700 | [diff] [blame] | 155 | int spi_flash_cmd_read(const struct spi_flash *flash, u32 offset, |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 156 | size_t len, void *buf) |
Aaron Durbin | 3a649ee | 2018-01-25 17:30:45 -0700 | [diff] [blame] | 157 | { |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 158 | u8 cmd[5 + ADDR_MOD]; |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 159 | int ret, cmd_len; |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 160 | int (*do_cmd)(const struct spi_slave *spi, const u8 *din, |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 161 | size_t in_bytes, void *out, size_t out_bytes); |
| 162 | |
| 163 | if (CONFIG(SPI_FLASH_NO_FAST_READ)) { |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 164 | cmd_len = 4 + ADDR_MOD; |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 165 | cmd[0] = CMD_READ_ARRAY_SLOW; |
| 166 | do_cmd = do_spi_flash_cmd; |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 167 | } else if (flash->flags.dual_io && flash->spi.ctrlr->xfer_dual) { |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 168 | cmd_len = 5 + ADDR_MOD; |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 169 | cmd[0] = CMD_READ_FAST_DUAL_IO; |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 170 | cmd[4 + ADDR_MOD] = 0; |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 171 | do_cmd = do_dual_io_cmd; |
| 172 | } else if (flash->flags.dual_output && flash->spi.ctrlr->xfer_dual) { |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 173 | cmd_len = 5 + ADDR_MOD; |
Julius Werner | 99e45ce | 2019-06-06 17:03:44 -0700 | [diff] [blame] | 174 | cmd[0] = CMD_READ_FAST_DUAL_OUTPUT; |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 175 | cmd[4 + ADDR_MOD] = 0; |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 176 | do_cmd = do_dual_output_cmd; |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 177 | } else { |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 178 | cmd_len = 5 + ADDR_MOD; |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 179 | cmd[0] = CMD_READ_ARRAY_FAST; |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 180 | cmd[4 + ADDR_MOD] = 0; |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 181 | do_cmd = do_spi_flash_cmd; |
| 182 | } |
| 183 | |
Aaron Durbin | 3a649ee | 2018-01-25 17:30:45 -0700 | [diff] [blame] | 184 | uint8_t *data = buf; |
Aaron Durbin | 3a649ee | 2018-01-25 17:30:45 -0700 | [diff] [blame] | 185 | while (len) { |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 186 | size_t xfer_len = spi_crop_chunk(&flash->spi, cmd_len, len); |
| 187 | spi_flash_addr(offset, cmd); |
| 188 | ret = do_cmd(&flash->spi, cmd, cmd_len, data, xfer_len); |
| 189 | if (ret) { |
| 190 | printk(BIOS_WARNING, |
| 191 | "SF: Failed to send read command %#.2x(%#x, %#zx): %d\n", |
| 192 | cmd[0], offset, xfer_len, ret); |
Aaron Durbin | 3a649ee | 2018-01-25 17:30:45 -0700 | [diff] [blame] | 193 | return ret; |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 194 | } |
Aaron Durbin | 3a649ee | 2018-01-25 17:30:45 -0700 | [diff] [blame] | 195 | offset += xfer_len; |
| 196 | data += xfer_len; |
| 197 | len -= xfer_len; |
| 198 | } |
| 199 | |
| 200 | return 0; |
| 201 | } |
| 202 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 203 | int spi_flash_cmd_poll_bit(const struct spi_flash *flash, unsigned long timeout, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 204 | u8 cmd, u8 poll_bit) |
| 205 | { |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 206 | const struct spi_slave *spi = &flash->spi; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 207 | int ret; |
Raul E Rangel | b90e251 | 2022-04-29 14:06:09 -0600 | [diff] [blame] | 208 | int attempt = 0; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 209 | u8 status; |
Raul E Rangel | b10cbd0 | 2022-04-28 16:17:01 -0600 | [diff] [blame] | 210 | struct stopwatch sw; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 211 | |
Raul E Rangel | b10cbd0 | 2022-04-28 16:17:01 -0600 | [diff] [blame] | 212 | stopwatch_init_msecs_expire(&sw, timeout); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 213 | do { |
Raul E Rangel | b90e251 | 2022-04-29 14:06:09 -0600 | [diff] [blame] | 214 | attempt++; |
| 215 | |
Julius Werner | 1b7f99b | 2019-06-05 16:34:01 -0700 | [diff] [blame] | 216 | ret = do_spi_flash_cmd(spi, &cmd, 1, &status, 1); |
Raul E Rangel | b90e251 | 2022-04-29 14:06:09 -0600 | [diff] [blame] | 217 | if (ret) { |
| 218 | printk(BIOS_WARNING, |
| 219 | "SF: SPI command failed on attempt %d with rc %d\n", attempt, |
| 220 | ret); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 221 | return -1; |
Raul E Rangel | b90e251 | 2022-04-29 14:06:09 -0600 | [diff] [blame] | 222 | } |
| 223 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 224 | if ((status & poll_bit) == 0) |
Dave Frodin | c50c0ab | 2014-06-11 12:53:47 -0600 | [diff] [blame] | 225 | return 0; |
Raul E Rangel | b10cbd0 | 2022-04-28 16:17:01 -0600 | [diff] [blame] | 226 | } while (!stopwatch_expired(&sw)); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 227 | |
Rob Barnes | d522f38 | 2022-09-12 06:31:47 -0600 | [diff] [blame] | 228 | printk(BIOS_WARNING, "SF: timeout at %lld msec after %d attempts\n", |
Raul E Rangel | b90e251 | 2022-04-29 14:06:09 -0600 | [diff] [blame] | 229 | stopwatch_duration_msecs(&sw), attempt); |
Raul E Rangel | b10cbd0 | 2022-04-28 16:17:01 -0600 | [diff] [blame] | 230 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 231 | return -1; |
| 232 | } |
| 233 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 234 | int spi_flash_cmd_wait_ready(const struct spi_flash *flash, |
| 235 | unsigned long timeout) |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 236 | { |
| 237 | return spi_flash_cmd_poll_bit(flash, timeout, |
| 238 | CMD_READ_STATUS, STATUS_WIP); |
| 239 | } |
| 240 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 241 | int spi_flash_cmd_erase(const struct spi_flash *flash, u32 offset, size_t len) |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 242 | { |
| 243 | u32 start, end, erase_size; |
Richard Spiegel | 22ba205 | 2019-02-18 15:31:33 -0700 | [diff] [blame] | 244 | int ret = -1; |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 245 | u8 cmd[4 + ADDR_MOD]; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 246 | |
| 247 | erase_size = flash->sector_size; |
| 248 | if (offset % erase_size || len % erase_size) { |
| 249 | printk(BIOS_WARNING, "SF: Erase offset/length not multiple of erase size\n"); |
| 250 | return -1; |
| 251 | } |
Richard Spiegel | d87a9b8 | 2018-08-07 16:41:02 -0700 | [diff] [blame] | 252 | if (len == 0) { |
| 253 | printk(BIOS_WARNING, "SF: Erase length cannot be 0\n"); |
| 254 | return -1; |
| 255 | } |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 256 | |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 257 | cmd[0] = flash->erase_cmd; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 258 | start = offset; |
| 259 | end = start + len; |
| 260 | |
| 261 | while (offset < end) { |
| 262 | spi_flash_addr(offset, cmd); |
| 263 | offset += erase_size; |
| 264 | |
Fred Reitberger | 3b5d9ee | 2023-03-23 14:35:39 -0400 | [diff] [blame] | 265 | if (CONFIG(DEBUG_SPI_FLASH)) { |
| 266 | if (ADDR_MOD) |
| 267 | printk(BIOS_SPEW, "SF: erase %2x %2x %2x %2x %2x (%x)\n", |
| 268 | cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], offset); |
| 269 | else |
| 270 | printk(BIOS_SPEW, "SF: erase %2x %2x %2x %2x (%x)\n", |
| 271 | cmd[0], cmd[1], cmd[2], cmd[3], offset); |
| 272 | } |
| 273 | |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 274 | ret = spi_flash_cmd(&flash->spi, CMD_WRITE_ENABLE, NULL, 0); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 275 | if (ret) |
| 276 | goto out; |
| 277 | |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 278 | ret = spi_flash_cmd_write(&flash->spi, cmd, sizeof(cmd), NULL, 0); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 279 | if (ret) |
| 280 | goto out; |
| 281 | |
Uwe Poeche | 17362be | 2019-07-17 14:27:13 +0200 | [diff] [blame] | 282 | ret = spi_flash_cmd_wait_ready(flash, |
| 283 | SPI_FLASH_PAGE_ERASE_TIMEOUT_MS); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 284 | if (ret) |
| 285 | goto out; |
| 286 | } |
| 287 | |
| 288 | printk(BIOS_DEBUG, "SF: Successfully erased %zu bytes @ %#x\n", len, start); |
| 289 | |
Patrick Georgi | 20959ba | 2012-05-12 23:30:36 +0200 | [diff] [blame] | 290 | out: |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 291 | return ret; |
| 292 | } |
| 293 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 294 | int spi_flash_cmd_status(const struct spi_flash *flash, u8 *reg) |
Duncan Laurie | fb03239 | 2015-01-15 15:28:46 -0800 | [diff] [blame] | 295 | { |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 296 | return spi_flash_cmd(&flash->spi, flash->status_cmd, reg, sizeof(*reg)); |
Duncan Laurie | fb03239 | 2015-01-15 15:28:46 -0800 | [diff] [blame] | 297 | } |
| 298 | |
Aaron Durbin | d701ef7 | 2019-12-27 15:16:17 -0700 | [diff] [blame] | 299 | int spi_flash_cmd_write_page_program(const struct spi_flash *flash, u32 offset, |
| 300 | size_t len, const void *buf) |
| 301 | { |
| 302 | unsigned long byte_addr; |
| 303 | unsigned long page_size; |
| 304 | size_t chunk_len; |
| 305 | size_t actual; |
| 306 | int ret = 0; |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 307 | u8 cmd[4 + ADDR_MOD]; |
Aaron Durbin | d701ef7 | 2019-12-27 15:16:17 -0700 | [diff] [blame] | 308 | |
| 309 | page_size = flash->page_size; |
| 310 | cmd[0] = flash->pp_cmd; |
| 311 | |
| 312 | for (actual = 0; actual < len; actual += chunk_len) { |
| 313 | byte_addr = offset % page_size; |
| 314 | chunk_len = MIN(len - actual, page_size - byte_addr); |
| 315 | chunk_len = spi_crop_chunk(&flash->spi, sizeof(cmd), chunk_len); |
| 316 | |
| 317 | spi_flash_addr(offset, cmd); |
| 318 | if (CONFIG(DEBUG_SPI_FLASH)) { |
Fred Reitberger | 52be7f6 | 2023-03-21 15:37:06 -0400 | [diff] [blame] | 319 | if (ADDR_MOD) |
| 320 | printk(BIOS_SPEW, |
| 321 | "PP: %p => cmd = { 0x%02x 0x%02x%02x%02x%02x } chunk_len = %zu\n", |
| 322 | buf + actual, cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], |
| 323 | chunk_len); |
| 324 | else |
| 325 | printk(BIOS_SPEW, |
| 326 | "PP: %p => cmd = { 0x%02x 0x%02x%02x%02x } chunk_len = %zu\n", |
| 327 | buf + actual, cmd[0], cmd[1], cmd[2], cmd[3], |
| 328 | chunk_len); |
Aaron Durbin | d701ef7 | 2019-12-27 15:16:17 -0700 | [diff] [blame] | 329 | } |
| 330 | |
| 331 | ret = spi_flash_cmd(&flash->spi, flash->wren_cmd, NULL, 0); |
| 332 | if (ret < 0) { |
| 333 | printk(BIOS_WARNING, "SF: Enabling Write failed\n"); |
| 334 | goto out; |
| 335 | } |
| 336 | |
| 337 | ret = spi_flash_cmd_write(&flash->spi, cmd, sizeof(cmd), |
| 338 | buf + actual, chunk_len); |
| 339 | if (ret < 0) { |
| 340 | printk(BIOS_WARNING, "SF: Page Program failed\n"); |
| 341 | goto out; |
| 342 | } |
| 343 | |
| 344 | ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT_MS); |
| 345 | if (ret) |
| 346 | goto out; |
| 347 | |
| 348 | offset += chunk_len; |
| 349 | } |
| 350 | |
| 351 | if (CONFIG(DEBUG_SPI_FLASH)) |
| 352 | printk(BIOS_SPEW, "SF: : Successfully programmed %zu bytes @ 0x%lx\n", |
| 353 | len, (unsigned long)(offset - len)); |
| 354 | ret = 0; |
| 355 | |
| 356 | out: |
| 357 | return ret; |
| 358 | } |
| 359 | |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 360 | static const struct spi_flash_vendor_info *spi_flash_vendors[] = { |
| 361 | #if CONFIG(SPI_FLASH_ADESTO) |
| 362 | &spi_flash_adesto_vi, |
| 363 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 364 | #if CONFIG(SPI_FLASH_AMIC) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 365 | &spi_flash_amic_vi, |
Idwer Vollering | 73a1018 | 2014-02-16 00:32:13 +0000 | [diff] [blame] | 366 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 367 | #if CONFIG(SPI_FLASH_ATMEL) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 368 | &spi_flash_atmel_vi, |
Kyösti Mälkki | 96d9276 | 2014-11-11 15:04:38 +0200 | [diff] [blame] | 369 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 370 | #if CONFIG(SPI_FLASH_EON) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 371 | &spi_flash_eon_vi, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 372 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 373 | #if CONFIG(SPI_FLASH_GIGADEVICE) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 374 | &spi_flash_gigadevice_vi, |
Martin Roth | bceaf7f | 2012-09-07 15:02:35 -0600 | [diff] [blame] | 375 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 376 | #if CONFIG(SPI_FLASH_MACRONIX) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 377 | &spi_flash_macronix_vi, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 378 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 379 | #if CONFIG(SPI_FLASH_SPANSION) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 380 | &spi_flash_spansion_ext1_vi, |
| 381 | &spi_flash_spansion_ext2_vi, |
| 382 | &spi_flash_spansion_vi, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 383 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 384 | #if CONFIG(SPI_FLASH_SST) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 385 | &spi_flash_sst_ai_vi, |
| 386 | &spi_flash_sst_vi, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 387 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 388 | #if CONFIG(SPI_FLASH_STMICRO) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 389 | &spi_flash_stmicro1_vi, |
| 390 | &spi_flash_stmicro2_vi, |
| 391 | &spi_flash_stmicro3_vi, |
| 392 | &spi_flash_stmicro4_vi, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 393 | #endif |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 394 | #if CONFIG(SPI_FLASH_WINBOND) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 395 | &spi_flash_winbond_vi, |
Chris Douglass | b34739b | 2014-02-14 13:51:26 -0500 | [diff] [blame] | 396 | #endif |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 397 | }; |
Aaron Durbin | ae43f32 | 2020-01-11 09:59:32 -0700 | [diff] [blame] | 398 | #define IDCODE_LEN 5 |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 399 | |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 400 | static int fill_spi_flash(const struct spi_slave *spi, struct spi_flash *flash, |
| 401 | const struct spi_flash_vendor_info *vi, |
| 402 | const struct spi_flash_part_id *part) |
| 403 | { |
| 404 | memcpy(&flash->spi, spi, sizeof(*spi)); |
| 405 | flash->vendor = vi->id; |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 406 | flash->model = part->id[0]; |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 407 | |
| 408 | flash->page_size = 1U << vi->page_size_shift; |
| 409 | flash->sector_size = (1U << vi->sector_size_kib_shift) * KiB; |
| 410 | flash->size = flash->sector_size * (1U << part->nr_sectors_shift); |
| 411 | flash->erase_cmd = vi->desc->erase_cmd; |
| 412 | flash->status_cmd = vi->desc->status_cmd; |
| 413 | flash->pp_cmd = vi->desc->pp_cmd; |
| 414 | flash->wren_cmd = vi->desc->wren_cmd; |
| 415 | |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 416 | flash->flags.dual_output = part->fast_read_dual_output_support; |
| 417 | flash->flags.dual_io = part->fast_read_dual_io_support; |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 418 | |
| 419 | flash->ops = &vi->desc->ops; |
| 420 | flash->prot_ops = vi->prot_ops; |
| 421 | flash->part = part; |
| 422 | |
| 423 | if (vi->after_probe) |
| 424 | return vi->after_probe(flash); |
| 425 | |
| 426 | return 0; |
| 427 | } |
| 428 | |
| 429 | static const struct spi_flash_part_id *find_part(const struct spi_flash_vendor_info *vi, |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 430 | uint16_t id[2]) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 431 | { |
| 432 | size_t i; |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 433 | const uint16_t lid[2] = { |
| 434 | [0] = id[0] & vi->match_id_mask[0], |
| 435 | [1] = id[1] & vi->match_id_mask[1], |
| 436 | }; |
| 437 | |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 438 | for (i = 0; i < vi->nr_part_ids; i++) { |
| 439 | const struct spi_flash_part_id *part = &vi->ids[i]; |
| 440 | |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 441 | if (part->id[0] == lid[0] && part->id[1] == lid[1]) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 442 | return part; |
| 443 | } |
| 444 | |
| 445 | return NULL; |
| 446 | } |
| 447 | |
| 448 | static int find_match(const struct spi_slave *spi, struct spi_flash *flash, |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 449 | uint8_t manuf_id, uint16_t id[2]) |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 450 | { |
| 451 | int i; |
| 452 | |
| 453 | for (i = 0; i < (int)ARRAY_SIZE(spi_flash_vendors); i++) { |
| 454 | const struct spi_flash_vendor_info *vi; |
| 455 | const struct spi_flash_part_id *part; |
| 456 | |
| 457 | vi = spi_flash_vendors[i]; |
| 458 | |
| 459 | if (manuf_id != vi->id) |
| 460 | continue; |
| 461 | |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 462 | part = find_part(vi, id); |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 463 | |
| 464 | if (part == NULL) |
| 465 | continue; |
| 466 | |
| 467 | return fill_spi_flash(spi, flash, vi, part); |
| 468 | } |
| 469 | |
Fred Reitberger | 5f5c721 | 2023-06-01 15:59:43 -0400 | [diff] [blame^] | 470 | printk(BIOS_WARNING, "SF: no match for ID %04x %04x\n", id[0], id[1]); |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 471 | return -1; |
| 472 | } |
| 473 | |
Furquan Shaikh | a149157 | 2017-05-17 19:14:06 -0700 | [diff] [blame] | 474 | int spi_flash_generic_probe(const struct spi_slave *spi, |
Furquan Shaikh | bd9e32e | 2017-05-15 23:28:41 -0700 | [diff] [blame] | 475 | struct spi_flash *flash) |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 476 | { |
Aaron Durbin | ae43f32 | 2020-01-11 09:59:32 -0700 | [diff] [blame] | 477 | int ret, i; |
| 478 | u8 idcode[IDCODE_LEN]; |
| 479 | u8 manuf_id; |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 480 | u16 id[2]; |
Vladimir Serbinenko | e23bd0e | 2014-01-18 17:45:32 +0100 | [diff] [blame] | 481 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 482 | /* Read the ID codes */ |
| 483 | ret = spi_flash_cmd(spi, CMD_READ_ID, idcode, sizeof(idcode)); |
| 484 | if (ret) |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 485 | return -1; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 486 | |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 487 | if (CONFIG(DEBUG_SPI_FLASH)) { |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 488 | printk(BIOS_SPEW, "SF: Got idcode: "); |
| 489 | for (i = 0; i < sizeof(idcode); i++) |
| 490 | printk(BIOS_SPEW, "%02x ", idcode[i]); |
| 491 | printk(BIOS_SPEW, "\n"); |
| 492 | } |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 493 | |
Aaron Durbin | ae43f32 | 2020-01-11 09:59:32 -0700 | [diff] [blame] | 494 | manuf_id = idcode[0]; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 495 | |
Aaron Durbin | ae43f32 | 2020-01-11 09:59:32 -0700 | [diff] [blame] | 496 | printk(BIOS_INFO, "Manufacturer: %02x\n", manuf_id); |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 497 | |
Aaron Durbin | 1c3086a | 2020-01-03 15:58:11 -0700 | [diff] [blame] | 498 | /* If no result from RDID command and STMicro parts are enabled attempt |
| 499 | to wake the part from deep sleep and obtain alternative id info. */ |
Aaron Durbin | ae43f32 | 2020-01-11 09:59:32 -0700 | [diff] [blame] | 500 | if (CONFIG(SPI_FLASH_STMICRO) && manuf_id == 0xff) { |
Aaron Durbin | 1c3086a | 2020-01-03 15:58:11 -0700 | [diff] [blame] | 501 | if (stmicro_release_deep_sleep_identify(spi, idcode)) |
| 502 | return -1; |
Aaron Durbin | ae43f32 | 2020-01-11 09:59:32 -0700 | [diff] [blame] | 503 | manuf_id = idcode[0]; |
Aaron Durbin | 1c3086a | 2020-01-03 15:58:11 -0700 | [diff] [blame] | 504 | } |
| 505 | |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 506 | id[0] = (idcode[1] << 8) | idcode[2]; |
| 507 | id[1] = (idcode[3] << 8) | idcode[4]; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 508 | |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 509 | return find_match(spi, flash, manuf_id, id); |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 510 | } |
| 511 | |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 512 | int spi_flash_probe(unsigned int bus, unsigned int cs, struct spi_flash *flash) |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 513 | { |
Furquan Shaikh | 36b81af | 2016-12-01 01:02:44 -0800 | [diff] [blame] | 514 | struct spi_slave spi; |
Furquan Shaikh | a149157 | 2017-05-17 19:14:06 -0700 | [diff] [blame] | 515 | int ret = -1; |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 516 | |
Furquan Shaikh | 36b81af | 2016-12-01 01:02:44 -0800 | [diff] [blame] | 517 | if (spi_setup_slave(bus, cs, &spi)) { |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 518 | printk(BIOS_WARNING, "SF: Failed to set up slave\n"); |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 519 | return -1; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 520 | } |
| 521 | |
Furquan Shaikh | a149157 | 2017-05-17 19:14:06 -0700 | [diff] [blame] | 522 | /* Try special programmer probe if any. */ |
| 523 | if (spi.ctrlr->flash_probe) |
| 524 | ret = spi.ctrlr->flash_probe(&spi, flash); |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 525 | |
| 526 | /* If flash is not found, try generic spi flash probe. */ |
Furquan Shaikh | a149157 | 2017-05-17 19:14:06 -0700 | [diff] [blame] | 527 | if (ret) |
| 528 | ret = spi_flash_generic_probe(&spi, flash); |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 529 | |
| 530 | /* Give up -- nothing more to try if flash is not found. */ |
Furquan Shaikh | a149157 | 2017-05-17 19:14:06 -0700 | [diff] [blame] | 531 | if (ret) { |
| 532 | printk(BIOS_WARNING, "SF: Unsupported manufacturer!\n"); |
| 533 | return -1; |
| 534 | } |
Furquan Shaikh | d2fb6ae | 2016-11-17 20:38:07 -0800 | [diff] [blame] | 535 | |
Julius Werner | 99e45ce | 2019-06-06 17:03:44 -0700 | [diff] [blame] | 536 | const char *mode_string = ""; |
Julius Werner | df50622 | 2021-07-13 15:57:29 -0700 | [diff] [blame] | 537 | if (flash->flags.dual_io && spi.ctrlr->xfer_dual) |
| 538 | mode_string = " (Dual I/O mode)"; |
| 539 | else if (flash->flags.dual_output && spi.ctrlr->xfer_dual) |
| 540 | mode_string = " (Dual Output mode)"; |
Julius Werner | 99e45ce | 2019-06-06 17:03:44 -0700 | [diff] [blame] | 541 | printk(BIOS_INFO, |
Aaron Durbin | fc7b953 | 2020-01-23 11:45:30 -0700 | [diff] [blame] | 542 | "SF: Detected %02x %04x with sector size 0x%x, total 0x%x%s\n", |
| 543 | flash->vendor, flash->model, flash->sector_size, flash->size, mode_string); |
Arthur Heymans | 950332b | 2018-02-11 19:41:05 +0100 | [diff] [blame] | 544 | if (bus == CONFIG_BOOT_DEVICE_SPI_FLASH_BUS |
| 545 | && flash->size != CONFIG_ROM_SIZE) { |
| 546 | printk(BIOS_ERR, "SF size 0x%x does not correspond to" |
| 547 | " CONFIG_ROM_SIZE 0x%x!!\n", flash->size, |
| 548 | CONFIG_ROM_SIZE); |
| 549 | } |
T Michael Turney | d43e688 | 2022-01-20 11:55:40 -0800 | [diff] [blame] | 550 | |
| 551 | if (CONFIG(SPI_FLASH_EXIT_4_BYTE_ADDR_MODE) && ENV_INITIAL_STAGE) |
| 552 | spi_flash_cmd(&flash->spi, CMD_EXIT_4BYTE_ADDR_MODE, NULL, 0); |
| 553 | |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 554 | return 0; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 555 | } |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 556 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 557 | int spi_flash_read(const struct spi_flash *flash, u32 offset, size_t len, |
| 558 | void *buf) |
| 559 | { |
Aaron Durbin | cb01aa5 | 2020-01-11 11:44:47 -0700 | [diff] [blame] | 560 | return flash->ops->read(flash, offset, len, buf); |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 561 | } |
| 562 | |
| 563 | int spi_flash_write(const struct spi_flash *flash, u32 offset, size_t len, |
| 564 | const void *buf) |
| 565 | { |
| 566 | int ret; |
| 567 | |
| 568 | if (spi_flash_volatile_group_begin(flash)) |
| 569 | return -1; |
| 570 | |
Furquan Shaikh | e2fc5e2 | 2017-05-17 17:26:01 -0700 | [diff] [blame] | 571 | ret = flash->ops->write(flash, offset, len, buf); |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 572 | |
| 573 | if (spi_flash_volatile_group_end(flash)) |
| 574 | return -1; |
| 575 | |
| 576 | return ret; |
| 577 | } |
| 578 | |
| 579 | int spi_flash_erase(const struct spi_flash *flash, u32 offset, size_t len) |
| 580 | { |
| 581 | int ret; |
| 582 | |
| 583 | if (spi_flash_volatile_group_begin(flash)) |
| 584 | return -1; |
| 585 | |
Furquan Shaikh | e2fc5e2 | 2017-05-17 17:26:01 -0700 | [diff] [blame] | 586 | ret = flash->ops->erase(flash, offset, len); |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 587 | |
| 588 | if (spi_flash_volatile_group_end(flash)) |
| 589 | return -1; |
| 590 | |
| 591 | return ret; |
| 592 | } |
| 593 | |
| 594 | int spi_flash_status(const struct spi_flash *flash, u8 *reg) |
| 595 | { |
Furquan Shaikh | e2fc5e2 | 2017-05-17 17:26:01 -0700 | [diff] [blame] | 596 | if (flash->ops->status) |
| 597 | return flash->ops->status(flash, reg); |
| 598 | |
| 599 | return -1; |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 600 | } |
| 601 | |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 602 | int spi_flash_is_write_protected(const struct spi_flash *flash, |
| 603 | const struct region *region) |
| 604 | { |
| 605 | struct region flash_region = { 0 }; |
| 606 | |
| 607 | if (!flash || !region) |
| 608 | return -1; |
| 609 | |
| 610 | flash_region.size = flash->size; |
| 611 | |
| 612 | if (!region_is_subregion(&flash_region, region)) |
| 613 | return -1; |
| 614 | |
Aaron Durbin | f584f19 | 2020-01-11 14:03:27 -0700 | [diff] [blame] | 615 | if (!flash->prot_ops) { |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 616 | printk(BIOS_WARNING, "SPI: Write-protection gathering not " |
| 617 | "implemented for this vendor.\n"); |
Patrick Rudolph | b66907a | 2018-11-07 15:23:40 +0100 | [diff] [blame] | 618 | return -1; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 619 | } |
| 620 | |
Aaron Durbin | f584f19 | 2020-01-11 14:03:27 -0700 | [diff] [blame] | 621 | return flash->prot_ops->get_write(flash, region); |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 622 | } |
| 623 | |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 624 | int spi_flash_set_write_protected(const struct spi_flash *flash, |
| 625 | const struct region *region, |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 626 | const enum spi_flash_status_reg_lockdown mode) |
| 627 | { |
| 628 | struct region flash_region = { 0 }; |
| 629 | int ret; |
| 630 | |
| 631 | if (!flash) |
| 632 | return -1; |
| 633 | |
| 634 | flash_region.size = flash->size; |
| 635 | |
| 636 | if (!region_is_subregion(&flash_region, region)) |
| 637 | return -1; |
| 638 | |
Aaron Durbin | f584f19 | 2020-01-11 14:03:27 -0700 | [diff] [blame] | 639 | if (!flash->prot_ops) { |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 640 | printk(BIOS_WARNING, "SPI: Setting write-protection is not " |
| 641 | "implemented for this vendor.\n"); |
Patrick Rudolph | b66907a | 2018-11-07 15:23:40 +0100 | [diff] [blame] | 642 | return -1; |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 643 | } |
| 644 | |
Daniel Gröber | 0d0b2f4 | 2020-05-26 22:12:14 +0200 | [diff] [blame] | 645 | ret = flash->prot_ops->set_write(flash, region, mode); |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 646 | |
| 647 | if (ret == 0 && mode != SPI_WRITE_PROTECTION_PRESERVE) { |
| 648 | printk(BIOS_INFO, "SPI: SREG lock-down was set to "); |
| 649 | switch (mode) { |
| 650 | case SPI_WRITE_PROTECTION_NONE: |
| 651 | printk(BIOS_INFO, "NEVER\n"); |
| 652 | break; |
| 653 | case SPI_WRITE_PROTECTION_PIN: |
| 654 | printk(BIOS_INFO, "WP\n"); |
| 655 | break; |
| 656 | case SPI_WRITE_PROTECTION_REBOOT: |
| 657 | printk(BIOS_INFO, "REBOOT\n"); |
| 658 | break; |
| 659 | case SPI_WRITE_PROTECTION_PERMANENT: |
| 660 | printk(BIOS_INFO, "PERMANENT\n"); |
| 661 | break; |
| 662 | default: |
| 663 | printk(BIOS_INFO, "UNKNOWN\n"); |
| 664 | break; |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | return ret; |
| 669 | } |
| 670 | |
Arthur Heymans | c2a9c42 | 2019-11-20 21:33:31 +0100 | [diff] [blame] | 671 | static uint32_t volatile_group_count; |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 672 | |
| 673 | int spi_flash_volatile_group_begin(const struct spi_flash *flash) |
| 674 | { |
| 675 | uint32_t count; |
| 676 | int ret = 0; |
| 677 | |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 678 | if (!CONFIG(SPI_FLASH_HAS_VOLATILE_GROUP)) |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 679 | return ret; |
| 680 | |
Arthur Heymans | c2a9c42 | 2019-11-20 21:33:31 +0100 | [diff] [blame] | 681 | count = volatile_group_count; |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 682 | if (count == 0) |
| 683 | ret = chipset_volatile_group_begin(flash); |
| 684 | |
| 685 | count++; |
Arthur Heymans | c2a9c42 | 2019-11-20 21:33:31 +0100 | [diff] [blame] | 686 | volatile_group_count = count; |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 687 | return ret; |
| 688 | } |
| 689 | |
| 690 | int spi_flash_volatile_group_end(const struct spi_flash *flash) |
| 691 | { |
| 692 | uint32_t count; |
| 693 | int ret = 0; |
| 694 | |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 695 | if (!CONFIG(SPI_FLASH_HAS_VOLATILE_GROUP)) |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 696 | return ret; |
| 697 | |
Arthur Heymans | c2a9c42 | 2019-11-20 21:33:31 +0100 | [diff] [blame] | 698 | count = volatile_group_count; |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 699 | assert(count == 0); |
| 700 | count--; |
Arthur Heymans | c2a9c42 | 2019-11-20 21:33:31 +0100 | [diff] [blame] | 701 | volatile_group_count = count; |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 702 | |
| 703 | if (count == 0) |
| 704 | ret = chipset_volatile_group_end(flash); |
| 705 | |
| 706 | return ret; |
| 707 | } |
| 708 | |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 709 | void lb_spi_flash(struct lb_header *header) |
| 710 | { |
| 711 | struct lb_spi_flash *flash; |
Furquan Shaikh | f422fd2 | 2017-05-07 13:05:39 -0700 | [diff] [blame] | 712 | const struct spi_flash *spi_flash_dev; |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 713 | |
Julius Werner | cd49cce | 2019-03-05 16:53:33 -0800 | [diff] [blame] | 714 | if (!CONFIG(BOOT_DEVICE_SPI_FLASH)) |
Aaron Durbin | c0a823c | 2016-08-11 14:51:38 -0500 | [diff] [blame] | 715 | return; |
| 716 | |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 717 | flash = (struct lb_spi_flash *)lb_new_record(header); |
| 718 | |
| 719 | flash->tag = LB_TAG_SPI_FLASH; |
| 720 | flash->size = sizeof(*flash); |
| 721 | |
Furquan Shaikh | f422fd2 | 2017-05-07 13:05:39 -0700 | [diff] [blame] | 722 | spi_flash_dev = boot_device_spi_flash(); |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 723 | |
| 724 | if (spi_flash_dev) { |
| 725 | flash->flash_size = spi_flash_dev->size; |
| 726 | flash->sector_size = spi_flash_dev->sector_size; |
| 727 | flash->erase_cmd = spi_flash_dev->erase_cmd; |
| 728 | } else { |
| 729 | flash->flash_size = CONFIG_ROM_SIZE; |
| 730 | /* Default 64k erase command should work on most flash. |
| 731 | * Uniform 4k erase only works on certain devices. */ |
| 732 | flash->sector_size = 64 * KiB; |
| 733 | flash->erase_cmd = CMD_BLOCK_ERASE; |
| 734 | } |
Furquan Shaikh | 493937e | 2020-11-25 17:15:09 -0800 | [diff] [blame] | 735 | |
| 736 | if (!CONFIG(BOOT_DEVICE_MEMORY_MAPPED)) { |
| 737 | flash->mmap_count = 0; |
| 738 | } else { |
| 739 | struct flash_mmap_window *table = (struct flash_mmap_window *)(flash + 1); |
| 740 | flash->mmap_count = spi_flash_get_mmap_windows(table); |
| 741 | flash->size += flash->mmap_count * sizeof(*table); |
| 742 | } |
Dan Ehrenberg | a5aac76 | 2015-01-08 10:29:19 -0800 | [diff] [blame] | 743 | } |
Aaron Durbin | 10d65b0 | 2017-12-14 14:34:47 -0700 | [diff] [blame] | 744 | |
Aaron Durbin | 10d65b0 | 2017-12-14 14:34:47 -0700 | [diff] [blame] | 745 | int spi_flash_ctrlr_protect_region(const struct spi_flash *flash, |
Rizwan Qureshi | f9f5093 | 2018-12-31 15:19:16 +0530 | [diff] [blame] | 746 | const struct region *region, |
| 747 | const enum ctrlr_prot_type type) |
Aaron Durbin | 10d65b0 | 2017-12-14 14:34:47 -0700 | [diff] [blame] | 748 | { |
| 749 | const struct spi_ctrlr *ctrlr; |
| 750 | struct region flash_region = { 0 }; |
| 751 | |
| 752 | if (!flash) |
| 753 | return -1; |
| 754 | |
| 755 | flash_region.size = flash->size; |
| 756 | |
| 757 | if (!region_is_subregion(&flash_region, region)) |
| 758 | return -1; |
| 759 | |
| 760 | ctrlr = flash->spi.ctrlr; |
| 761 | |
| 762 | if (!ctrlr) |
| 763 | return -1; |
| 764 | |
| 765 | if (ctrlr->flash_protect) |
Rizwan Qureshi | f9f5093 | 2018-12-31 15:19:16 +0530 | [diff] [blame] | 766 | return ctrlr->flash_protect(flash, region, type); |
Aaron Durbin | 10d65b0 | 2017-12-14 14:34:47 -0700 | [diff] [blame] | 767 | |
| 768 | return -1; |
| 769 | } |
Aaron Durbin | 851dde8 | 2018-04-19 21:15:25 -0600 | [diff] [blame] | 770 | |
| 771 | int spi_flash_vector_helper(const struct spi_slave *slave, |
| 772 | struct spi_op vectors[], size_t count, |
| 773 | int (*func)(const struct spi_slave *slave, const void *dout, |
| 774 | size_t bytesout, void *din, size_t bytesin)) |
| 775 | { |
| 776 | int ret; |
| 777 | void *din; |
| 778 | size_t bytes_in; |
| 779 | |
| 780 | if (count < 1 || count > 2) |
| 781 | return -1; |
| 782 | |
| 783 | /* SPI flash commands always have a command first... */ |
| 784 | if (!vectors[0].dout || !vectors[0].bytesout) |
| 785 | return -1; |
| 786 | /* And not read any data during the command. */ |
| 787 | if (vectors[0].din || vectors[0].bytesin) |
| 788 | return -1; |
| 789 | |
| 790 | if (count == 2) { |
| 791 | /* If response bytes requested ensure the buffer is valid. */ |
| 792 | if (vectors[1].bytesin && !vectors[1].din) |
| 793 | return -1; |
| 794 | /* No sends can accompany a receive. */ |
| 795 | if (vectors[1].dout || vectors[1].bytesout) |
| 796 | return -1; |
| 797 | din = vectors[1].din; |
| 798 | bytes_in = vectors[1].bytesin; |
| 799 | } else { |
| 800 | din = NULL; |
| 801 | bytes_in = 0; |
| 802 | } |
| 803 | |
| 804 | ret = func(slave, vectors[0].dout, vectors[0].bytesout, din, bytes_in); |
| 805 | |
| 806 | if (ret) { |
| 807 | vectors[0].status = SPI_OP_FAILURE; |
| 808 | if (count == 2) |
| 809 | vectors[1].status = SPI_OP_FAILURE; |
| 810 | } else { |
| 811 | vectors[0].status = SPI_OP_SUCCESS; |
| 812 | if (count == 2) |
| 813 | vectors[1].status = SPI_OP_SUCCESS; |
| 814 | } |
| 815 | |
| 816 | return ret; |
| 817 | } |
Aaron Durbin | 5abeb06 | 2020-01-12 15:12:18 -0700 | [diff] [blame] | 818 | |
| 819 | const struct spi_flash_ops_descriptor spi_flash_pp_0x20_sector_desc = { |
| 820 | .erase_cmd = 0x20, /* Sector Erase */ |
| 821 | .status_cmd = 0x05, /* Read Status */ |
| 822 | .pp_cmd = 0x02, /* Page Program */ |
| 823 | .wren_cmd = 0x06, /* Write Enable */ |
| 824 | .ops = { |
| 825 | .read = spi_flash_cmd_read, |
| 826 | .write = spi_flash_cmd_write_page_program, |
| 827 | .erase = spi_flash_cmd_erase, |
| 828 | .status = spi_flash_cmd_status, |
| 829 | }, |
| 830 | }; |
| 831 | |
| 832 | const struct spi_flash_ops_descriptor spi_flash_pp_0xd8_sector_desc = { |
| 833 | .erase_cmd = 0xd8, /* Sector Erase */ |
| 834 | .status_cmd = 0x05, /* Read Status */ |
| 835 | .pp_cmd = 0x02, /* Page Program */ |
| 836 | .wren_cmd = 0x06, /* Write Enable */ |
| 837 | .ops = { |
| 838 | .read = spi_flash_cmd_read, |
| 839 | .write = spi_flash_cmd_write_page_program, |
| 840 | .erase = spi_flash_cmd_erase, |
| 841 | .status = spi_flash_cmd_status, |
| 842 | }, |
| 843 | }; |