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

Signed-off-by: Suneel Garapati <suneelgli...@gmail.com>
Signed-off-by: Jagan Teki <ja...@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

_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
https://lists.denx.de/listinfo/u-boot

Reply via email to