treewide: replace GPLv2 long form headers with SPDX header
[coreboot.git] / src / drivers / spi / winbond.c
blob278e64d8b08a6592dc64f94021d58e4b7b631f89
1 /* This file is part of the coreboot project. */
2 /* SPDX-License-Identifier: GPL-2.0-or-later */
4 #include <console/console.h>
5 #include <commonlib/helpers.h>
6 #include <spi_flash.h>
7 #include <spi-generic.h>
8 #include <string.h>
9 #include <delay.h>
10 #include <lib.h>
12 #include "spi_flash_internal.h"
13 #include "spi_winbond.h"
15 union status_reg1_bp3 {
16 uint8_t u;
17 struct {
18 uint8_t busy : 1;
19 uint8_t wel : 1;
20 uint8_t bp : 3;
21 uint8_t tb : 1;
22 uint8_t sec : 1;
23 uint8_t srp0 : 1;
27 union status_reg1_bp4 {
28 uint8_t u;
29 struct {
30 uint8_t busy : 1;
31 uint8_t wel : 1;
32 uint8_t bp : 4;
33 uint8_t tb : 1;
34 uint8_t srp0 : 1;
38 union status_reg2 {
39 uint8_t u;
40 struct {
41 uint8_t srp1 : 1;
42 uint8_t qe : 1;
43 uint8_t res : 1;
44 uint8_t lb : 3;
45 uint8_t cmp : 1;
46 uint8_t sus : 1;
50 struct status_regs {
51 union {
52 struct {
53 #if defined(__BIG_ENDIAN)
54 union status_reg2 reg2;
55 union {
56 union status_reg1_bp3 reg1_bp3;
57 union status_reg1_bp4 reg1_bp4;
59 #else
60 union {
61 union status_reg1_bp3 reg1_bp3;
62 union status_reg1_bp4 reg1_bp4;
64 union status_reg2 reg2;
65 #endif
67 u16 u;
71 static const struct spi_flash_part_id flash_table[] = {
73 /* W25P80 */
74 .id[0] = 0x2014,
75 .nr_sectors_shift = 8,
78 /* W25P16 */
79 .id[0] = 0x2015,
80 .nr_sectors_shift = 9,
83 /* W25P32 */
84 .id[0] = 0x2016,
85 .nr_sectors_shift = 10,
88 /* W25X80 */
89 .id[0] = 0x3014,
90 .nr_sectors_shift = 8,
91 .fast_read_dual_output_support = 1,
94 /* W25X16 */
95 .id[0] = 0x3015,
96 .nr_sectors_shift = 9,
97 .fast_read_dual_output_support = 1,
100 /* W25X32 */
101 .id[0] = 0x3016,
102 .nr_sectors_shift = 10,
103 .fast_read_dual_output_support = 1,
106 /* W25X64 */
107 .id[0] = 0x3017,
108 .nr_sectors_shift = 11,
109 .fast_read_dual_output_support = 1,
112 /* W25Q80_V */
113 .id[0] = 0x4014,
114 .nr_sectors_shift = 8,
115 .fast_read_dual_output_support = 1,
118 /* W25Q16_V */
119 .id[0] = 0x4015,
120 .nr_sectors_shift = 9,
121 .fast_read_dual_output_support = 1,
122 .protection_granularity_shift = 16,
123 .bp_bits = 3,
126 /* W25Q16DW */
127 .id[0] = 0x6015,
128 .nr_sectors_shift = 9,
129 .fast_read_dual_output_support = 1,
130 .protection_granularity_shift = 16,
131 .bp_bits = 3,
134 /* W25Q32_V */
135 .id[0] = 0x4016,
136 .nr_sectors_shift = 10,
137 .fast_read_dual_output_support = 1,
138 .protection_granularity_shift = 16,
139 .bp_bits = 3,
142 /* W25Q32DW */
143 .id[0] = 0x6016,
144 .nr_sectors_shift = 10,
145 .fast_read_dual_output_support = 1,
146 .protection_granularity_shift = 16,
147 .bp_bits = 3,
150 /* W25Q64_V */
151 .id[0] = 0x4017,
152 .nr_sectors_shift = 11,
153 .fast_read_dual_output_support = 1,
154 .protection_granularity_shift = 17,
155 .bp_bits = 3,
158 /* W25Q64DW */
159 .id[0] = 0x6017,
160 .nr_sectors_shift = 11,
161 .fast_read_dual_output_support = 1,
162 .protection_granularity_shift = 17,
163 .bp_bits = 3,
166 /* W25Q64JW */
167 .id[0] = 0x8017,
168 .nr_sectors_shift = 11,
169 .fast_read_dual_output_support = 1,
170 .protection_granularity_shift = 17,
171 .bp_bits = 3,
174 /* W25Q128_V */
175 .id[0] = 0x4018,
176 .nr_sectors_shift = 12,
177 .fast_read_dual_output_support = 1,
178 .protection_granularity_shift = 18,
179 .bp_bits = 3,
182 /* W25Q128FW */
183 .id[0] = 0x6018,
184 .nr_sectors_shift = 12,
185 .fast_read_dual_output_support = 1,
186 .protection_granularity_shift = 18,
187 .bp_bits = 3,
190 /* W25Q128J */
191 .id[0] = 0x7018,
192 .nr_sectors_shift = 12,
193 .fast_read_dual_output_support = 1,
194 .protection_granularity_shift = 18,
195 .bp_bits = 3,
198 /* W25Q128JW */
199 .id[0] = 0x8018,
200 .nr_sectors_shift = 12,
201 .fast_read_dual_output_support = 1,
202 .protection_granularity_shift = 18,
203 .bp_bits = 3,
206 /* W25Q256_V */
207 .id[0] = 0x4019,
208 .nr_sectors_shift = 13,
209 .fast_read_dual_output_support = 1,
210 .protection_granularity_shift = 16,
211 .bp_bits = 4,
214 /* W25Q256J */
215 .id[0] = 0x7019,
216 .nr_sectors_shift = 13,
217 .fast_read_dual_output_support = 1,
218 .protection_granularity_shift = 16,
219 .bp_bits = 4,
224 * Convert BPx, TB and CMP to a region.
225 * SEC (if available) must be zero.
227 static void winbond_bpbits_to_region(const size_t granularity,
228 const u8 bp,
229 bool tb,
230 const bool cmp,
231 const size_t flash_size,
232 struct region *out)
234 size_t protected_size =
235 MIN(bp ? granularity << (bp - 1) : 0, flash_size);
237 if (cmp) {
238 protected_size = flash_size - protected_size;
239 tb = !tb;
242 out->offset = tb ? 0 : flash_size - protected_size;
243 out->size = protected_size;
247 * Available on all devices.
248 * Read block protect bits from Status/Status2 Reg.
249 * Converts block protection bits to a region.
251 * Returns:
252 * -1 on error
253 * 1 if region is covered by write protection
254 * 0 if a part of region isn't covered by write protection
256 static int winbond_get_write_protection(const struct spi_flash *flash,
257 const struct region *region)
259 const struct spi_flash_part_id *params;
260 struct region wp_region;
261 union status_reg2 reg2;
262 u8 bp, tb;
263 int ret;
265 params = flash->part;
267 if (!params)
268 return -1;
270 const size_t granularity = (1 << params->protection_granularity_shift);
272 if (params->bp_bits == 3) {
273 union status_reg1_bp3 reg1_bp3 = { .u = 0 };
275 ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp3.u,
276 sizeof(reg1_bp3.u));
278 if (reg1_bp3.sec) {
279 // FIXME: not supported
280 return -1;
283 bp = reg1_bp3.bp;
284 tb = reg1_bp3.tb;
285 } else if (params->bp_bits == 4) {
286 union status_reg1_bp4 reg1_bp4 = { .u = 0 };
288 ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp4.u,
289 sizeof(reg1_bp4.u));
291 bp = reg1_bp4.bp;
292 tb = reg1_bp4.tb;
293 } else {
294 // FIXME: not supported
295 return -1;
297 if (ret)
298 return ret;
300 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg2.u,
301 sizeof(reg2.u));
302 if (ret)
303 return ret;
305 winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size,
306 &wp_region);
308 if (!region_sz(&wp_region)) {
309 printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n");
311 return 0;
314 printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n",
315 region_offset(&wp_region), region_end(&wp_region));
317 return region_is_subregion(&wp_region, region);
321 * Common method to write some bit of the status register 1 & 2 at the same
322 * time. Only change bits that are one in @mask.
323 * Compare the final result to make sure that the register isn't locked.
325 * @param mask: The bits that are affected by @val
326 * @param val: The bits to write
327 * @param non_volatile: Make setting permanent
329 * @return 0 on success
331 static int winbond_flash_cmd_status(const struct spi_flash *flash,
332 const u16 mask,
333 const u16 val,
334 const bool non_volatile)
336 struct {
337 u8 cmd;
338 u16 sreg;
339 } __packed cmdbuf;
340 u8 reg8;
341 int ret;
343 if (!flash)
344 return -1;
346 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
347 if (ret)
348 return ret;
350 cmdbuf.sreg = reg8;
352 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
353 if (ret)
354 return ret;
356 cmdbuf.sreg |= reg8 << 8;
358 if ((val & mask) == (cmdbuf.sreg & mask))
359 return 0;
361 if (non_volatile) {
362 ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
363 } else {
364 ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL,
367 if (ret)
368 return ret;
370 cmdbuf.sreg &= ~mask;
371 cmdbuf.sreg |= val & mask;
372 cmdbuf.cmd = CMD_W25_WRSR;
374 /* Legacy method of writing status register 1 & 2 */
375 ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf),
376 NULL, 0);
377 if (ret)
378 return ret;
380 if (non_volatile) {
381 /* Wait tw */
382 ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT);
383 if (ret)
384 return ret;
385 } else {
386 /* Wait tSHSL */
387 udelay(1);
390 /* Now read the status register to make sure it's not locked */
391 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
392 if (ret)
393 return ret;
395 cmdbuf.sreg = reg8;
397 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
398 if (ret)
399 return ret;
401 cmdbuf.sreg |= reg8 << 8;
403 printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n",
404 cmdbuf.sreg & 0xff,
405 cmdbuf.sreg >> 8);
407 /* Compare against expected result */
408 if ((val & mask) != (cmdbuf.sreg & mask)) {
409 printk(BIOS_ERR, "WINBOND: SREG is locked!\n");
410 ret = -1;
413 return ret;
417 * Available on all devices.
418 * Protect a region starting from start of flash or end of flash.
419 * The caller must provide a supported protected region size.
420 * SEC isn't supported and set to zero.
421 * Write block protect bits to Status/Status2 Reg.
422 * Optionally lock the status register if lock_sreg is set with the provided
423 * mode.
425 * @param flash: The flash to operate on
426 * @param region: The region to write protect
427 * @param non_volatile: Make setting permanent
428 * @param mode: Optional status register lock-down mode
430 * @return 0 on success
432 static int
433 winbond_set_write_protection(const struct spi_flash *flash,
434 const struct region *region,
435 const bool non_volatile,
436 const enum spi_flash_status_reg_lockdown mode)
438 const struct spi_flash_part_id *params;
439 struct status_regs mask, val;
440 struct region wp_region;
441 u8 cmp, bp, tb;
442 int ret;
444 /* Need to touch TOP or BOTTOM */
445 if (region_offset(region) != 0 && region_end(region) != flash->size)
446 return -1;
448 params = flash->part;
450 if (!params)
451 return -1;
453 if (params->bp_bits != 3 && params->bp_bits != 4) {
454 /* FIXME: not implemented */
455 return -1;
458 wp_region = *region;
460 if (region_offset(&wp_region) == 0)
461 tb = 1;
462 else
463 tb = 0;
465 if (region_sz(&wp_region) > flash->size / 2) {
466 cmp = 1;
467 wp_region.offset = tb ? 0 : region_sz(&wp_region);
468 wp_region.size = flash->size - region_sz(&wp_region);
469 tb = !tb;
470 } else {
471 cmp = 0;
474 if (region_sz(&wp_region) == 0) {
475 bp = 0;
476 } else if (IS_POWER_OF_2(region_sz(&wp_region)) &&
477 (region_sz(&wp_region) >=
478 (1 << params->protection_granularity_shift))) {
479 bp = log2(region_sz(&wp_region)) -
480 params->protection_granularity_shift + 1;
481 } else {
482 printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n");
483 return -1;
486 /* Write block protection bits */
488 if (params->bp_bits == 3) {
489 val.reg1_bp3 = (union status_reg1_bp3) { .bp = bp, .tb = tb,
490 .sec = 0 };
491 mask.reg1_bp3 = (union status_reg1_bp3) { .bp = ~0, .tb = 1,
492 .sec = 1 };
493 } else {
494 val.reg1_bp4 = (union status_reg1_bp4) { .bp = bp, .tb = tb };
495 mask.reg1_bp4 = (union status_reg1_bp4) { .bp = ~0, .tb = 1 };
498 val.reg2 = (union status_reg2) { .cmp = cmp };
499 mask.reg2 = (union status_reg2) { .cmp = 1 };
501 if (mode != SPI_WRITE_PROTECTION_PRESERVE) {
502 u8 srp;
503 switch (mode) {
504 case SPI_WRITE_PROTECTION_NONE:
505 srp = 0;
506 break;
507 case SPI_WRITE_PROTECTION_PIN:
508 srp = 1;
509 break;
510 case SPI_WRITE_PROTECTION_REBOOT:
511 srp = 2;
512 break;
513 case SPI_WRITE_PROTECTION_PERMANENT:
514 srp = 3;
515 break;
516 default:
517 return -1;
520 if (params->bp_bits == 3) {
521 val.reg1_bp3.srp0 = !!(srp & 1);
522 mask.reg1_bp3.srp0 = 1;
523 } else {
524 val.reg1_bp4.srp0 = !!(srp & 1);
525 mask.reg1_bp4.srp0 = 1;
528 val.reg2.srp1 = !!(srp & 2);
529 mask.reg2.srp1 = 1;
532 ret = winbond_flash_cmd_status(flash, mask.u, val.u, non_volatile);
533 if (ret)
534 return ret;
536 printk(BIOS_DEBUG, "WINBOND: write-protection set to range "
537 "0x%08zx-0x%08zx\n", region_offset(region), region_end(region));
539 return ret;
542 static const struct spi_flash_protection_ops spi_flash_protection_ops = {
543 .get_write = winbond_get_write_protection,
544 .set_write = winbond_set_write_protection,
547 const struct spi_flash_vendor_info spi_flash_winbond_vi = {
548 .id = VENDOR_ID_WINBOND,
549 .page_size_shift = 8,
550 .sector_size_kib_shift = 2,
551 .match_id_mask[0] = 0xffff,
552 .ids = flash_table,
553 .nr_part_ids = ARRAY_SIZE(flash_table),
554 .desc = &spi_flash_pp_0x20_sector_desc,
555 .prot_ops = &spi_flash_protection_ops,