[U-Boot] [PATCH v10 05/27] mtd: spi-nor: sync/modify lock operations

Jagan Teki
2017-12-28T06:12:11Z

sync the lock operations from legacy spi_flash.c and
update them according to current spi-nor framework.

Signed-off-by: Suneel Garapati <suneelglinux@gmail.com>
Signed-off-by: Jagan Teki <jagan@amarulasolutions.com>
---
drivers/mtd/spi-nor/spi-nor.c | 218 ++++++++++++++++++++++++++++++++++++++++++
include/mtd.h | 3 +
2 files changed, 221 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index cfd21fb..8bf9e67 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -186,6 +186,7 @@ static const struct spi_nor_info *spi_nor_id(struct udevice *dev)
int spi_nor_merase(struct udevice *dev, struct erase_info *instr)
{
struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+ const struct mtd_ops *mops = mtd_get_ops(mtd->dev);
int devnum = mtd->devnum;
struct spi_nor *nor;
const struct spi_nor_ops *ops;
@@ -205,6 +206,14 @@ int spi_nor_merase(struct udevice *dev, struct erase_info *instr)
addr = instr->addr;
len = instr->len;

+ if (mops->is_locked) {
+ ret = mops->is_locked(mtd->dev, addr, len);
+ if (ret > 0) {
+ printf("spi-nor: offset 0x%x is locked, cannot be erased\n", addr);
+ return -EINVAL;
+ }
+ }
+
while (len) {
erase_addr = addr;

@@ -238,6 +247,7 @@ static int spi_nor_mwrite(struct udevice *dev, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+ const struct mtd_ops *mops = mtd_get_ops(mtd->dev);
int devnum = mtd->devnum;
struct spi_nor *nor;
const struct spi_nor_ops *ops;
@@ -253,6 +263,14 @@ static int spi_nor_mwrite(struct udevice *dev, loff_t to, size_t len,

page_size = mtd->writebufsize;

+ if (mops->is_locked) {
+ ret = mops->is_locked(mtd->dev, to, len);
+ if (ret > 0) {
+ printf("spi-nor: offset 0x%llx is locked, cannot be written\n", to);
+ return -EINVAL;
+ }
+ }
+
for (actual = 0; actual < len; actual += chunk_len) {
addr = to;

@@ -423,6 +441,197 @@ static int sst_mwrite_bp(struct udevice *dev, loff_t to, size_t len,
}
#endif

+#if defined(CONFIG_SPI_NOR_STMICRO) || defined(CONFIG_SPI_NOR_SST)
+static void stm_get_locked_range(struct mtd_info *mtd, u8 sr, loff_t *ofs,
+ u64 *len)
+{
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+ int shift = ffs(mask) - 1;
+ int pow;
+
+ if (!(sr & mask)) {
+ /* No protection */
+ *ofs = 0;
+ *len = 0;
+ } else {
+ pow = ((sr & mask) ^ mask) >> shift;
+ *len = mtd->size >> pow;
+ *ofs = mtd->size - *len;
+ }
+}
+
+/* Return 1 if the entire region is locked, 0 otherwise */
+static int stm_is_locked_sr(struct mtd_info *mtd, loff_t ofs, u64 len,
+ u8 sr)
+{
+ loff_t lock_offs;
+ u64 lock_len;
+
+ stm_get_locked_range(mtd, sr, &lock_offs, &lock_len);
+
+ return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs);
+}
+
+/*
+ * Check if a region of the flash is (completely) locked. See stm_lock() for
+ * more info.
+ *
+ * Returns 1 if entire region is locked, 0 if any portion is unlocked, and
+ * negative on errors.
+ */
+static int stm_is_locked(struct udevice *dev, loff_t ofs, uint64_t len)
+{
+ struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+ int devnum = mtd->devnum;
+ struct spi_nor *nor;
+ int status;
+
+ nor = find_spi_nor_device(devnum);
+ if (!nor)
+ return -ENODEV;
+
+ status = read_sr(nor->dev);
+ if (status < 0)
+ return status;
+
+ return stm_is_locked_sr(mtd, ofs, len, status);
+}
+
+/*
+ * Lock a region of the flash. Compatible with ST Micro and similar flash.
+ * Supports only the block protection bits BP{0,1,2} in the status register
+ * (SR). Does not support these features found in newer SR bitfields:
+ * - TB: top/bottom protect - only handle TB=0 (top protect)
+ * - SEC: sector/block protect - only handle SEC=0 (block protect)
+ * - CMP: complement protect - only support CMP=0 (range is not complemented)
+ *
+ * Sample table portion for 8MB flash (Winbond w25q64fw):
+ *
+ * SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion
+ * --------------------------------------------------------------------------
+ * X | X | 0 | 0 | 0 | NONE | NONE
+ * 0 | 0 | 0 | 0 | 1 | 128 KB | Upper 1/64
+ * 0 | 0 | 0 | 1 | 0 | 256 KB | Upper 1/32
+ * 0 | 0 | 0 | 1 | 1 | 512 KB | Upper 1/16
+ * 0 | 0 | 1 | 0 | 0 | 1 MB | Upper 1/8
+ * 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4
+ * 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2
+ * X | X | 1 | 1 | 1 | 8 MB | ALL
+ *
+ * Returns negative on errors, 0 on success.
+ */
+static int stm_lock(struct udevice *dev, loff_t ofs, uint64_t len)
+{
+ struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+ int devnum = mtd->devnum;
+ struct spi_nor *nor;
+ u8 status_old, status_new;
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+ u8 shift = ffs(mask) - 1, pow, val;
+
+ nor = find_spi_nor_device(devnum);
+ if (!nor)
+ return -ENODEV;
+
+ status_old = read_sr(nor->dev);
+ if (status_old < 0)
+ return status_old;
+
+ /* SPI NOR always locks to the end */
+ if (ofs + len != mtd->size) {
+ /* Does combined region extend to end? */
+ if (!stm_is_locked_sr(mtd, ofs + len, mtd->size - ofs - len,
+ status_old))
+ return -EINVAL;
+ len = mtd->size - ofs;
+ }
+
+ /*
+ * Need smallest pow such that:
+ *
+ * 1 / (2^pow) <= (len / size)
+ *
+ * so (assuming power-of-2 size) we do:
+ *
+ * pow = ceil(log2(size / len)) = log2(size) - floor(log2(len))
+ */
+ pow = ilog2(mtd->size) - ilog2(len);
+ val = mask - (pow << shift);
+ if (val & ~mask)
+ return -EINVAL;
+
+ /* Don't "lock" with no region! */
+ if (!(val & mask))
+ return -EINVAL;
+
+ status_new = (status_old & ~mask) | val;
+
+ /* Only modify protection if it will not unlock other areas */
+ if ((status_new & mask) <= (status_old & mask))
+ return -EINVAL;
+
+ write_sr(nor->dev, status_new);
+
+ return 0;
+}
+
+/*
+ * Unlock a region of the flash. See stm_lock() for more info
+ *
+ * Returns negative on errors, 0 on success.
+ */
+static int stm_unlock(struct udevice *dev, loff_t ofs, uint64_t len)
+{
+ struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+ int devnum = mtd->devnum;
+ struct spi_nor *nor;
+ uint8_t status_old, status_new;
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+ u8 shift = ffs(mask) - 1, pow, val;
+
+ nor = find_spi_nor_device(devnum);
+ if (!nor)
+ return -ENODEV;
+
+ status_old = read_sr(nor->dev);
+ if (status_old < 0)
+ return status_old;
+
+ /* Cannot unlock; would unlock larger region than requested */
+ if (stm_is_locked_sr(mtd, ofs - mtd->erasesize, mtd->erasesize,
+ status_old))
+ return -EINVAL;
+ /*
+ * Need largest pow such that:
+ *
+ * 1 / (2^pow) >= (len / size)
+ *
+ * so (assuming power-of-2 size) we do:
+ *
+ * pow = floor(log2(size / len)) = log2(size) - ceil(log2(len))
+ */
+ pow = ilog2(mtd->size) - order_base_2(mtd->size - (ofs + len));
+ if (ofs + len == mtd->size) {
+ val = 0; /* fully unlocked */
+ } else {
+ val = mask - (pow << shift);
+ /* Some power-of-two sizes are not supported */
+ if (val & ~mask)
+ return -EINVAL;
+ }
+
+ status_new = (status_old & ~mask) | val;
+
+ /* Only modify protection if it will not lock other areas */
+ if ((status_new & mask) >= (status_old & mask))
+ return -EINVAL;
+
+ write_sr(nor->dev, status_new);
+
+ return 0;
+}
+#endif
+
#ifdef CONFIG_SPI_NOR_MACRONIX
static int macronix_quad_enable(struct udevice *dev)
{
@@ -598,6 +807,15 @@ int spi_nor_scan(struct spi_nor *nor)
}
#endif

+#if defined(CONFIG_SPI_NOR_STMICRO) || defined(CONFIG_SPI_NOR_SST)
+ /* NOR protection support for STmicro/Micron chips and similar */
+ if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
+ JEDEC_MFR(info) == SNOR_MFR_SST) {
+ ops->lock = stm_lock;
+ ops->unlock = stm_unlock;
+ ops->is_locked = stm_is_locked;
+ }
+#endif
/* compute the flash size */
nor->page_size = info->page_size;
/*
diff --git a/include/mtd.h b/include/mtd.h
index 273b3a6..acb2256 100644
--- a/include/mtd.h
+++ b/include/mtd.h
@@ -26,6 +26,9 @@ struct mtd_ops {
size_t *retlen, u_char *buf);
int (*write)(struct udevice *dev, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
+ int (*lock) (struct udevice *dev, loff_t ofs, uint64_t len);
+ int (*unlock) (struct udevice *dev, loff_t ofs, uint64_t len);
+ int (*is_locked) (struct udevice *dev, loff_t ofs, uint64_t len);
};

/* Access the serial operations for a device */
--
2.7.4