
From: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
Spansion nor flashes provide block protection support using BP0, BP1, BP2 bits in status register.
The top/bottom select is instead done via a bit in the configuration register, which is OTP, so once set to use bottom protect, one cannot use top. On top of that, reading the configuration register uses a different opcode (0x15) than the existing SPINOR_OP_RDCR (0x35).
Used bottom protect to test s25fl512s flash part lock/unlock on zc1751+dc1 board.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com Signed-off-by: Tejas Bhumkar tejas.arvind.bhumkar@amd.com --- drivers/mtd/spi/spi-nor-core.c | 372 +++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 1 + 2 files changed, 373 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 3e2491cc3e..42483caaaa 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -5465,6 +5465,370 @@ static int giga_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) return ret; } #endif /* CONFIG_SPI_FLASH_GIGADEVICE */ + +#if defined(CONFIG_SPI_FLASH_SPANSION) +/** + * spansion_read_cr() - read configuration register + * @nor: pointer to a 'struct spi_nor'. + * + * Spansion devices have top/bottom area protection bits selection into + * configuration reg. The bits in CR are OTP. So once it's written, + * it cannot be changed. + * + * Return: Value in configuration register or negative if error. + */ +static int spansion_read_cr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDCR, &val, 1); + if (ret < 0) { + dev_dbg(nor->dev, "error %d reading CR\n", ret); + return ret; + } + + return val; +} + +static void spansion_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, + uint64_t *len) +{ + struct mtd_info *mtd = &nor->mtd; + int pow, cr; + int shift = 0; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + + shift = ffs(mask) - 1; + + cr = spansion_read_cr(nor); + if (!(sr & mask)) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + pow = ((sr & mask) ^ mask) >> shift; + *len = mtd->size >> pow; + /* SPANSION device's have top/bottom select bit in configuration reg */ + if (nor->flags & SNOR_F_HAS_SR_TB && cr & CR_TB_SPAN) + *ofs = 0; + else + *ofs = mtd->size - *len; + } +} + +/** + * spansion_check_lock_status_sr() - check the status register and return + * the region is locked or unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * @locked: locked:1 unlocked:0 value + * + * Return: 1 if the entire region is locked (if @locked is true) or unlocked (if + * @locked is false); 0 otherwise. + */ +static int spansion_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, u64 len, + u8 sr, bool locked) +{ + loff_t lock_offs; + u64 lock_len; + + if (!len) + return 1; + + spansion_get_locked_range(nor, sr, &lock_offs, &lock_len); + if (locked) + /* Requested range is a sub-range of locked range */ + return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs); + + /* Requested range does not overlap with locked range */ + return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); +} + +/** + * spansion_is_locked_sr() - check if the memory region is locked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is locked. + * + * Return: false if region is locked 0 otherwise. + */ +static int spansion_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return spansion_check_lock_status_sr(nor, ofs, len, sr, true); +} + +/** + * spansion_is_unlocked_sr() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * @sr: status register + * + * Check if memory region is unlocked. + * + * Return: false if region is locked 0 otherwise. + */ +static int spansion_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr) +{ + return spansion_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * spansion_is_unlocked() - check if the memory region is unlocked + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset of the flash + * @len: length to be locked + * + * check if memory region is unlocked + * + * Return: false if region is locked 0 otherwise. + */ +static int spansion_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int sr; + + sr = read_sr(nor); + if (sr < 0) + return sr; + + return spansion_check_lock_status_sr(nor, ofs, len, sr, false); +} + +/** + * spansion_nor_select_zone() - Select top area or bottom area to lock/unlock + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * @sr: status register + * @tb: pointer to top/bottom bool used in caller function + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock + * + * Select the top area / bottom area pattern to protect memory blocks. + * + * Return: negative on errors, 0 on success. + */ +static int spansion_nor_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, + u8 sr, bool *tb, bool op) +{ + int retval = 1; + bool can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, can_be_top = true; + + if (op) { + /* Select for lock zone operation */ + + /* + * If nothing in our range is unlocked, we don't need + * to do anything. + */ + if (spansion_is_locked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is unlocked, we can't use 'bottom' + * protection. + */ + if (!spansion_is_locked_sr(nor, 0, ofs, sr)) + can_be_bottom = false; + + /* + * If anything above us is unlocked, we can't use 'top' + * protection. + */ + if (!spansion_is_locked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_top = false; + } else { + /* Select unlock zone */ + + /* + * If nothing in our range is locked, we don't need to + * do anything. + */ + if (spansion_is_unlocked_sr(nor, ofs, len, sr)) + return 0; + + /* + * If anything below us is locked, we can't use 'top' + * protection + */ + if (!spansion_is_unlocked_sr(nor, 0, ofs, sr)) + can_be_top = false; + + /* + * If anything above us is locked, we can't use 'bottom' + * protection + */ + if (!spansion_is_unlocked_sr(nor, ofs + len, + nor->mtd.size - (ofs + len), sr)) + can_be_bottom = false; + } + + if (!can_be_bottom && !can_be_top) + return -EINVAL; + + /* Prefer top, if both are valid */ + *tb = can_be_top; + return retval; +} + +static int spansion_write_sr_cr(struct spi_nor *nor, u8 *sr_cr) +{ + int ret; + + write_enable(nor); + + ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2); + if (ret < 0) { + dev_dbg(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + ret = spi_nor_wait_till_ready(nor); + if (ret) { + dev_dbg(nor->dev, + "timeout while writing configuration register\n"); + return ret; + } + + ret = write_disable(nor); + if (ret) + return ret; + + return 0; +} + +/** + * spansion_flash_lock() - set BP[012] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to lock memory. + * @len: number of bytes to unlock. + * + * Lock a region of the flash.Implementation is based on stm_lock + * Supports the block protection bits BP{0,1,2} in status register + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + u64 lock_len; + u32 sector_size; + u16 n_sectors; + unsigned int bp_slots, bp_slots_needed; + int cr, can_be_top, val, status_old; + u8 pow, ret, shift, sr_cr[2]; + bool use_top = false; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + + shift = ffs(mask) - 1; + + status_old = read_sr(nor); + /* if status reg is Write protected don't update bit protection */ + if (status_old & SR_SRWD) { + dev_err(nor->dev, + "SR is write protected, can't update BP bits...\n"); + return -EINVAL; + } + + cr = spansion_read_cr(nor); + if (cr < 0) + return cr; + + log_debug("SPI Protection: %s\n", (cr & CR_TB_SPAN) ? "bottom" : "top"); + + /* CR_TB is OTP, so we can't use 'top' protection if that is already set. */ + can_be_top = !(cr & CR_TB_SPAN); + + ret = spansion_nor_select_zone(nor, ofs, len, status_old, &use_top, 1); + /* Older protected blocks include the new requested block's */ + if (ret <= 0) + return ret; + + use_top = can_be_top; + + /* lock_len: length of region that should end up locked */ + if (use_top) + lock_len = nor->mtd.size - ofs; + else + lock_len = ofs + len; + + sector_size = nor->sector_size; + n_sectors = (nor->size) / sector_size; + + bp_slots = (1 << hweight8(mask)) - 2; + bp_slots_needed = ilog2(n_sectors); + + if (bp_slots_needed > bp_slots) + sector_size <<= (bp_slots_needed - bp_slots); + + pow = ilog2(lock_len) - ilog2(sector_size) + 1; + val = pow << shift; + sr_cr[0] = status_old & ~mask; + sr_cr[0] |= val; + + sr_cr[1] = cr | (use_top ? 0 : CR_TB_SPAN); + ret = spansion_write_sr_cr(nor, sr_cr); + if (ret) + return ret; + + /* Check that the bits got written as expected */ + ret = read_sr(nor); + if (ret < 0) + return ret; + + ret = spansion_read_cr(nor); + if (ret < 0) + return ret; + + return 0; +} + +/** + * spansion_flash_unlock() - clear BP[012] write-protection. + * @nor: pointer to a 'struct spi_nor'. + * @ofs: offset from which to unlock memory. + * @len: number of bytes to unlock. + * + * Bits [234] of the Status Register are BP[012]. + * Clear the corresponding BP status bits. + * + * Return: 0 on success, -errno otherwise. + */ +static int spansion_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) +{ + int ret, val; + u8 mask = SR_BP0 | SR_BP1 | SR_BP2; + + val = read_sr(nor); + if (val < 0) + return val; + + if (!(val & mask)) + return 0; + + write_enable(nor); + + write_sr(nor, val & ~mask); + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = write_disable(nor); + if (ret) + return ret; + + return 0; +} +#endif /* CONFIG_SPI_FLASH_SPANSION */ #endif /* CONFIG_SPI_FLASH_LOCK */
#ifdef CONFIG_SPI_FLASH_SOFT_RESET @@ -5708,6 +6072,14 @@ int spi_nor_scan(struct spi_nor *nor) } #endif
+#if defined(CONFIG_SPI_FLASH_SPANSION) + if (JEDEC_MFR(info) == SNOR_MFR_SPANSION) { + nor->flash_lock = spansion_flash_lock; + nor->flash_unlock = spansion_flash_unlock; + nor->flash_is_unlocked = spansion_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 797f0f275a..07d6c4ff93 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -203,6 +203,7 @@ /* Configuration Register bits. */ #define CR_QUAD_EN_SPAN BIT(1) /* Spansion Quad I/O */ #define CR_TB_MX BIT(3) /* Macronix Top/Bottom protect */ +#define CR_TB_SPAN BIT(5) /* Spansion Top/Bottom protect */
/* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT7 BIT(7)