[PATCH v2 29/30] mtd: spi-nor: Add support for locking on GIGADEVICE nor flashes
Tejas Bhumkar
tejas.arvind.bhumkar at amd.com
Wed Dec 6 10:31:40 CET 2023
From: Venkatesh Yadav Abbarapu <venkatesh.abbarapu at amd.com>
GIGADEVICE nor flashes provide block protection support using
BP0, BP1, BP2, BP3 & TB bits in status register.
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.
Used bottom/top protect to test lock/unlock on zc1751+dc1 board.
Signed-off-by: Venkatesh Yadav Abbarapu <venkatesh.abbarapu at amd.com>
Signed-off-by: Tejas Bhumkar <tejas.arvind.bhumkar at amd.com>
---
drivers/mtd/spi/spi-nor-core.c | 309 +++++++++++++++++++++++++++++++++
include/linux/mtd/spi-nor.h | 2 +
2 files changed, 311 insertions(+)
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index 3bf1e5471b..3e2491cc3e 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -5164,6 +5164,307 @@ static int issi_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
return ret;
}
#endif /* CONFIG_SPI_FLASH_ISSI */
+
+#if defined(CONFIG_SPI_FLASH_GIGADEVICE)
+static void giga_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs,
+ uint64_t *len)
+{
+ struct mtd_info *mtd = &nor->mtd;
+ int shift = 0;
+ int pow;
+ u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_GIGA;
+ u32 sector_size;
+
+ sector_size = nor->sector_size;
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ sector_size >>= 1;
+
+ shift = ffs(mask) - 1;
+
+ if (!(sr & mask)) {
+ /* No protection */
+ *ofs = 0;
+ *len = 0;
+ } else {
+ pow = ((sr & mask) >> shift) - 1;
+ *len = sector_size << pow;
+ if (*len > mtd->size)
+ *len = mtd->size;
+ /* GIGA device's have top/bottom select bit in status reg */
+ if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB_GIGA)
+ *ofs = 0;
+ else
+ *ofs = mtd->size - *len;
+ }
+}
+
+/**
+ * giga_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 giga_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;
+
+ giga_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);
+}
+
+/**
+ * giga_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 giga_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
+ u8 sr)
+{
+ return giga_check_lock_status_sr(nor, ofs, len, sr, true);
+}
+
+/**
+ * giga_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 giga_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
+ u8 sr)
+{
+ return giga_check_lock_status_sr(nor, ofs, len, sr, false);
+}
+
+/**
+ * giga_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 giga_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+ int sr;
+
+ sr = read_sr(nor);
+ if (sr < 0)
+ return sr;
+
+ return giga_check_lock_status_sr(nor, ofs, len, sr, false);
+}
+
+/**
+ * giga_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 giga_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 (giga_is_locked_sr(nor, ofs, len, sr))
+ return 0;
+
+ /*
+ * If anything below us is unlocked, we can't use 'bottom'
+ * protection.
+ */
+ if (!giga_is_locked_sr(nor, 0, ofs, sr))
+ can_be_bottom = false;
+
+ /*
+ * If anything above us is unlocked, we can't use 'top'
+ * protection.
+ */
+ if (!giga_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 (giga_is_unlocked_sr(nor, ofs, len, sr))
+ return 0;
+
+ /*
+ * If anything below us is locked, we can't use 'top'
+ * protection
+ */
+ if (!giga_is_unlocked_sr(nor, 0, ofs, sr))
+ can_be_top = false;
+
+ /*
+ * If anything above us is locked, we can't use 'bottom'
+ * protection
+ */
+ if (!giga_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;
+}
+
+/**
+ * giga_flash_lock() - set BP[0123] 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,3} in status register
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int giga_flash_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+ int status_old, status_new, blk_prot;
+ loff_t lock_len;
+ u8 pow, ret, shift;
+ bool use_top = false;
+ u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_GIGA;
+
+ 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;
+ }
+
+ log_debug("SPI Protection: %s\n", (status_old & SR_TB_GIGA) ? "bottom" : "top");
+
+ ret = giga_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;
+
+ /* lock_len: length of region that should end up locked */
+ if (use_top)
+ lock_len = nor->mtd.size - ofs;
+ else
+ lock_len = ofs + len;
+
+ pow = order_base_2(lock_len);
+ blk_prot = mask & (((pow + 1) & 0xf) << shift);
+ if (lock_len <= 0) {
+ dev_err(nor->dev, "invalid Length to protect");
+ return -EINVAL;
+ }
+
+ status_new = status_old | blk_prot;
+ if (!use_top)
+ status_new |= SR_TB_GIGA;
+ else
+ status_new &= ~SR_TB_GIGA;
+
+ if (status_old == status_new)
+ return 0;
+
+ return write_sr_and_check(nor, status_new, mask);
+}
+
+/**
+ * giga_flash_unlock() - clear BP[0123] write-protection.
+ * @nor: pointer to a 'struct spi_nor'.
+ * @ofs: offset from which to unlock memory.
+ * @len: number of bytes to unlock.
+ *
+ * Bits [2345] of the Status Register are BP[0123].
+ * Clear the corresponding BP status bits.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int giga_flash_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+ int ret, val;
+ u8 mask = SR_BP0 | SR_BP1 | SR_BP2 | SR_BP3_GIGA;
+
+ 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;
+
+ ret = read_sr(nor);
+ if (ret > 0 && !(ret & mask)) {
+ dev_info(nor->dev, "block protect bits cleared SR: 0x%x\n",
+ ret);
+ ret = 0;
+ }
+ return ret;
+}
+#endif /* CONFIG_SPI_FLASH_GIGADEVICE */
#endif /* CONFIG_SPI_FLASH_LOCK */
#ifdef CONFIG_SPI_FLASH_SOFT_RESET
@@ -5399,6 +5700,14 @@ int spi_nor_scan(struct spi_nor *nor)
}
#endif
+#if defined(CONFIG_SPI_FLASH_GIGADEVICE)
+ if (JEDEC_MFR(info) == SNOR_MFR_GIGADEVICE) {
+ nor->flash_lock = giga_flash_lock;
+ nor->flash_unlock = giga_flash_unlock;
+ nor->flash_is_unlocked = giga_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 9a560d94a2..797f0f275a 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -172,6 +172,8 @@
#define SR_BP3 BIT(6) /* Block protect 3 */
#define SR_BP3_MX BIT(5) /* Block protect 3 (Macronix) */
#define SR_BP3_ISSI BIT(5) /* Block protect 3 (ISSI) */
+#define SR_TB_GIGA BIT(6) /* Top/Bottom protect (GIGADEVICE)*/
+#define SR_BP3_GIGA BIT(5) /* Block protect 3 (GIGADEVICE) */
#define SR_TB BIT(5) /* Top/Bottom protect */
#define SR_SRWD BIT(7) /* SR write protect */
--
2.27.0
More information about the U-Boot
mailing list