Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2008, Network Appliance Inc. |
| 3 | * Author: Jason McMullan <mcmullan <at> netapp.com> |
| 4 | * Licensed under the GPL-2 or later. |
| 5 | */ |
| 6 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 7 | #include <console/console.h> |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 8 | #include <stdlib.h> |
| 9 | #include <spi_flash.h> |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 10 | #include <spi-generic.h> |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 11 | #include <string.h> |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 12 | #include <assert.h> |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 13 | #include <delay.h> |
| 14 | #include <lib.h> |
Edward O'Callaghan | c4561e2 | 2014-06-26 15:02:40 +1000 | [diff] [blame] | 15 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 16 | #include "spi_flash_internal.h" |
| 17 | |
| 18 | /* M25Pxx-specific commands */ |
| 19 | #define CMD_W25_WREN 0x06 /* Write Enable */ |
| 20 | #define CMD_W25_WRDI 0x04 /* Write Disable */ |
| 21 | #define CMD_W25_RDSR 0x05 /* Read Status Register */ |
| 22 | #define CMD_W25_WRSR 0x01 /* Write Status Register */ |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 23 | #define CMD_W25_RDSR2 0x35 /* Read Status2 Register */ |
| 24 | #define CMD_W25_WRSR2 0x31 /* Write Status2 Register */ |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 25 | #define CMD_W25_READ 0x03 /* Read Data Bytes */ |
| 26 | #define CMD_W25_FAST_READ 0x0b /* Read Data Bytes at Higher Speed */ |
| 27 | #define CMD_W25_PP 0x02 /* Page Program */ |
| 28 | #define CMD_W25_SE 0x20 /* Sector (4K) Erase */ |
| 29 | #define CMD_W25_BE 0xd8 /* Block (64K) Erase */ |
| 30 | #define CMD_W25_CE 0xc7 /* Chip Erase */ |
| 31 | #define CMD_W25_DP 0xb9 /* Deep Power-down */ |
| 32 | #define CMD_W25_RES 0xab /* Release from DP, and Read Signature */ |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 33 | #define CMD_VOLATILE_SREG_WREN 0x50 /* Write Enable for Volatile SREG */ |
| 34 | |
| 35 | /* tw: Maximum time to write a flash cell in milliseconds */ |
| 36 | #define WINBOND_FLASH_TIMEOUT 30 |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 37 | |
| 38 | struct winbond_spi_flash_params { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 39 | uint16_t id; |
| 40 | uint8_t l2_page_size_shift; |
| 41 | uint8_t pages_per_sector_shift : 4; |
| 42 | uint8_t sectors_per_block_shift : 4; |
| 43 | uint8_t nr_blocks_shift; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 44 | uint8_t bp_bits : 3; |
| 45 | uint8_t protection_granularity_shift : 5; |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 46 | char name[10]; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 47 | }; |
| 48 | |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 49 | union status_reg1_bp3 { |
| 50 | uint8_t u; |
| 51 | struct { |
| 52 | uint8_t busy : 1; |
| 53 | uint8_t wel : 1; |
| 54 | uint8_t bp : 3; |
| 55 | uint8_t tb : 1; |
| 56 | uint8_t sec : 1; |
| 57 | uint8_t srp0 : 1; |
| 58 | }; |
| 59 | }; |
| 60 | |
| 61 | union status_reg1_bp4 { |
| 62 | uint8_t u; |
| 63 | struct { |
| 64 | uint8_t busy : 1; |
| 65 | uint8_t wel : 1; |
| 66 | uint8_t bp : 4; |
| 67 | uint8_t tb : 1; |
| 68 | uint8_t srp0 : 1; |
| 69 | }; |
| 70 | }; |
| 71 | |
| 72 | union status_reg2 { |
| 73 | uint8_t u; |
| 74 | struct { |
| 75 | uint8_t srp1 : 1; |
| 76 | uint8_t qe : 1; |
| 77 | uint8_t res : 1; |
| 78 | uint8_t lb : 3; |
| 79 | uint8_t cmp : 1; |
| 80 | uint8_t sus : 1; |
| 81 | }; |
| 82 | }; |
| 83 | |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 84 | struct status_regs { |
| 85 | union { |
| 86 | struct { |
| 87 | #if defined(__BIG_ENDIAN) |
| 88 | union status_reg2 reg2; |
| 89 | union { |
| 90 | union status_reg1_bp3 reg1_bp3; |
| 91 | union status_reg1_bp4 reg1_bp4; |
| 92 | }; |
| 93 | #else |
| 94 | union { |
| 95 | union status_reg1_bp3 reg1_bp3; |
| 96 | union status_reg1_bp4 reg1_bp4; |
| 97 | }; |
| 98 | union status_reg2 reg2; |
| 99 | #endif |
| 100 | }; |
| 101 | u16 u; |
| 102 | }; |
| 103 | }; |
| 104 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 105 | static const struct winbond_spi_flash_params winbond_spi_flash_table[] = { |
| 106 | { |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 107 | .id = 0x2014, |
| 108 | .l2_page_size_shift = 8, |
| 109 | .pages_per_sector_shift = 4, |
| 110 | .sectors_per_block_shift = 4, |
| 111 | .nr_blocks_shift = 4, |
| 112 | .name = "W25P80", |
| 113 | }, |
| 114 | { |
| 115 | .id = 0x2015, |
| 116 | .l2_page_size_shift = 8, |
| 117 | .pages_per_sector_shift = 4, |
| 118 | .sectors_per_block_shift = 4, |
| 119 | .nr_blocks_shift = 5, |
| 120 | .name = "W25P16", |
| 121 | }, |
| 122 | { |
| 123 | .id = 0x2016, |
| 124 | .l2_page_size_shift = 8, |
| 125 | .pages_per_sector_shift = 4, |
| 126 | .sectors_per_block_shift = 4, |
| 127 | .nr_blocks_shift = 6, |
| 128 | .name = "W25P32", |
| 129 | }, |
| 130 | { |
| 131 | .id = 0x3014, |
| 132 | .l2_page_size_shift = 8, |
| 133 | .pages_per_sector_shift = 4, |
| 134 | .sectors_per_block_shift = 4, |
| 135 | .nr_blocks_shift = 4, |
| 136 | .name = "W25X80", |
| 137 | }, |
| 138 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 139 | .id = 0x3015, |
| 140 | .l2_page_size_shift = 8, |
| 141 | .pages_per_sector_shift = 4, |
| 142 | .sectors_per_block_shift = 4, |
| 143 | .nr_blocks_shift = 5, |
| 144 | .name = "W25X16", |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 145 | }, |
| 146 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 147 | .id = 0x3016, |
| 148 | .l2_page_size_shift = 8, |
| 149 | .pages_per_sector_shift = 4, |
| 150 | .sectors_per_block_shift = 4, |
| 151 | .nr_blocks_shift = 6, |
| 152 | .name = "W25X32", |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 153 | }, |
| 154 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 155 | .id = 0x3017, |
| 156 | .l2_page_size_shift = 8, |
| 157 | .pages_per_sector_shift = 4, |
| 158 | .sectors_per_block_shift = 4, |
| 159 | .nr_blocks_shift = 7, |
| 160 | .name = "W25X64", |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 161 | }, |
| 162 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 163 | .id = 0x4014, |
| 164 | .l2_page_size_shift = 8, |
| 165 | .pages_per_sector_shift = 4, |
| 166 | .sectors_per_block_shift = 4, |
| 167 | .nr_blocks_shift = 4, |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 168 | .name = "W25Q80_V", |
Kyösti Mälkki | 76f7b79 | 2018-06-14 22:25:58 +0300 | [diff] [blame] | 169 | }, |
| 170 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 171 | .id = 0x4015, |
| 172 | .l2_page_size_shift = 8, |
| 173 | .pages_per_sector_shift = 4, |
| 174 | .sectors_per_block_shift = 4, |
| 175 | .nr_blocks_shift = 5, |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 176 | .name = "W25Q16_V", |
| 177 | .protection_granularity_shift = 16, |
| 178 | .bp_bits = 3, |
| 179 | }, |
| 180 | { |
| 181 | .id = 0x6015, |
| 182 | .l2_page_size_shift = 8, |
| 183 | .pages_per_sector_shift = 4, |
| 184 | .sectors_per_block_shift = 4, |
| 185 | .nr_blocks_shift = 5, |
| 186 | .name = "W25Q16DW", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 187 | .protection_granularity_shift = 16, |
| 188 | .bp_bits = 3, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 189 | }, |
| 190 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 191 | .id = 0x4016, |
| 192 | .l2_page_size_shift = 8, |
| 193 | .pages_per_sector_shift = 4, |
| 194 | .sectors_per_block_shift = 4, |
| 195 | .nr_blocks_shift = 6, |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 196 | .name = "W25Q32_V", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 197 | .protection_granularity_shift = 16, |
| 198 | .bp_bits = 3, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 199 | }, |
| 200 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 201 | .id = 0x6016, |
| 202 | .l2_page_size_shift = 8, |
| 203 | .pages_per_sector_shift = 4, |
| 204 | .sectors_per_block_shift = 4, |
| 205 | .nr_blocks_shift = 6, |
| 206 | .name = "W25Q32DW", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 207 | .protection_granularity_shift = 16, |
| 208 | .bp_bits = 3, |
David Hendricks | 43e9252 | 2014-03-21 19:45:02 -0700 | [diff] [blame] | 209 | }, |
| 210 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 211 | .id = 0x4017, |
| 212 | .l2_page_size_shift = 8, |
| 213 | .pages_per_sector_shift = 4, |
| 214 | .sectors_per_block_shift = 4, |
| 215 | .nr_blocks_shift = 7, |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 216 | .name = "W25Q64_V", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 217 | .protection_granularity_shift = 17, |
| 218 | .bp_bits = 3, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 219 | }, |
| 220 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 221 | .id = 0x6017, |
| 222 | .l2_page_size_shift = 8, |
| 223 | .pages_per_sector_shift = 4, |
| 224 | .sectors_per_block_shift = 4, |
| 225 | .nr_blocks_shift = 7, |
| 226 | .name = "W25Q64DW", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 227 | .protection_granularity_shift = 17, |
| 228 | .bp_bits = 3, |
Aaron Durbin | 16000c8 | 2013-09-19 12:19:51 -0500 | [diff] [blame] | 229 | }, |
| 230 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 231 | .id = 0x4018, |
| 232 | .l2_page_size_shift = 8, |
| 233 | .pages_per_sector_shift = 4, |
| 234 | .sectors_per_block_shift = 4, |
| 235 | .nr_blocks_shift = 8, |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 236 | .name = "W25Q128_V", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 237 | .protection_granularity_shift = 18, |
| 238 | .bp_bits = 3, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 239 | }, |
Mohan D'Costa | bdae9be | 2014-09-25 14:40:44 +0900 | [diff] [blame] | 240 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 241 | .id = 0x6018, |
| 242 | .l2_page_size_shift = 8, |
| 243 | .pages_per_sector_shift = 4, |
| 244 | .sectors_per_block_shift = 4, |
| 245 | .nr_blocks_shift = 8, |
| 246 | .name = "W25Q128FW", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 247 | .protection_granularity_shift = 18, |
| 248 | .bp_bits = 3, |
Mohan D'Costa | bdae9be | 2014-09-25 14:40:44 +0900 | [diff] [blame] | 249 | }, |
Varadarajan Narayanan | 934c683 | 2016-03-21 12:56:43 +0530 | [diff] [blame] | 250 | { |
Patrick Rudolph | fca506d | 2018-10-01 16:00:16 +0200 | [diff] [blame] | 251 | .id = 0x7018, |
| 252 | .l2_page_size_shift = 8, |
| 253 | .pages_per_sector_shift = 4, |
| 254 | .sectors_per_block_shift = 4, |
| 255 | .nr_blocks_shift = 8, |
| 256 | .name = "W25Q128J", |
| 257 | .protection_granularity_shift = 18, |
| 258 | .bp_bits = 3, |
| 259 | }, |
| 260 | { |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 261 | .id = 0x4019, |
| 262 | .l2_page_size_shift = 8, |
| 263 | .pages_per_sector_shift = 4, |
| 264 | .sectors_per_block_shift = 4, |
| 265 | .nr_blocks_shift = 9, |
Mike Banon | 72812fa | 2019-01-08 19:11:34 +0300 | [diff] [blame] | 266 | .name = "W25Q256_V", |
| 267 | .protection_granularity_shift = 16, |
| 268 | .bp_bits = 4, |
| 269 | }, |
| 270 | { |
| 271 | .id = 0x7019, |
| 272 | .l2_page_size_shift = 8, |
| 273 | .pages_per_sector_shift = 4, |
| 274 | .sectors_per_block_shift = 4, |
| 275 | .nr_blocks_shift = 9, |
| 276 | .name = "W25Q256J", |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 277 | .protection_granularity_shift = 16, |
| 278 | .bp_bits = 4, |
Varadarajan Narayanan | 934c683 | 2016-03-21 12:56:43 +0530 | [diff] [blame] | 279 | }, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 280 | }; |
| 281 | |
Furquan Shaikh | c28984d | 2016-11-20 21:04:00 -0800 | [diff] [blame] | 282 | static int winbond_write(const struct spi_flash *flash, u32 offset, size_t len, |
| 283 | const void *buf) |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 284 | { |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 285 | unsigned long byte_addr; |
| 286 | unsigned long page_size; |
| 287 | size_t chunk_len; |
| 288 | size_t actual; |
David Hendricks | 032c843 | 2014-04-11 19:48:55 -0700 | [diff] [blame] | 289 | int ret = 0; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 290 | u8 cmd[4]; |
| 291 | |
Furquan Shaikh | fc1a123 | 2017-05-12 00:19:56 -0700 | [diff] [blame] | 292 | page_size = flash->page_size; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 293 | |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 294 | for (actual = 0; actual < len; actual += chunk_len) { |
Aaron Durbin | 41f6690 | 2016-12-17 13:16:07 -0600 | [diff] [blame] | 295 | byte_addr = offset % page_size; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 296 | chunk_len = min(len - actual, page_size - byte_addr); |
Furquan Shaikh | de705fa | 2017-04-19 19:27:28 -0700 | [diff] [blame] | 297 | chunk_len = spi_crop_chunk(&flash->spi, sizeof(cmd), chunk_len); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 298 | |
| 299 | cmd[0] = CMD_W25_PP; |
| 300 | cmd[1] = (offset >> 16) & 0xff; |
| 301 | cmd[2] = (offset >> 8) & 0xff; |
| 302 | cmd[3] = offset & 0xff; |
Martin Roth | e7d0a37 | 2017-06-24 14:02:00 -0600 | [diff] [blame] | 303 | #if IS_ENABLED(CONFIG_DEBUG_SPI_FLASH) |
Stefan Reinauer | 8ea5a34 | 2012-05-14 13:52:32 -0700 | [diff] [blame] | 304 | printk(BIOS_SPEW, "PP: 0x%p => cmd = { 0x%02x 0x%02x%02x%02x }" |
| 305 | " chunk_len = %zu\n", buf + actual, |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 306 | cmd[0], cmd[1], cmd[2], cmd[3], chunk_len); |
Stefan Reinauer | 5649b08 | 2012-05-23 11:03:29 -0700 | [diff] [blame] | 307 | #endif |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 308 | |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 309 | ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0); |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 310 | if (ret < 0) { |
| 311 | printk(BIOS_WARNING, "SF: Enabling Write failed\n"); |
| 312 | goto out; |
| 313 | } |
| 314 | |
Furquan Shaikh | 810e2cd | 2016-12-05 20:32:24 -0800 | [diff] [blame] | 315 | ret = spi_flash_cmd_write(&flash->spi, cmd, sizeof(cmd), |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 316 | buf + actual, chunk_len); |
| 317 | if (ret < 0) { |
| 318 | printk(BIOS_WARNING, "SF: Winbond Page Program failed\n"); |
| 319 | goto out; |
| 320 | } |
| 321 | |
| 322 | ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT); |
| 323 | if (ret) |
| 324 | goto out; |
| 325 | |
| 326 | offset += chunk_len; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 327 | } |
| 328 | |
Martin Roth | e7d0a37 | 2017-06-24 14:02:00 -0600 | [diff] [blame] | 329 | #if IS_ENABLED(CONFIG_DEBUG_SPI_FLASH) |
Marc Jones | 747127d | 2012-12-03 22:16:29 -0700 | [diff] [blame] | 330 | printk(BIOS_SPEW, "SF: Winbond: Successfully programmed %zu bytes @" |
Stefan Reinauer | 8ea5a34 | 2012-05-14 13:52:32 -0700 | [diff] [blame] | 331 | " 0x%lx\n", len, (unsigned long)(offset - len)); |
Marc Jones | 747127d | 2012-12-03 22:16:29 -0700 | [diff] [blame] | 332 | #endif |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 333 | ret = 0; |
| 334 | |
| 335 | out: |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 336 | return ret; |
| 337 | } |
| 338 | |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 339 | /* |
| 340 | * Convert BPx, TB and CMP to a region. |
| 341 | * SEC (if available) must be zero. |
| 342 | */ |
| 343 | static void winbond_bpbits_to_region(const size_t granularity, |
| 344 | const u8 bp, |
| 345 | bool tb, |
| 346 | const bool cmp, |
| 347 | const size_t flash_size, |
| 348 | struct region *out) |
| 349 | { |
| 350 | size_t protected_size = |
| 351 | min(bp ? granularity << (bp - 1) : 0, flash_size); |
| 352 | |
| 353 | if (cmp) { |
| 354 | protected_size = flash_size - protected_size; |
| 355 | tb = !tb; |
| 356 | } |
| 357 | |
Patrick Rudolph | e736015 | 2018-12-03 09:41:06 +0100 | [diff] [blame] | 358 | out->offset = tb ? 0 : flash_size - protected_size; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 359 | out->size = protected_size; |
| 360 | } |
| 361 | |
| 362 | /* |
| 363 | * Available on all devices. |
| 364 | * Read block protect bits from Status/Status2 Reg. |
| 365 | * Converts block protection bits to a region. |
| 366 | * |
| 367 | * Returns: |
| 368 | * -1 on error |
| 369 | * 1 if region is covered by write protection |
| 370 | * 0 if a part of region isn't covered by write protection |
| 371 | */ |
| 372 | static int winbond_get_write_protection(const struct spi_flash *flash, |
| 373 | const struct region *region) |
| 374 | { |
| 375 | const struct winbond_spi_flash_params *params; |
| 376 | struct region wp_region; |
| 377 | union status_reg2 reg2; |
| 378 | u8 bp, tb; |
| 379 | int ret; |
| 380 | |
| 381 | params = (const struct winbond_spi_flash_params *)flash->driver_private; |
| 382 | const size_t granularity = (1 << params->protection_granularity_shift); |
| 383 | |
| 384 | if (params->bp_bits == 3) { |
Patrick Rudolph | 8747136 | 2018-09-25 14:26:33 +0200 | [diff] [blame] | 385 | union status_reg1_bp3 reg1_bp3 = { .u = 0 }; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 386 | |
| 387 | ret = spi_flash_cmd(&flash->spi, flash->status_cmd, ®1_bp3.u, |
| 388 | sizeof(reg1_bp3.u)); |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 389 | |
| 390 | if (reg1_bp3.sec) { |
| 391 | // FIXME: not supported |
| 392 | return -1; |
| 393 | } |
| 394 | |
| 395 | bp = reg1_bp3.bp; |
| 396 | tb = reg1_bp3.tb; |
| 397 | } else if (params->bp_bits == 4) { |
Patrick Rudolph | 8747136 | 2018-09-25 14:26:33 +0200 | [diff] [blame] | 398 | union status_reg1_bp4 reg1_bp4 = { .u = 0 }; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 399 | |
| 400 | ret = spi_flash_cmd(&flash->spi, flash->status_cmd, ®1_bp4.u, |
| 401 | sizeof(reg1_bp4.u)); |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 402 | |
| 403 | bp = reg1_bp4.bp; |
| 404 | tb = reg1_bp4.tb; |
| 405 | } else { |
| 406 | // FIXME: not supported |
| 407 | return -1; |
| 408 | } |
Patrick Rudolph | 8747136 | 2018-09-25 14:26:33 +0200 | [diff] [blame] | 409 | if (ret) |
| 410 | return ret; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 411 | |
| 412 | ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®2.u, |
| 413 | sizeof(reg2.u)); |
| 414 | if (ret) |
| 415 | return ret; |
| 416 | |
| 417 | winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size, |
| 418 | &wp_region); |
| 419 | |
Patrick Rudolph | 8747136 | 2018-09-25 14:26:33 +0200 | [diff] [blame] | 420 | if (!region_sz(&wp_region)) { |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 421 | printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n"); |
| 422 | |
| 423 | return 0; |
| 424 | } |
| 425 | |
| 426 | printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n", |
Patrick Rudolph | 8747136 | 2018-09-25 14:26:33 +0200 | [diff] [blame] | 427 | region_offset(&wp_region), |
| 428 | region_offset(&wp_region) + region_sz(&wp_region)); |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 429 | |
| 430 | return region_is_subregion(&wp_region, region); |
| 431 | } |
| 432 | |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 433 | /** |
| 434 | * Common method to write some bit of the status register 1 & 2 at the same |
| 435 | * time. Only change bits that are one in @mask. |
| 436 | * Compare the final result to make sure that the register isn't locked. |
| 437 | * |
| 438 | * @param mask: The bits that are affected by @val |
| 439 | * @param val: The bits to write |
| 440 | * @param non_volatile: Make setting permanent |
| 441 | * |
| 442 | * @return 0 on success |
| 443 | */ |
| 444 | static int winbond_flash_cmd_status(const struct spi_flash *flash, |
| 445 | const u16 mask, |
| 446 | const u16 val, |
| 447 | const bool non_volatile) |
| 448 | { |
| 449 | struct { |
| 450 | u8 cmd; |
| 451 | u16 sreg; |
| 452 | } __packed cmdbuf; |
| 453 | u8 reg8; |
| 454 | int ret; |
| 455 | |
| 456 | if (!flash) |
| 457 | return -1; |
| 458 | |
| 459 | ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, ®8, sizeof(reg8)); |
| 460 | if (ret) |
| 461 | return ret; |
| 462 | |
| 463 | cmdbuf.sreg = reg8; |
| 464 | |
| 465 | ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®8, sizeof(reg8)); |
| 466 | if (ret) |
| 467 | return ret; |
| 468 | |
| 469 | cmdbuf.sreg |= reg8 << 8; |
| 470 | |
| 471 | if ((val & mask) == (cmdbuf.sreg & mask)) |
| 472 | return 0; |
| 473 | |
| 474 | if (non_volatile) { |
| 475 | ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0); |
| 476 | } else { |
| 477 | ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL, |
| 478 | 0); |
| 479 | } |
| 480 | if (ret) |
| 481 | return ret; |
| 482 | |
| 483 | cmdbuf.sreg &= ~mask; |
| 484 | cmdbuf.sreg |= val & mask; |
| 485 | cmdbuf.cmd = CMD_W25_WRSR; |
| 486 | |
| 487 | /* Legacy method of writing status register 1 & 2 */ |
| 488 | ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf), |
| 489 | NULL, 0); |
| 490 | if (ret) |
| 491 | return ret; |
| 492 | |
| 493 | if (non_volatile) { |
| 494 | /* Wait tw */ |
| 495 | ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT); |
| 496 | if (ret) |
| 497 | return ret; |
| 498 | } else { |
| 499 | /* Wait tSHSL */ |
| 500 | udelay(1); |
| 501 | } |
| 502 | |
| 503 | /* Now read the status register to make sure it's not locked */ |
| 504 | ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, ®8, sizeof(reg8)); |
| 505 | if (ret) |
| 506 | return ret; |
| 507 | |
| 508 | cmdbuf.sreg = reg8; |
| 509 | |
| 510 | ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®8, sizeof(reg8)); |
| 511 | if (ret) |
| 512 | return ret; |
| 513 | |
| 514 | cmdbuf.sreg |= reg8 << 8; |
| 515 | |
| 516 | printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n", |
| 517 | cmdbuf.sreg & 0xff, |
| 518 | cmdbuf.sreg >> 8); |
| 519 | |
| 520 | /* Compare against expected result */ |
| 521 | if ((val & mask) != (cmdbuf.sreg & mask)) { |
| 522 | printk(BIOS_ERR, "WINBOND: SREG is locked!\n"); |
| 523 | ret = -1; |
| 524 | } |
| 525 | |
| 526 | return ret; |
| 527 | } |
| 528 | |
| 529 | /* |
| 530 | * Available on all devices. |
| 531 | * Protect a region starting from start of flash or end of flash. |
| 532 | * The caller must provide a supported protected region size. |
| 533 | * SEC isn't supported and set to zero. |
| 534 | * Write block protect bits to Status/Status2 Reg. |
| 535 | * Optionally lock the status register if lock_sreg is set with the provided |
| 536 | * mode. |
| 537 | * |
| 538 | * @param flash: The flash to operate on |
| 539 | * @param region: The region to write protect |
| 540 | * @param non_volatile: Make setting permanent |
| 541 | * @param mode: Optional status register lock-down mode |
| 542 | * |
| 543 | * @return 0 on success |
| 544 | */ |
| 545 | static int |
| 546 | winbond_set_write_protection(const struct spi_flash *flash, |
| 547 | const struct region *region, |
| 548 | const bool non_volatile, |
| 549 | const enum spi_flash_status_reg_lockdown mode) |
| 550 | { |
| 551 | const struct winbond_spi_flash_params *params; |
| 552 | struct status_regs mask, val; |
| 553 | struct region wp_region; |
| 554 | u8 cmp, bp, tb; |
| 555 | int ret; |
| 556 | |
| 557 | /* Need to touch TOP or BOTTOM */ |
| 558 | if (region_offset(region) != 0 && |
| 559 | (region_offset(region) + region_sz(region)) != flash->size) |
| 560 | return -1; |
| 561 | |
| 562 | params = (const struct winbond_spi_flash_params *)flash->driver_private; |
| 563 | if (!params) |
| 564 | return -1; |
| 565 | |
| 566 | if (params->bp_bits != 3 && params->bp_bits != 4) { |
| 567 | /* FIXME: not implemented */ |
| 568 | return -1; |
| 569 | } |
| 570 | |
| 571 | wp_region = *region; |
| 572 | |
| 573 | if (region_offset(&wp_region) == 0) |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 574 | tb = 1; |
Patrick Rudolph | e736015 | 2018-12-03 09:41:06 +0100 | [diff] [blame] | 575 | else |
| 576 | tb = 0; |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 577 | |
| 578 | if (region_sz(&wp_region) > flash->size / 2) { |
| 579 | cmp = 1; |
| 580 | wp_region.offset = tb ? 0 : region_sz(&wp_region); |
| 581 | wp_region.size = flash->size - region_sz(&wp_region); |
| 582 | tb = !tb; |
| 583 | } else { |
| 584 | cmp = 0; |
| 585 | } |
| 586 | |
| 587 | if (region_sz(&wp_region) == 0) { |
| 588 | bp = 0; |
| 589 | } else if (IS_POWER_OF_2(region_sz(&wp_region)) && |
| 590 | (region_sz(&wp_region) >= |
| 591 | (1 << params->protection_granularity_shift))) { |
| 592 | bp = log2(region_sz(&wp_region)) - |
| 593 | params->protection_granularity_shift + 1; |
| 594 | } else { |
| 595 | printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n"); |
| 596 | return -1; |
| 597 | } |
| 598 | |
| 599 | /* Write block protection bits */ |
| 600 | |
| 601 | if (params->bp_bits == 3) { |
| 602 | val.reg1_bp3 = (union status_reg1_bp3) { .bp = bp, .tb = tb, |
| 603 | .sec = 0 }; |
| 604 | mask.reg1_bp3 = (union status_reg1_bp3) { .bp = ~0, .tb = 1, |
| 605 | .sec = 1 }; |
| 606 | } else { |
| 607 | val.reg1_bp4 = (union status_reg1_bp4) { .bp = bp, .tb = tb }; |
| 608 | mask.reg1_bp4 = (union status_reg1_bp4) { .bp = ~0, .tb = 1 }; |
| 609 | } |
| 610 | |
| 611 | val.reg2 = (union status_reg2) { .cmp = cmp }; |
| 612 | mask.reg2 = (union status_reg2) { .cmp = 1 }; |
| 613 | |
| 614 | if (mode != SPI_WRITE_PROTECTION_PRESERVE) { |
| 615 | u8 srp; |
| 616 | switch (mode) { |
| 617 | case SPI_WRITE_PROTECTION_NONE: |
| 618 | srp = 0; |
| 619 | break; |
| 620 | case SPI_WRITE_PROTECTION_PIN: |
| 621 | srp = 1; |
| 622 | break; |
| 623 | case SPI_WRITE_PROTECTION_REBOOT: |
| 624 | srp = 2; |
| 625 | break; |
| 626 | case SPI_WRITE_PROTECTION_PERMANENT: |
| 627 | srp = 3; |
| 628 | break; |
| 629 | default: |
| 630 | return -1; |
| 631 | } |
| 632 | |
| 633 | if (params->bp_bits == 3) { |
| 634 | val.reg1_bp3.srp0 = !!(srp & 1); |
| 635 | mask.reg1_bp3.srp0 = 1; |
| 636 | } else { |
| 637 | val.reg1_bp4.srp0 = !!(srp & 1); |
| 638 | mask.reg1_bp4.srp0 = 1; |
| 639 | } |
| 640 | |
| 641 | val.reg2.srp1 = !!(srp & 2); |
| 642 | mask.reg2.srp1 = 1; |
| 643 | } |
| 644 | |
| 645 | ret = winbond_flash_cmd_status(flash, mask.u, val.u, non_volatile); |
| 646 | if (ret) |
| 647 | return ret; |
| 648 | |
| 649 | printk(BIOS_DEBUG, "WINBOND: write-protection set to range " |
| 650 | "0x%08zx-0x%08zx\n", region_offset(region), |
| 651 | region_offset(region) + region_sz(region)); |
| 652 | |
| 653 | return ret; |
| 654 | } |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 655 | |
Furquan Shaikh | e2fc5e2 | 2017-05-17 17:26:01 -0700 | [diff] [blame] | 656 | static const struct spi_flash_ops spi_flash_ops = { |
| 657 | .write = winbond_write, |
| 658 | .erase = spi_flash_cmd_erase, |
| 659 | .status = spi_flash_cmd_status, |
| 660 | #if IS_ENABLED(CONFIG_SPI_FLASH_NO_FAST_READ) |
| 661 | .read = spi_flash_cmd_read_slow, |
| 662 | #else |
| 663 | .read = spi_flash_cmd_read_fast, |
| 664 | #endif |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 665 | .get_write_protection = winbond_get_write_protection, |
Patrick Rudolph | e63a5f1 | 2018-03-12 11:34:53 +0100 | [diff] [blame] | 666 | .set_write_protection = winbond_set_write_protection, |
Furquan Shaikh | e2fc5e2 | 2017-05-17 17:26:01 -0700 | [diff] [blame] | 667 | }; |
| 668 | |
Furquan Shaikh | bd9e32e | 2017-05-15 23:28:41 -0700 | [diff] [blame] | 669 | int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode, |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 670 | struct spi_flash *flash) |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 671 | { |
| 672 | const struct winbond_spi_flash_params *params; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 673 | unsigned int i; |
| 674 | |
| 675 | for (i = 0; i < ARRAY_SIZE(winbond_spi_flash_table); i++) { |
| 676 | params = &winbond_spi_flash_table[i]; |
| 677 | if (params->id == ((idcode[1] << 8) | idcode[2])) |
| 678 | break; |
| 679 | } |
| 680 | |
| 681 | if (i == ARRAY_SIZE(winbond_spi_flash_table)) { |
| 682 | printk(BIOS_WARNING, "SF: Unsupported Winbond ID %02x%02x\n", |
| 683 | idcode[1], idcode[2]); |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 684 | return -1; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 685 | } |
| 686 | |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 687 | memcpy(&flash->spi, spi, sizeof(*spi)); |
| 688 | flash->name = params->name; |
Patrick Rudolph | 79ab843 | 2018-08-03 08:19:00 +0200 | [diff] [blame] | 689 | |
| 690 | /* Params are in power-of-two. */ |
| 691 | flash->page_size = 1 << params->l2_page_size_shift; |
| 692 | flash->sector_size = flash->page_size * |
| 693 | (1 << params->pages_per_sector_shift); |
| 694 | flash->size = flash->sector_size * |
| 695 | (1 << params->sectors_per_block_shift) * |
| 696 | (1 << params->nr_blocks_shift); |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 697 | flash->erase_cmd = CMD_W25_SE; |
| 698 | flash->status_cmd = CMD_W25_RDSR; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 699 | |
Furquan Shaikh | e2fc5e2 | 2017-05-17 17:26:01 -0700 | [diff] [blame] | 700 | flash->ops = &spi_flash_ops; |
Patrick Rudolph | 0f8bf02 | 2018-03-09 14:20:25 +0100 | [diff] [blame] | 701 | flash->driver_private = params; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 702 | |
Furquan Shaikh | 30221b4 | 2017-05-15 14:35:15 -0700 | [diff] [blame] | 703 | return 0; |
Stefan Reinauer | 1c56d9b | 2012-05-10 11:27:32 -0700 | [diff] [blame] | 704 | } |