summaryrefslogtreecommitdiffstats
path: root/drivers/mtd
diff options
context:
space:
mode:
authorBrian Norris <computersforpeace@gmail.com>2016-01-29 20:25:36 +0100
committerBrian Norris <computersforpeace@gmail.com>2016-03-08 03:01:57 +0100
commit3dd8012a8eeb3702fa17450ec1a16a3f38af138d (patch)
tree699d4370d9e17fd294f695f7942debd3684c7a5c /drivers/mtd
parentmtd: spi-nor: add SPI_NOR_HAS_LOCK flag (diff)
downloadlinux-3dd8012a8eeb3702fa17450ec1a16a3f38af138d.tar.xz
linux-3dd8012a8eeb3702fa17450ec1a16a3f38af138d.zip
mtd: spi-nor: add TB (Top/Bottom) protect support
Some flash support a bit in the status register that inverts protection so that it applies to the bottom of the flash, not the top. This yields additions to the protection range table, as noted in the comments. Because this feature is not universal to all flash that support lock/unlock, control it via a new flag. Signed-off-by: Brian Norris <computersforpeace@gmail.com> Tested-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/spi-nor/spi-nor.c70
1 files changed, 63 insertions, 7 deletions
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index d1edafc51cbb..2aebf18f7c36 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -70,6 +70,11 @@ struct flash_info {
#define SPI_NOR_QUAD_READ BIT(6) /* Flash supports Quad Read */
#define USE_FSR BIT(7) /* use flag status register */
#define SPI_NOR_HAS_LOCK BIT(8) /* Flash supports lock/unlock via SR */
+#define SPI_NOR_HAS_TB BIT(9) /*
+ * Flash SR has Top/Bottom (TB) protect
+ * bit. Must be used with
+ * SPI_NOR_HAS_LOCK.
+ */
};
#define JEDEC_MFR(info) ((info)->id[0])
@@ -435,7 +440,10 @@ static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs,
} else {
pow = ((sr & mask) ^ mask) >> shift;
*len = mtd->size >> pow;
- *ofs = mtd->size - *len;
+ if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB)
+ *ofs = 0;
+ else
+ *ofs = mtd->size - *len;
}
}
@@ -476,12 +484,14 @@ static int stm_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
/*
* 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
+ * Supports 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)
*
+ * Support for the following is provided conditionally for some flash:
+ * - TB: top/bottom protect
+ *
* Sample table portion for 8MB flash (Winbond w25q64fw):
*
* SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion
@@ -494,6 +504,13 @@ static int stm_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
* 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
+ * ------|-------|-------|-------|-------|---------------|-------------------
+ * 0 | 1 | 0 | 0 | 1 | 128 KB | Lower 1/64
+ * 0 | 1 | 0 | 1 | 0 | 256 KB | Lower 1/32
+ * 0 | 1 | 0 | 1 | 1 | 512 KB | Lower 1/16
+ * 0 | 1 | 1 | 0 | 0 | 1 MB | Lower 1/8
+ * 0 | 1 | 1 | 0 | 1 | 2 MB | Lower 1/4
+ * 0 | 1 | 1 | 1 | 0 | 4 MB | Lower 1/2
*
* Returns negative on errors, 0 on success.
*/
@@ -504,6 +521,8 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 shift = ffs(mask) - 1, pow, val;
loff_t lock_len;
+ bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
+ bool use_top;
int ret;
status_old = read_sr(nor);
@@ -514,13 +533,26 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
if (stm_is_locked_sr(nor, ofs, len, status_old))
return 0;
+ /* If anything below us is unlocked, we can't use 'bottom' protection */
+ if (!stm_is_locked_sr(nor, 0, ofs, status_old))
+ can_be_bottom = false;
+
/* If anything above us is unlocked, we can't use 'top' protection */
if (!stm_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len),
status_old))
+ can_be_top = false;
+
+ if (!can_be_bottom && !can_be_top)
return -EINVAL;
+ /* Prefer top, if both are valid */
+ use_top = can_be_top;
+
/* lock_len: length of region that should end up locked */
- lock_len = mtd->size - ofs;
+ if (use_top)
+ lock_len = mtd->size - ofs;
+ else
+ lock_len = ofs + len;
/*
* Need smallest pow such that:
@@ -539,11 +571,14 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
if (!(val & mask))
return -EINVAL;
- status_new = (status_old & ~mask) | val;
+ status_new = (status_old & ~mask & ~SR_TB) | val;
/* Disallow further writes if WP pin is asserted */
status_new |= SR_SRWD;
+ if (!use_top)
+ status_new |= SR_TB;
+
/* Don't bother if they're the same */
if (status_new == status_old)
return 0;
@@ -571,6 +606,8 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 shift = ffs(mask) - 1, pow, val;
loff_t lock_len;
+ bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
+ bool use_top;
int ret;
status_old = read_sr(nor);
@@ -583,10 +620,24 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
/* If anything below us is locked, we can't use 'top' protection */
if (!stm_is_unlocked_sr(nor, 0, ofs, status_old))
+ can_be_top = false;
+
+ /* If anything above us is locked, we can't use 'bottom' protection */
+ if (!stm_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len),
+ status_old))
+ can_be_bottom = false;
+
+ if (!can_be_bottom && !can_be_top)
return -EINVAL;
+ /* Prefer top, if both are valid */
+ use_top = can_be_top;
+
/* lock_len: length of region that should remain locked */
- lock_len = mtd->size - (ofs + len);
+ if (use_top)
+ lock_len = mtd->size - (ofs + len);
+ else
+ lock_len = ofs;
/*
* Need largest pow such that:
@@ -607,12 +658,15 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
return -EINVAL;
}
- status_new = (status_old & ~mask) | val;
+ status_new = (status_old & ~mask & ~SR_TB) | val;
/* Don't protect status register if we're fully unlocked */
if (lock_len == mtd->size)
status_new &= ~SR_SRWD;
+ if (!use_top)
+ status_new |= SR_TB;
+
/* Don't bother if they're the same */
if (status_new == status_old)
return 0;
@@ -1277,6 +1331,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
if (info->flags & USE_FSR)
nor->flags |= SNOR_F_USE_FSR;
+ if (info->flags & SPI_NOR_HAS_TB)
+ nor->flags |= SNOR_F_HAS_SR_TB;
#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
/* prefer "small sector" erase if possible */