[PATCH v2] mtd: m25p80: Calculate flash block protect bits based on number of sectors

Austin Boyle boyle.austin at gmail.com
Sun Apr 6 04:19:59 PDT 2014


This patch generalises the calculation of block protect bits based on the number
of sectors and implements the _is_locked function.

Existing calculation of block protect bits only works for devices with 64
sectors or more. This new logic is applicable to the STmicro devices:
m25p10, p20, p40, p80, p16, pe16, p32, p64, p128.
Note devices with >64 sectors only allow the protected region to be specified
to a resolution of 1/64th of the total size (such as m25p64).

New return codes for ioctl(MEMISLOCKED) have been added to uapi/mtd/mtd-abi.h
because the _is_locked function can query a region which is partially unlocked.

Added flag to m25p_ids table to indicate if flash protection is supported.

Added n_sectors and sector_size to m25p flash structure so it can be used in
block protect bit calculation.

From: Austin Boyle <boyle.austin at gmail.com>
Signed-off-by: Austin Boyle <boyle.austin at gmail.com>
---
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index ad19139..f632e41 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -77,8 +77,13 @@
 #define	SR_BP0			4	/* Block protect 0 */
 #define	SR_BP1			8	/* Block protect 1 */
 #define	SR_BP2			0x10	/* Block protect 2 */
+#define	SR_BP_BIT_OFFSET	2	/* Offset to Block protect 0 */
+#define	SR_BP_BIT_MASK		(SR_BP2 | SR_BP1 | SR_BP0)
 #define	SR_SRWD			0x80	/* SR write protect */
 
+/* Highest resolution of sector locking */
+#define M25P_MAX_LOCKABLE_SECTORS	64
+
 #define SR_QUAD_EN_MX           0x40    /* Macronix Quad I/O */
 
 /* Configuration Register bits. */
@@ -104,6 +109,8 @@ struct m25p {
 	struct mtd_info		mtd;
 	u16			page_size;
 	u16			addr_width;
+	u16			n_sectors;
+	u32			sector_size;
 	u8			erase_opcode;
 	u8			read_opcode;
 	u8			program_opcode;
@@ -736,11 +743,25 @@ time_out:
 	return ret;
 }
 
+static inline uint16_t min_lockable_sectors(uint16_t n_sectors)
+{
+	return max(1, n_sectors/M25P_MAX_LOCKABLE_SECTORS);
+}
+
+static inline uint32_t get_protected_area(struct m25p *flash,
+							uint8_t lock_bits)
+{
+	return (1<<(lock_bits-1)) * min_lockable_sectors(flash->n_sectors) *
+							flash->sector_size;
+}
+
 static int m25p80_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
 {
 	struct m25p *flash = mtd_to_m25p(mtd);
 	uint32_t offset = ofs;
 	uint8_t status_old, status_new;
+	uint8_t lock_bits;
+	uint32_t protected_area_start;
 	int res = 0;
 
 	mutex_lock(&flash->lock);
@@ -752,24 +773,18 @@ static int m25p80_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
 
 	status_old = read_sr(flash);
 
-	if (offset < flash->mtd.size-(flash->mtd.size/2))
-		status_new = status_old | SR_BP2 | SR_BP1 | SR_BP0;
-	else if (offset < flash->mtd.size-(flash->mtd.size/4))
-		status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1;
-	else if (offset < flash->mtd.size-(flash->mtd.size/8))
-		status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0;
-	else if (offset < flash->mtd.size-(flash->mtd.size/16))
-		status_new = (status_old & ~(SR_BP0|SR_BP1)) | SR_BP2;
-	else if (offset < flash->mtd.size-(flash->mtd.size/32))
-		status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0;
-	else if (offset < flash->mtd.size-(flash->mtd.size/64))
-		status_new = (status_old & ~(SR_BP2|SR_BP0)) | SR_BP1;
-	else
-		status_new = (status_old & ~(SR_BP2|SR_BP1)) | SR_BP0;
+	for (lock_bits = 1; lock_bits < 7; lock_bits++) {
+		protected_area_start = flash->mtd.size -
+					get_protected_area(flash, lock_bits);
+		if (offset >= protected_area_start)
+			break;
+	}
+
+	status_new = (status_old & ~SR_BP_BIT_MASK) |
+			((lock_bits << SR_BP_BIT_OFFSET) & SR_BP_BIT_MASK);
 
 	/* Only modify protection if it will not unlock other areas */
-	if ((status_new&(SR_BP2|SR_BP1|SR_BP0)) >
-					(status_old&(SR_BP2|SR_BP1|SR_BP0))) {
+	if ((status_new & SR_BP_BIT_MASK) > (status_old & SR_BP_BIT_MASK)) {
 		write_enable(flash);
 		if (write_sr(flash, status_new) < 0) {
 			res = 1;
@@ -786,6 +801,8 @@ static int m25p80_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
 	struct m25p *flash = mtd_to_m25p(mtd);
 	uint32_t offset = ofs;
 	uint8_t status_old, status_new;
+	uint8_t lock_bits;
+	uint32_t protected_area_start;
 	int res = 0;
 
 	mutex_lock(&flash->lock);
@@ -797,24 +814,19 @@ static int m25p80_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
 
 	status_old = read_sr(flash);
 
-	if (offset+len > flash->mtd.size-(flash->mtd.size/64))
-		status_new = status_old & ~(SR_BP2|SR_BP1|SR_BP0);
-	else if (offset+len > flash->mtd.size-(flash->mtd.size/32))
-		status_new = (status_old & ~(SR_BP2|SR_BP1)) | SR_BP0;
-	else if (offset+len > flash->mtd.size-(flash->mtd.size/16))
-		status_new = (status_old & ~(SR_BP2|SR_BP0)) | SR_BP1;
-	else if (offset+len > flash->mtd.size-(flash->mtd.size/8))
-		status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0;
-	else if (offset+len > flash->mtd.size-(flash->mtd.size/4))
-		status_new = (status_old & ~(SR_BP0|SR_BP1)) | SR_BP2;
-	else if (offset+len > flash->mtd.size-(flash->mtd.size/2))
-		status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0;
-	else
-		status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1;
+	for (lock_bits = 1; lock_bits < 7; lock_bits++) {
+		protected_area_start = flash->mtd.size -
+					get_protected_area(flash, lock_bits);
+		if (offset+len >= protected_area_start)
+			break;
+	}
+	lock_bits--;
+
+	status_new = (status_old & ~SR_BP_BIT_MASK) |
+			((lock_bits << SR_BP_BIT_OFFSET) & SR_BP_BIT_MASK);
 
 	/* Only modify protection if it will not lock other areas */
-	if ((status_new&(SR_BP2|SR_BP1|SR_BP0)) <
-					(status_old&(SR_BP2|SR_BP1|SR_BP0))) {
+	if ((status_new & SR_BP_BIT_MASK) < (status_old & SR_BP_BIT_MASK)) {
 		write_enable(flash);
 		if (write_sr(flash, status_new) < 0) {
 			res = 1;
@@ -826,6 +838,39 @@ err:	mutex_unlock(&flash->lock);
 	return res;
 }
 
+static int m25p80_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
+{
+	struct m25p *flash = mtd_to_m25p(mtd);
+	uint32_t offset = ofs;
+	uint8_t status;
+	uint8_t lock_bits;
+	uint32_t protected_area_start;
+	int res;
+
+	mutex_lock(&flash->lock);
+	/* Wait until finished previous command */
+	if (wait_till_ready(flash)) {
+		mutex_unlock(&flash->lock);
+		return -EBUSY;
+	}
+	status = read_sr(flash);
+	mutex_unlock(&flash->lock);
+
+	lock_bits = ((status & SR_BP_BIT_MASK) >> SR_BP_BIT_OFFSET);
+
+	protected_area_start = flash->mtd.size -
+					get_protected_area(flash, lock_bits);
+
+	if (offset > protected_area_start)
+		res = MTD_IS_LOCKED;
+	else if (offset+len < protected_area_start)
+		res = MTD_IS_UNLOCKED;
+	else
+		res = MTD_IS_PARTIALLY_LOCKED;
+
+	return res;
+}
+
 /****************************************************************************/
 
 /*
@@ -856,6 +901,7 @@ struct flash_info {
 #define	M25P_NO_FR	0x08		/* Can't do fastread */
 #define	SECT_4K_PMC	0x10		/* OPCODE_BE_4K_PMC works uniformly */
 #define	M25P80_QUAD_READ	0x20    /* Flash supports Quad Read */
+#define	M25P_FLASH_LOCK	0x40		/* Flash protection support */
 };
 
 #define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags)	\
@@ -981,25 +1027,25 @@ static const struct spi_device_id m25p_ids[] = {
 
 	/* ST Microelectronics -- newer production may have feature updates */
 	{ "m25p05",  INFO(0x202010,  0,  32 * 1024,   2, 0) },
-	{ "m25p10",  INFO(0x202011,  0,  32 * 1024,   4, 0) },
-	{ "m25p20",  INFO(0x202012,  0,  64 * 1024,   4, 0) },
-	{ "m25p40",  INFO(0x202013,  0,  64 * 1024,   8, 0) },
-	{ "m25p80",  INFO(0x202014,  0,  64 * 1024,  16, 0) },
-	{ "m25p16",  INFO(0x202015,  0,  64 * 1024,  32, 0) },
-	{ "m25p32",  INFO(0x202016,  0,  64 * 1024,  64, 0) },
-	{ "m25p64",  INFO(0x202017,  0,  64 * 1024, 128, 0) },
-	{ "m25p128", INFO(0x202018,  0, 256 * 1024,  64, 0) },
+	{ "m25p10",  INFO(0x202011,  0,  32 * 1024,   4, M25P_FLASH_LOCK) },
+	{ "m25p20",  INFO(0x202012,  0,  64 * 1024,   4, M25P_FLASH_LOCK) },
+	{ "m25p40",  INFO(0x202013,  0,  64 * 1024,   8, M25P_FLASH_LOCK) },
+	{ "m25p80",  INFO(0x202014,  0,  64 * 1024,  16, M25P_FLASH_LOCK) },
+	{ "m25p16",  INFO(0x202015,  0,  64 * 1024,  32, M25P_FLASH_LOCK) },
+	{ "m25p32",  INFO(0x202016,  0,  64 * 1024,  64, M25P_FLASH_LOCK) },
+	{ "m25p64",  INFO(0x202017,  0,  64 * 1024, 128, M25P_FLASH_LOCK) },
+	{ "m25p128", INFO(0x202018,  0, 256 * 1024,  64, M25P_FLASH_LOCK) },
 	{ "n25q032", INFO(0x20ba16,  0,  64 * 1024,  64, 0) },
 
 	{ "m25p05-nonjedec",  INFO(0, 0,  32 * 1024,   2, 0) },
-	{ "m25p10-nonjedec",  INFO(0, 0,  32 * 1024,   4, 0) },
-	{ "m25p20-nonjedec",  INFO(0, 0,  64 * 1024,   4, 0) },
-	{ "m25p40-nonjedec",  INFO(0, 0,  64 * 1024,   8, 0) },
-	{ "m25p80-nonjedec",  INFO(0, 0,  64 * 1024,  16, 0) },
-	{ "m25p16-nonjedec",  INFO(0, 0,  64 * 1024,  32, 0) },
-	{ "m25p32-nonjedec",  INFO(0, 0,  64 * 1024,  64, 0) },
-	{ "m25p64-nonjedec",  INFO(0, 0,  64 * 1024, 128, 0) },
-	{ "m25p128-nonjedec", INFO(0, 0, 256 * 1024,  64, 0) },
+	{ "m25p10-nonjedec",  INFO(0, 0,  32 * 1024,   4, M25P_FLASH_LOCK) },
+	{ "m25p20-nonjedec",  INFO(0, 0,  64 * 1024,   4, M25P_FLASH_LOCK) },
+	{ "m25p40-nonjedec",  INFO(0, 0,  64 * 1024,   8, M25P_FLASH_LOCK) },
+	{ "m25p80-nonjedec",  INFO(0, 0,  64 * 1024,  16, M25P_FLASH_LOCK) },
+	{ "m25p16-nonjedec",  INFO(0, 0,  64 * 1024,  32, M25P_FLASH_LOCK) },
+	{ "m25p32-nonjedec",  INFO(0, 0,  64 * 1024,  64, M25P_FLASH_LOCK) },
+	{ "m25p64-nonjedec",  INFO(0, 0,  64 * 1024, 128, M25P_FLASH_LOCK) },
+	{ "m25p128-nonjedec", INFO(0, 0, 256 * 1024,  64, M25P_FLASH_LOCK) },
 
 	{ "m45pe10", INFO(0x204011,  0, 64 * 1024,    2, 0) },
 	{ "m45pe80", INFO(0x204014,  0, 64 * 1024,   16, 0) },
@@ -1007,7 +1053,7 @@ static const struct spi_device_id m25p_ids[] = {
 
 	{ "m25pe20", INFO(0x208012,  0, 64 * 1024,  4,       0) },
 	{ "m25pe80", INFO(0x208014,  0, 64 * 1024, 16,       0) },
-	{ "m25pe16", INFO(0x208015,  0, 64 * 1024, 32, SECT_4K) },
+	{ "m25pe16", INFO(0x208015,  0, 64 * 1024, 32, SECT_4K | M25P_FLASH_LOCK) },
 
 	{ "m25px16",    INFO(0x207115,  0, 64 * 1024, 32, SECT_4K) },
 	{ "m25px32",    INFO(0x207116,  0, 64 * 1024, 64, SECT_4K) },
@@ -1176,13 +1222,17 @@ static int m25p_probe(struct spi_device *spi)
 	flash->mtd.writesize = 1;
 	flash->mtd.flags = MTD_CAP_NORFLASH;
 	flash->mtd.size = info->sector_size * info->n_sectors;
+	flash->n_sectors = info->n_sectors;
+	flash->sector_size = info->sector_size;
 	flash->mtd._erase = m25p80_erase;
 	flash->mtd._read = m25p80_read;
 
 	/* flash protection support for STmicro chips */
-	if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
+	if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST &&
+					(info->flags & M25P_FLASH_LOCK)) {
 		flash->mtd._lock = m25p80_lock;
 		flash->mtd._unlock = m25p80_unlock;
+		flash->mtd._is_locked = m25p80_is_locked;
 	}
 
 	/* sst flash chips use AAI word program */
diff --git a/include/uapi/mtd/mtd-abi.h b/include/uapi/mtd/mtd-abi.h
index e272ea0..f8f5e9d 100644
--- a/include/uapi/mtd/mtd-abi.h
+++ b/include/uapi/mtd/mtd-abi.h
@@ -251,7 +251,7 @@ struct mtd_ecc_stats {
 	__u32 bbtblocks;
 };
 
-/*
+/**
  * MTD file modes - for read/write access to MTD
  *
  * @MTD_FILE_MODE_NORMAL:	OTP disabled, ECC enabled
@@ -275,6 +275,19 @@ enum mtd_file_modes {
 	MTD_FILE_MODE_RAW,
 };
 
+/**
+ * MTD locking states - return codes for ioctl(MEMISLOCKED)
+ *
+ * @MTD_IS_UNLOCKED:		Specified region is completely unlocked
+ * @MTD_IS_LOCKED:		Specified region is completely locked
+ * @MTD_IS_PARTIALLY_LOCKED:	Specified region is partially locked
+ */
+enum mtd_locking_states {
+	MTD_IS_UNLOCKED,
+	MTD_IS_LOCKED,
+	MTD_IS_PARTIALLY_LOCKED,
+};
+
 static inline int mtd_type_is_nand_user(const struct mtd_info_user *mtd)
 {
 	return mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH;



More information about the linux-mtd mailing list