|
@@ -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 */
|