From: Venkatesh Yadav Abbarapu <venkatesh.abbar...@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.abbar...@amd.com>
Signed-off-by: Tejas Bhumkar <tejas.arvind.bhum...@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)
-- 
2.27.0

Reply via email to