From: T Karthik Reddy <t.karthik.re...@xilinx.com> Micron nor flashes provide block protection support using BP0, BP1, BP2, BP3 & TB bits in status register. This patch supports for micron nor flashes with manufacturer id 0x20 and 0x2c.
Where BP(Block Protection) bits defines memory to be software protected against PROGRAM or ERASE operations. When one or more block protect bits are set to 1, a designated memory area is protected from PROGRAM and ERASE operations. TB(Top/Bottom) bit determines whether the protected memory area defined by the block protect bits starts from the top or bottom of the memory array. Block Protection table for N25Q00AA with size 128MB, sector size 64KB and with 2048 sectors. Top protection: -------------- TB BP3 BP2 BP1 BP0 Protected Area Un-Protected Area 0 0 0 0 0 None All sectors 0 0 0 0 1 Sector 2047 Sectors (0 to 2046) 0 0 0 1 0 Sectors (2046 to 2047) Sectors (0 to 2045) 0 0 0 1 1 Sectors (2044 to 2047) Sectors (0 to 2043) 0 0 1 0 0 Sectors (2040 to 2047) Sectors (0 to 2039) 0 0 1 0 1 Sectors (2032 to 2047) Sectors (0 to 2031) 0 0 1 1 0 Sectors (2016 to 2047) Sectors (0 to 2015) 0 0 1 1 1 Sectors (1984 to 2047) Sectors (0 to 1983) 0 1 0 0 0 Sectors (1920 to 2047) Sectors (0 to 1919) 0 1 0 0 1 Sectors (1792 to 2047) Sectors (0 to 1791) 0 1 0 1 0 Sectors (1936 to 2047) Sectors (0 to 1535) 0 1 0 1 1 Sectors (1024 to 2047) None Bottom protection: ----------------- TB BP3 BP2 BP1 BP0 Protected Area Un-protected Area 1 0 0 0 0 None All sectors 1 0 0 0 1 sector 0 Sectors (1 to 2047) 1 0 0 1 0 Sectors (0 to 1) Sectors (2 to 2047) 1 0 0 1 1 Sectors (0 to 3) Sectors (4 to 2047) 1 0 1 0 0 Sectors (0 to 7) Sectors (8 to 2047) 1 0 1 0 1 Sectors (0 to 15) Sectors (16 to 2047) 1 0 1 1 0 Sectors (0 to 31) Sectors (32 to 2047) 1 0 1 1 1 Sectors (0 to 63) Sectors (64 to 2047) 1 1 0 0 0 Sectors (0 to 127) Sectors (128 to 2047) 1 1 0 0 1 Sectors (0 to 255) Sectors (256 to 2047) 1 1 0 1 0 Sectors (0 to 511) Sectors (512 to 2047) 1 1 0 1 1 Sectors (0 to 1023) Sectors (1024 to 2047) Signed-off-by: Siva Durga Prasad Paladugu <siva.durga.palad...@xilinx.com> Signed-off-by: Ashok Reddy Soma <ashok.reddy.s...@xilinx.com> Signed-off-by: T Karthik Reddy <t.karthik.re...@xilinx.com> Signed-off-by: Venkatesh Yadav Abbarapu <venkatesh.abbar...@amd.com> Signed-off-by: Tejas Bhumkar <tejas.arvind.bhum...@amd.com> --- drivers/mtd/spi/spi-nor-core.c | 255 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 7 + 2 files changed, 262 insertions(+) diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index ccda722df5..c40899a281 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -4336,6 +4336,253 @@ static int spi_nor_init(struct spi_nor *nor) return 0; } +#if defined(CONFIG_SPI_FLASH_LOCK) +#if defined(CONFIG_SPI_FLASH_STMICRO) +static inline uint16_t min_lockable_sectors(struct spi_nor *nor, + uint16_t n_sectors) +{ + /* lock granularity */ + return 1; +} + +static inline uint32_t get_protected_area_start(struct spi_nor *nor, + u8 lock_bits, + bool is_bottom) +{ + u16 n_sectors; + u32 sector_size, flash_size; + int ret; + + sector_size = nor->sector_size; + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + n_sectors = (nor->size >> 0x01) / sector_size; + flash_size = nor->size >> 0x01; + } else { + n_sectors = nor->size / sector_size; + flash_size = nor->size; + } + + if (!is_bottom) + ret = flash_size - ((1 << (lock_bits - 1)) * sector_size * + min_lockable_sectors(nor, n_sectors)); + else + ret = (1 << (lock_bits - 1)) * sector_size * + min_lockable_sectors(nor, n_sectors); + + return ret; +} + +static uint8_t min_protected_area_including_offset(struct spi_nor *nor, + u32 offset, + bool is_bottom) +{ + u8 lock_bits, lockbits_limit; + + lockbits_limit = MAX_LOCKBITS; + + for (lock_bits = 1; lock_bits < lockbits_limit; lock_bits++) { + if (!is_bottom) { + /* top protection */ + if (offset >= get_protected_area_start(nor, + lock_bits, + is_bottom)) + break; + } else { + /* bottom protection */ + if (offset <= get_protected_area_start(nor, + lock_bits, + is_bottom)) + break; + } + } + return lock_bits; +} + +static int write_sr_modify_protection(struct spi_nor *nor, u8 status, + u8 lock_bits, bool is_bottom) +{ + u8 status_new, bp_mask; + int ret; + + status_new = status & ~BP_MASK; + bp_mask = (lock_bits << BP_SHIFT) & BP_MASK; + + status_new &= ~SR_BP3; + /* Protected area starts from top */ + status_new &= ~SR_TB; + + /* If bottom area is to be Protected set SR_TB */ + if (is_bottom) + status_new |= SR_TB; + + if (lock_bits > 7) + bp_mask |= SR_BP3; + + status_new |= bp_mask; + + write_enable(nor); + + nor->spi->flags |= SPI_XFER_LOWER; + + ret = write_sr(nor, status_new); + if (ret) + return ret; + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + nor->spi->flags |= SPI_XFER_UPPER; + ret = write_sr(nor, status_new); + if (ret) + return ret; + } + + return write_disable(nor); +} + +static void micron_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + int shift = ffs(mask) - 1; + int pow; + u64 norsize = nor->size; + + if (nor->flags & SNOR_F_HAS_PARALLEL) + norsize /= 2; + + if (!(sr & (mask | SR_BP3))) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = (sr & mask) >> shift; + pow |= sr & SR_BP3 ? BIT(3) : 0; + + if (pow) + pow--; + + *len = nor->sector_size << pow; + if (*len >= norsize) + *len = norsize; + + if (!(sr & SR_TB)) + *ofs = norsize - *len; + else + *ofs = 0; + + debug("%s, ofs:0x%lx, len:0x%lx\n", __func__, + (unsigned long)*ofs, (unsigned long)*len); + } +} + +static int micron_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + loff_t lock_offs; + u64 lock_len; + int given_range, locked_range; + bool locked_value = false; + bool offset_value = false; + + if (nor->flags & SNOR_F_HAS_PARALLEL) + ofs /= 2; + + /* Avoid dividing by 2 of data length for size 1 */ + if (nor->flags & SNOR_F_HAS_PARALLEL && len != 1) + len /= 2; + + debug("%s, ofs:0x%lx, len:0x%lx\n", __func__, + (unsigned long)ofs, + (unsigned long)len); + + micron_get_locked_range(nor, sr, &lock_offs, &lock_len); + + given_range = ofs + len; + locked_range = lock_offs + lock_len; + + if (given_range <= locked_range) + locked_value = true; + + if (ofs >= lock_offs) + offset_value = true; + + if (sr & SR_TB) + return !locked_value; + + return !(locked_value && offset_value); +} + +static int micron_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + u8 status_old, status_old_up; + u8 lock_bits; + loff_t lock_len; + int ret = 0; + bool is_bottom = false; /* Use TOP protection by default */ + + if (nor->flags & SNOR_F_HAS_PARALLEL) + nor->spi->flags |= SPI_XFER_LOWER; + + status_old = read_sr(nor); + if (status_old < 0) + return status_old; + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + nor->spi->flags |= SPI_XFER_UPPER; + status_old_up = read_sr(nor); + if (status_old_up < 0) + return status_old_up; + if ((status_old & BPTB_MASK) != (status_old_up & BPTB_MASK)) { + printf("BP is different in both flashes lo:0x%x, up:0x%x\n", + status_old, status_old_up); + return -EINVAL; + } + } + + if (ofs < nor->size / 2) + is_bottom = true; /* Change it to bottom protection */ + + debug("Status in both flashes lo:0x%x, up:0x%x\n", + status_old, status_old_up); + + if (nor->flags & SNOR_F_HAS_PARALLEL) { + ofs /= 2; + len /= 2; + } + + if (!is_bottom) + lock_len = ofs; + else + lock_len = ofs + len; + + lock_bits = min_protected_area_including_offset(nor, lock_len, + is_bottom); + + if (lock_bits > ((status_old & (BP_MASK << BP_SHIFT)) >> 2)) + ret = write_sr_modify_protection(nor, status_old, lock_bits, + is_bottom); + + return ret; +} + +static int micron_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + return write_sr_modify_protection(nor, 0, 0, 0); +} + +static int micron_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int status; + + status = read_sr(nor); + if (status < 0) + return status; + + return micron_is_unlocked_sr(nor, ofs, len, status); +} +#endif /* CONFIG_SPI_FLASH_STMICRO */ +#endif /* CONFIG_SPI_FLASH_LOCK */ + #ifdef CONFIG_SPI_FLASH_SOFT_RESET /** * spi_nor_soft_reset() - perform the JEDEC Software Reset sequence @@ -4545,6 +4792,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif +#if defined(CONFIG_SPI_FLASH_STMICRO) + if (JEDEC_MFR(info) == SNOR_MFR_ST || JEDEC_MFR(info) == SNOR_MFR_MICRON) { + nor->flash_lock = micron_flash_lock; + nor->flash_unlock = micron_flash_unlock; + nor->flash_is_unlocked = micron_is_unlocked; + } +#endif + #ifdef CONFIG_SPI_FLASH_SST /* * sst26 series block protection implementation differs from other diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 34e0aedc24..03413063ae 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -166,8 +166,15 @@ #define SR_BP0 BIT(2) /* Block protect 0 */ #define SR_BP1 BIT(3) /* Block protect 1 */ #define SR_BP2 BIT(4) /* Block protect 2 */ +#define SR_BP3 BIT(6) /* Block protect 3 */ #define SR_TB BIT(5) /* Top/Bottom protect */ #define SR_SRWD BIT(7) /* SR write protect */ + +#define BPTB_MASK 0x7C /* BP & TB bits mask */ +#define BP_MASK 0x1C /* BP bits mask */ +#define BP_SHIFT 0x2 /* BP shift*/ +#define MAX_LOCKBITS 15 + /* Spansion/Cypress specific status bits */ #define SR_E_ERR BIT(5) #define SR_P_ERR BIT(6) -- 2.27.0