Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mtd: spi-nor: add TB (Top/Bottom) protect support

Some flash support a bit in the status register that inverts protection
so that it applies to the bottom of the flash, not the top. This yields
additions to the protection range table, as noted in the comments.

Because this feature is not universal to all flash that support
lock/unlock, control it via a new flag.

Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Tested-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>

+65 -7
+63 -7
drivers/mtd/spi-nor/spi-nor.c
··· 70 70 #define SPI_NOR_QUAD_READ BIT(6) /* Flash supports Quad Read */ 71 71 #define USE_FSR BIT(7) /* use flag status register */ 72 72 #define SPI_NOR_HAS_LOCK BIT(8) /* Flash supports lock/unlock via SR */ 73 + #define SPI_NOR_HAS_TB BIT(9) /* 74 + * Flash SR has Top/Bottom (TB) protect 75 + * bit. Must be used with 76 + * SPI_NOR_HAS_LOCK. 77 + */ 73 78 }; 74 79 75 80 #define JEDEC_MFR(info) ((info)->id[0]) ··· 440 435 } else { 441 436 pow = ((sr & mask) ^ mask) >> shift; 442 437 *len = mtd->size >> pow; 443 - *ofs = mtd->size - *len; 438 + if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) 439 + *ofs = 0; 440 + else 441 + *ofs = mtd->size - *len; 444 442 } 445 443 } 446 444 ··· 484 476 485 477 /* 486 478 * Lock a region of the flash. Compatible with ST Micro and similar flash. 487 - * Supports only the block protection bits BP{0,1,2} in the status register 479 + * Supports the block protection bits BP{0,1,2} in the status register 488 480 * (SR). Does not support these features found in newer SR bitfields: 489 - * - TB: top/bottom protect - only handle TB=0 (top protect) 490 481 * - SEC: sector/block protect - only handle SEC=0 (block protect) 491 482 * - CMP: complement protect - only support CMP=0 (range is not complemented) 483 + * 484 + * Support for the following is provided conditionally for some flash: 485 + * - TB: top/bottom protect 492 486 * 493 487 * Sample table portion for 8MB flash (Winbond w25q64fw): 494 488 * ··· 504 494 * 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4 505 495 * 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2 506 496 * X | X | 1 | 1 | 1 | 8 MB | ALL 497 + * ------|-------|-------|-------|-------|---------------|------------------- 498 + * 0 | 1 | 0 | 0 | 1 | 128 KB | Lower 1/64 499 + * 0 | 1 | 0 | 1 | 0 | 256 KB | Lower 1/32 500 + * 0 | 1 | 0 | 1 | 1 | 512 KB | Lower 1/16 501 + * 0 | 1 | 1 | 0 | 0 | 1 MB | Lower 1/8 502 + * 0 | 1 | 1 | 0 | 1 | 2 MB | Lower 1/4 503 + * 0 | 1 | 1 | 1 | 0 | 4 MB | Lower 1/2 507 504 * 508 505 * Returns negative on errors, 0 on success. 509 506 */ ··· 521 504 u8 mask = SR_BP2 | SR_BP1 | SR_BP0; 522 505 u8 shift = ffs(mask) - 1, pow, val; 523 506 loff_t lock_len; 507 + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; 508 + bool use_top; 524 509 int ret; 525 510 526 511 status_old = read_sr(nor); ··· 533 514 if (stm_is_locked_sr(nor, ofs, len, status_old)) 534 515 return 0; 535 516 517 + /* If anything below us is unlocked, we can't use 'bottom' protection */ 518 + if (!stm_is_locked_sr(nor, 0, ofs, status_old)) 519 + can_be_bottom = false; 520 + 536 521 /* If anything above us is unlocked, we can't use 'top' protection */ 537 522 if (!stm_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len), 538 523 status_old)) 524 + can_be_top = false; 525 + 526 + if (!can_be_bottom && !can_be_top) 539 527 return -EINVAL; 540 528 529 + /* Prefer top, if both are valid */ 530 + use_top = can_be_top; 531 + 541 532 /* lock_len: length of region that should end up locked */ 542 - lock_len = mtd->size - ofs; 533 + if (use_top) 534 + lock_len = mtd->size - ofs; 535 + else 536 + lock_len = ofs + len; 543 537 544 538 /* 545 539 * Need smallest pow such that: ··· 571 539 if (!(val & mask)) 572 540 return -EINVAL; 573 541 574 - status_new = (status_old & ~mask) | val; 542 + status_new = (status_old & ~mask & ~SR_TB) | val; 575 543 576 544 /* Disallow further writes if WP pin is asserted */ 577 545 status_new |= SR_SRWD; 546 + 547 + if (!use_top) 548 + status_new |= SR_TB; 578 549 579 550 /* Don't bother if they're the same */ 580 551 if (status_new == status_old) ··· 606 571 u8 mask = SR_BP2 | SR_BP1 | SR_BP0; 607 572 u8 shift = ffs(mask) - 1, pow, val; 608 573 loff_t lock_len; 574 + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; 575 + bool use_top; 609 576 int ret; 610 577 611 578 status_old = read_sr(nor); ··· 620 583 621 584 /* If anything below us is locked, we can't use 'top' protection */ 622 585 if (!stm_is_unlocked_sr(nor, 0, ofs, status_old)) 586 + can_be_top = false; 587 + 588 + /* If anything above us is locked, we can't use 'bottom' protection */ 589 + if (!stm_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len), 590 + status_old)) 591 + can_be_bottom = false; 592 + 593 + if (!can_be_bottom && !can_be_top) 623 594 return -EINVAL; 624 595 596 + /* Prefer top, if both are valid */ 597 + use_top = can_be_top; 598 + 625 599 /* lock_len: length of region that should remain locked */ 626 - lock_len = mtd->size - (ofs + len); 600 + if (use_top) 601 + lock_len = mtd->size - (ofs + len); 602 + else 603 + lock_len = ofs; 627 604 628 605 /* 629 606 * Need largest pow such that: ··· 658 607 return -EINVAL; 659 608 } 660 609 661 - status_new = (status_old & ~mask) | val; 610 + status_new = (status_old & ~mask & ~SR_TB) | val; 662 611 663 612 /* Don't protect status register if we're fully unlocked */ 664 613 if (lock_len == mtd->size) 665 614 status_new &= ~SR_SRWD; 615 + 616 + if (!use_top) 617 + status_new |= SR_TB; 666 618 667 619 /* Don't bother if they're the same */ 668 620 if (status_new == status_old) ··· 1331 1277 1332 1278 if (info->flags & USE_FSR) 1333 1279 nor->flags |= SNOR_F_USE_FSR; 1280 + if (info->flags & SPI_NOR_HAS_TB) 1281 + nor->flags |= SNOR_F_HAS_SR_TB; 1334 1282 1335 1283 #ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS 1336 1284 /* prefer "small sector" erase if possible */
+2
include/linux/mtd/spi-nor.h
··· 85 85 #define SR_BP0 BIT(2) /* Block protect 0 */ 86 86 #define SR_BP1 BIT(3) /* Block protect 1 */ 87 87 #define SR_BP2 BIT(4) /* Block protect 2 */ 88 + #define SR_TB BIT(5) /* Top/Bottom protect */ 88 89 #define SR_SRWD BIT(7) /* SR write protect */ 89 90 90 91 #define SR_QUAD_EN_MX BIT(6) /* Macronix Quad I/O */ ··· 117 116 118 117 enum spi_nor_option_flags { 119 118 SNOR_F_USE_FSR = BIT(0), 119 + SNOR_F_HAS_SR_TB = BIT(1), 120 120 }; 121 121 122 122 /**