{drivers,southbridge}: Replace min() with MIN()
[coreboot.git] / src / drivers / spi / winbond.c
blob3790ce7bf146121f80d6bb5c8daba2b94fdec8a6
1 /*
2 * This file is part of the coreboot project.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
15 #include <console/console.h>
16 #include <commonlib/helpers.h>
17 #include <spi_flash.h>
18 #include <spi-generic.h>
19 #include <string.h>
20 #include <delay.h>
21 #include <lib.h>
23 #include "spi_flash_internal.h"
24 #include "spi_winbond.h"
26 struct winbond_spi_flash_params {
27 uint16_t id;
28 uint8_t dual_spi : 1;
29 uint8_t _reserved_for_flags : 3;
30 uint8_t l2_page_size_shift : 4;
31 uint8_t pages_per_sector_shift : 4;
32 uint8_t sectors_per_block_shift : 4;
33 uint8_t nr_blocks_shift;
34 uint8_t bp_bits : 3;
35 uint8_t protection_granularity_shift : 5;
36 char name[10];
39 union status_reg1_bp3 {
40 uint8_t u;
41 struct {
42 uint8_t busy : 1;
43 uint8_t wel : 1;
44 uint8_t bp : 3;
45 uint8_t tb : 1;
46 uint8_t sec : 1;
47 uint8_t srp0 : 1;
51 union status_reg1_bp4 {
52 uint8_t u;
53 struct {
54 uint8_t busy : 1;
55 uint8_t wel : 1;
56 uint8_t bp : 4;
57 uint8_t tb : 1;
58 uint8_t srp0 : 1;
62 union status_reg2 {
63 uint8_t u;
64 struct {
65 uint8_t srp1 : 1;
66 uint8_t qe : 1;
67 uint8_t res : 1;
68 uint8_t lb : 3;
69 uint8_t cmp : 1;
70 uint8_t sus : 1;
74 struct status_regs {
75 union {
76 struct {
77 #if defined(__BIG_ENDIAN)
78 union status_reg2 reg2;
79 union {
80 union status_reg1_bp3 reg1_bp3;
81 union status_reg1_bp4 reg1_bp4;
83 #else
84 union {
85 union status_reg1_bp3 reg1_bp3;
86 union status_reg1_bp4 reg1_bp4;
88 union status_reg2 reg2;
89 #endif
91 u16 u;
95 static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
97 .id = 0x2014,
98 .l2_page_size_shift = 8,
99 .pages_per_sector_shift = 4,
100 .sectors_per_block_shift = 4,
101 .nr_blocks_shift = 4,
102 .name = "W25P80",
105 .id = 0x2015,
106 .l2_page_size_shift = 8,
107 .pages_per_sector_shift = 4,
108 .sectors_per_block_shift = 4,
109 .nr_blocks_shift = 5,
110 .name = "W25P16",
113 .id = 0x2016,
114 .l2_page_size_shift = 8,
115 .pages_per_sector_shift = 4,
116 .sectors_per_block_shift = 4,
117 .nr_blocks_shift = 6,
118 .name = "W25P32",
121 .id = 0x3014,
122 .l2_page_size_shift = 8,
123 .pages_per_sector_shift = 4,
124 .sectors_per_block_shift = 4,
125 .nr_blocks_shift = 4,
126 .name = "W25X80",
127 .dual_spi = 1,
130 .id = 0x3015,
131 .l2_page_size_shift = 8,
132 .pages_per_sector_shift = 4,
133 .sectors_per_block_shift = 4,
134 .nr_blocks_shift = 5,
135 .name = "W25X16",
136 .dual_spi = 1,
139 .id = 0x3016,
140 .l2_page_size_shift = 8,
141 .pages_per_sector_shift = 4,
142 .sectors_per_block_shift = 4,
143 .nr_blocks_shift = 6,
144 .name = "W25X32",
145 .dual_spi = 1,
148 .id = 0x3017,
149 .l2_page_size_shift = 8,
150 .pages_per_sector_shift = 4,
151 .sectors_per_block_shift = 4,
152 .nr_blocks_shift = 7,
153 .name = "W25X64",
154 .dual_spi = 1,
157 .id = 0x4014,
158 .l2_page_size_shift = 8,
159 .pages_per_sector_shift = 4,
160 .sectors_per_block_shift = 4,
161 .nr_blocks_shift = 4,
162 .name = "W25Q80_V",
163 .dual_spi = 1,
166 .id = 0x4015,
167 .l2_page_size_shift = 8,
168 .pages_per_sector_shift = 4,
169 .sectors_per_block_shift = 4,
170 .nr_blocks_shift = 5,
171 .name = "W25Q16_V",
172 .dual_spi = 1,
173 .protection_granularity_shift = 16,
174 .bp_bits = 3,
177 .id = 0x6015,
178 .l2_page_size_shift = 8,
179 .pages_per_sector_shift = 4,
180 .sectors_per_block_shift = 4,
181 .nr_blocks_shift = 5,
182 .name = "W25Q16DW",
183 .dual_spi = 1,
184 .protection_granularity_shift = 16,
185 .bp_bits = 3,
188 .id = 0x4016,
189 .l2_page_size_shift = 8,
190 .pages_per_sector_shift = 4,
191 .sectors_per_block_shift = 4,
192 .nr_blocks_shift = 6,
193 .name = "W25Q32_V",
194 .dual_spi = 1,
195 .protection_granularity_shift = 16,
196 .bp_bits = 3,
199 .id = 0x6016,
200 .l2_page_size_shift = 8,
201 .pages_per_sector_shift = 4,
202 .sectors_per_block_shift = 4,
203 .nr_blocks_shift = 6,
204 .name = "W25Q32DW",
205 .dual_spi = 1,
206 .protection_granularity_shift = 16,
207 .bp_bits = 3,
210 .id = 0x4017,
211 .l2_page_size_shift = 8,
212 .pages_per_sector_shift = 4,
213 .sectors_per_block_shift = 4,
214 .nr_blocks_shift = 7,
215 .name = "W25Q64_V",
216 .dual_spi = 1,
217 .protection_granularity_shift = 17,
218 .bp_bits = 3,
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",
227 .dual_spi = 1,
228 .protection_granularity_shift = 17,
229 .bp_bits = 3,
232 .id = 0x4018,
233 .l2_page_size_shift = 8,
234 .pages_per_sector_shift = 4,
235 .sectors_per_block_shift = 4,
236 .nr_blocks_shift = 8,
237 .name = "W25Q128_V",
238 .dual_spi = 1,
239 .protection_granularity_shift = 18,
240 .bp_bits = 3,
243 .id = 0x6018,
244 .l2_page_size_shift = 8,
245 .pages_per_sector_shift = 4,
246 .sectors_per_block_shift = 4,
247 .nr_blocks_shift = 8,
248 .name = "W25Q128FW",
249 .dual_spi = 1,
250 .protection_granularity_shift = 18,
251 .bp_bits = 3,
254 .id = 0x7018,
255 .l2_page_size_shift = 8,
256 .pages_per_sector_shift = 4,
257 .sectors_per_block_shift = 4,
258 .nr_blocks_shift = 8,
259 .name = "W25Q128J",
260 .dual_spi = 1,
261 .protection_granularity_shift = 18,
262 .bp_bits = 3,
265 .id = 0x8018,
266 .l2_page_size_shift = 8,
267 .pages_per_sector_shift = 4,
268 .sectors_per_block_shift = 4,
269 .nr_blocks_shift = 8,
270 .name = "W25Q128JW",
271 .dual_spi = 1,
272 .protection_granularity_shift = 18,
273 .bp_bits = 3,
276 .id = 0x4019,
277 .l2_page_size_shift = 8,
278 .pages_per_sector_shift = 4,
279 .sectors_per_block_shift = 4,
280 .nr_blocks_shift = 9,
281 .name = "W25Q256_V",
282 .dual_spi = 1,
283 .protection_granularity_shift = 16,
284 .bp_bits = 4,
287 .id = 0x7019,
288 .l2_page_size_shift = 8,
289 .pages_per_sector_shift = 4,
290 .sectors_per_block_shift = 4,
291 .nr_blocks_shift = 9,
292 .name = "W25Q256J",
293 .dual_spi = 1,
294 .protection_granularity_shift = 16,
295 .bp_bits = 4,
299 static int winbond_write(const struct spi_flash *flash, u32 offset, size_t len,
300 const void *buf)
302 unsigned long byte_addr;
303 unsigned long page_size;
304 size_t chunk_len;
305 size_t actual;
306 int ret = 0;
307 u8 cmd[4];
309 page_size = flash->page_size;
311 for (actual = 0; actual < len; actual += chunk_len) {
312 byte_addr = offset % page_size;
313 chunk_len = MIN(len - actual, page_size - byte_addr);
314 chunk_len = spi_crop_chunk(&flash->spi, sizeof(cmd), chunk_len);
316 cmd[0] = CMD_W25_PP;
317 cmd[1] = (offset >> 16) & 0xff;
318 cmd[2] = (offset >> 8) & 0xff;
319 cmd[3] = offset & 0xff;
320 #if CONFIG(DEBUG_SPI_FLASH)
321 printk(BIOS_SPEW, "PP: %p => cmd = { 0x%02x 0x%02x%02x%02x }"
322 " chunk_len = %zu\n", buf + actual,
323 cmd[0], cmd[1], cmd[2], cmd[3], chunk_len);
324 #endif
326 ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
327 if (ret < 0) {
328 printk(BIOS_WARNING, "SF: Enabling Write failed\n");
329 goto out;
332 ret = spi_flash_cmd_write(&flash->spi, cmd, sizeof(cmd),
333 buf + actual, chunk_len);
334 if (ret < 0) {
335 printk(BIOS_WARNING, "SF: Winbond Page Program failed\n");
336 goto out;
339 ret = spi_flash_cmd_wait_ready(flash,
340 SPI_FLASH_PROG_TIMEOUT_MS);
341 if (ret)
342 goto out;
344 offset += chunk_len;
347 #if CONFIG(DEBUG_SPI_FLASH)
348 printk(BIOS_SPEW, "SF: Winbond: Successfully programmed %zu bytes @"
349 " 0x%lx\n", len, (unsigned long)(offset - len));
350 #endif
351 ret = 0;
353 out:
354 return ret;
358 * Convert BPx, TB and CMP to a region.
359 * SEC (if available) must be zero.
361 static void winbond_bpbits_to_region(const size_t granularity,
362 const u8 bp,
363 bool tb,
364 const bool cmp,
365 const size_t flash_size,
366 struct region *out)
368 size_t protected_size =
369 MIN(bp ? granularity << (bp - 1) : 0, flash_size);
371 if (cmp) {
372 protected_size = flash_size - protected_size;
373 tb = !tb;
376 out->offset = tb ? 0 : flash_size - protected_size;
377 out->size = protected_size;
381 * Available on all devices.
382 * Read block protect bits from Status/Status2 Reg.
383 * Converts block protection bits to a region.
385 * Returns:
386 * -1 on error
387 * 1 if region is covered by write protection
388 * 0 if a part of region isn't covered by write protection
390 static int winbond_get_write_protection(const struct spi_flash *flash,
391 const struct region *region)
393 const struct winbond_spi_flash_params *params;
394 struct region wp_region;
395 union status_reg2 reg2;
396 u8 bp, tb;
397 int ret;
399 params = (const struct winbond_spi_flash_params *)flash->driver_private;
400 const size_t granularity = (1 << params->protection_granularity_shift);
402 if (params->bp_bits == 3) {
403 union status_reg1_bp3 reg1_bp3 = { .u = 0 };
405 ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp3.u,
406 sizeof(reg1_bp3.u));
408 if (reg1_bp3.sec) {
409 // FIXME: not supported
410 return -1;
413 bp = reg1_bp3.bp;
414 tb = reg1_bp3.tb;
415 } else if (params->bp_bits == 4) {
416 union status_reg1_bp4 reg1_bp4 = { .u = 0 };
418 ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp4.u,
419 sizeof(reg1_bp4.u));
421 bp = reg1_bp4.bp;
422 tb = reg1_bp4.tb;
423 } else {
424 // FIXME: not supported
425 return -1;
427 if (ret)
428 return ret;
430 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg2.u,
431 sizeof(reg2.u));
432 if (ret)
433 return ret;
435 winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size,
436 &wp_region);
438 if (!region_sz(&wp_region)) {
439 printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n");
441 return 0;
444 printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n",
445 region_offset(&wp_region), region_end(&wp_region));
447 return region_is_subregion(&wp_region, region);
451 * Common method to write some bit of the status register 1 & 2 at the same
452 * time. Only change bits that are one in @mask.
453 * Compare the final result to make sure that the register isn't locked.
455 * @param mask: The bits that are affected by @val
456 * @param val: The bits to write
457 * @param non_volatile: Make setting permanent
459 * @return 0 on success
461 static int winbond_flash_cmd_status(const struct spi_flash *flash,
462 const u16 mask,
463 const u16 val,
464 const bool non_volatile)
466 struct {
467 u8 cmd;
468 u16 sreg;
469 } __packed cmdbuf;
470 u8 reg8;
471 int ret;
473 if (!flash)
474 return -1;
476 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
477 if (ret)
478 return ret;
480 cmdbuf.sreg = reg8;
482 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
483 if (ret)
484 return ret;
486 cmdbuf.sreg |= reg8 << 8;
488 if ((val & mask) == (cmdbuf.sreg & mask))
489 return 0;
491 if (non_volatile) {
492 ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
493 } else {
494 ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL,
497 if (ret)
498 return ret;
500 cmdbuf.sreg &= ~mask;
501 cmdbuf.sreg |= val & mask;
502 cmdbuf.cmd = CMD_W25_WRSR;
504 /* Legacy method of writing status register 1 & 2 */
505 ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf),
506 NULL, 0);
507 if (ret)
508 return ret;
510 if (non_volatile) {
511 /* Wait tw */
512 ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT);
513 if (ret)
514 return ret;
515 } else {
516 /* Wait tSHSL */
517 udelay(1);
520 /* Now read the status register to make sure it's not locked */
521 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
522 if (ret)
523 return ret;
525 cmdbuf.sreg = reg8;
527 ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
528 if (ret)
529 return ret;
531 cmdbuf.sreg |= reg8 << 8;
533 printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n",
534 cmdbuf.sreg & 0xff,
535 cmdbuf.sreg >> 8);
537 /* Compare against expected result */
538 if ((val & mask) != (cmdbuf.sreg & mask)) {
539 printk(BIOS_ERR, "WINBOND: SREG is locked!\n");
540 ret = -1;
543 return ret;
547 * Available on all devices.
548 * Protect a region starting from start of flash or end of flash.
549 * The caller must provide a supported protected region size.
550 * SEC isn't supported and set to zero.
551 * Write block protect bits to Status/Status2 Reg.
552 * Optionally lock the status register if lock_sreg is set with the provided
553 * mode.
555 * @param flash: The flash to operate on
556 * @param region: The region to write protect
557 * @param non_volatile: Make setting permanent
558 * @param mode: Optional status register lock-down mode
560 * @return 0 on success
562 static int
563 winbond_set_write_protection(const struct spi_flash *flash,
564 const struct region *region,
565 const bool non_volatile,
566 const enum spi_flash_status_reg_lockdown mode)
568 const struct winbond_spi_flash_params *params;
569 struct status_regs mask, val;
570 struct region wp_region;
571 u8 cmp, bp, tb;
572 int ret;
574 /* Need to touch TOP or BOTTOM */
575 if (region_offset(region) != 0 && region_end(region) != flash->size)
576 return -1;
578 params = (const struct winbond_spi_flash_params *)flash->driver_private;
579 if (!params)
580 return -1;
582 if (params->bp_bits != 3 && params->bp_bits != 4) {
583 /* FIXME: not implemented */
584 return -1;
587 wp_region = *region;
589 if (region_offset(&wp_region) == 0)
590 tb = 1;
591 else
592 tb = 0;
594 if (region_sz(&wp_region) > flash->size / 2) {
595 cmp = 1;
596 wp_region.offset = tb ? 0 : region_sz(&wp_region);
597 wp_region.size = flash->size - region_sz(&wp_region);
598 tb = !tb;
599 } else {
600 cmp = 0;
603 if (region_sz(&wp_region) == 0) {
604 bp = 0;
605 } else if (IS_POWER_OF_2(region_sz(&wp_region)) &&
606 (region_sz(&wp_region) >=
607 (1 << params->protection_granularity_shift))) {
608 bp = log2(region_sz(&wp_region)) -
609 params->protection_granularity_shift + 1;
610 } else {
611 printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n");
612 return -1;
615 /* Write block protection bits */
617 if (params->bp_bits == 3) {
618 val.reg1_bp3 = (union status_reg1_bp3) { .bp = bp, .tb = tb,
619 .sec = 0 };
620 mask.reg1_bp3 = (union status_reg1_bp3) { .bp = ~0, .tb = 1,
621 .sec = 1 };
622 } else {
623 val.reg1_bp4 = (union status_reg1_bp4) { .bp = bp, .tb = tb };
624 mask.reg1_bp4 = (union status_reg1_bp4) { .bp = ~0, .tb = 1 };
627 val.reg2 = (union status_reg2) { .cmp = cmp };
628 mask.reg2 = (union status_reg2) { .cmp = 1 };
630 if (mode != SPI_WRITE_PROTECTION_PRESERVE) {
631 u8 srp;
632 switch (mode) {
633 case SPI_WRITE_PROTECTION_NONE:
634 srp = 0;
635 break;
636 case SPI_WRITE_PROTECTION_PIN:
637 srp = 1;
638 break;
639 case SPI_WRITE_PROTECTION_REBOOT:
640 srp = 2;
641 break;
642 case SPI_WRITE_PROTECTION_PERMANENT:
643 srp = 3;
644 break;
645 default:
646 return -1;
649 if (params->bp_bits == 3) {
650 val.reg1_bp3.srp0 = !!(srp & 1);
651 mask.reg1_bp3.srp0 = 1;
652 } else {
653 val.reg1_bp4.srp0 = !!(srp & 1);
654 mask.reg1_bp4.srp0 = 1;
657 val.reg2.srp1 = !!(srp & 2);
658 mask.reg2.srp1 = 1;
661 ret = winbond_flash_cmd_status(flash, mask.u, val.u, non_volatile);
662 if (ret)
663 return ret;
665 printk(BIOS_DEBUG, "WINBOND: write-protection set to range "
666 "0x%08zx-0x%08zx\n", region_offset(region), region_end(region));
668 return ret;
671 static const struct spi_flash_ops spi_flash_ops = {
672 .write = winbond_write,
673 .erase = spi_flash_cmd_erase,
674 .status = spi_flash_cmd_status,
675 .get_write_protection = winbond_get_write_protection,
676 .set_write_protection = winbond_set_write_protection,
679 int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
680 struct spi_flash *flash)
682 const struct winbond_spi_flash_params *params;
683 unsigned int i;
685 for (i = 0; i < ARRAY_SIZE(winbond_spi_flash_table); i++) {
686 params = &winbond_spi_flash_table[i];
687 if (params->id == ((idcode[1] << 8) | idcode[2]))
688 break;
691 if (i == ARRAY_SIZE(winbond_spi_flash_table)) {
692 printk(BIOS_WARNING, "SF: Unsupported Winbond ID %02x%02x\n",
693 idcode[1], idcode[2]);
694 return -1;
697 memcpy(&flash->spi, spi, sizeof(*spi));
698 flash->name = params->name;
700 /* Params are in power-of-two. */
701 flash->page_size = 1 << params->l2_page_size_shift;
702 flash->sector_size = flash->page_size *
703 (1 << params->pages_per_sector_shift);
704 flash->size = flash->sector_size *
705 (1 << params->sectors_per_block_shift) *
706 (1 << params->nr_blocks_shift);
707 flash->erase_cmd = CMD_W25_SE;
708 flash->status_cmd = CMD_W25_RDSR;
710 flash->flags.dual_spi = params->dual_spi;
712 flash->ops = &spi_flash_ops;
713 flash->driver_private = params;
715 return 0;