[PATCH] mtd: spi-nor: implement ASP lock for S25FL512S

Christoph Reiter christoph.reiter at evk.biz
Mon Aug 25 07:59:05 CEST 2025


This implements the locking mechanism used for protecting individual
sectors of Spansion/Infineon S25FL512S chips. This might be applicable to other chips of this series,
but I don't have the hardware to test it.

Signed-off-by: Christoph Reiter <christoph.reiter at evk.biz>
---
 drivers/mtd/spi/sf_internal.h  |   1 +
 drivers/mtd/spi/spi-nor-core.c | 163 +++++++++++++++++++++++++++++++++
 drivers/mtd/spi/spi-nor-ids.c  |   4 +-
 include/linux/mtd/spi-nor.h    |   2 +
 4 files changed, 168 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h
index 8d2249ce354..319f4840cd6 100644
--- a/drivers/mtd/spi/sf_internal.h
+++ b/drivers/mtd/spi/sf_internal.h
@@ -69,6 +69,7 @@ struct flash_info {
 #define SPI_NOR_HAS_SST26LOCK	BIT(15)	/* Flash supports lock/unlock via BPR */
 #define SPI_NOR_OCTAL_READ	BIT(16)	/* Flash supports Octal Read */
 #define SPI_NOR_OCTAL_DTR_READ	BIT(17)	/* Flash supports Octal DTR Read */
+#define SPI_NOR_HAS_ASP_LOCK	BIT(18)	/* Flash supports Advanced sector protection */
 };
 
 extern const struct flash_info spi_nor_ids[];
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index 76c33b24368..8821afeda99 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -1553,6 +1553,158 @@ static int stm_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len)
 	return stm_is_unlocked_sr(nor, ofs, len, status);
 }
 #endif /* CONFIG_SPI_FLASH_STMICRO */
+
+
+/* Advanced Sector Protection for S25FL512S */
+
+#define ASP_DYB_LOCKED 0x0
+#define ASP_DYB_UNLOCKED 0xff
+
+static int _asp_check_params(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+	if (ofs + len > nor->size) {
+		dev_err(nor->dev, "Params exceed length of flash\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int _asp_read_dyb(struct spi_nor *nor, loff_t ofs)
+{
+	u8 buf;
+	int ret;
+	struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_ASP_DYBRD, 0),
+		SPI_MEM_OP_ADDR(4, ofs, 0), /* 4 address bytes */
+		SPI_MEM_OP_NO_DUMMY,
+		SPI_MEM_OP_DATA_IN(1, &buf, 0)); /* read 1 byte */
+	spi_nor_setup_op(nor, &op, nor->reg_proto);
+	op.data.dir = SPI_MEM_DATA_IN;
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret) {
+		debug("%s: spi_mem_exec_op() ret %d\n", __func__, ret);
+		return ret < 0 ? ret : -1;
+	}
+	return buf;
+}
+
+static int _asp_write_dyb(struct spi_nor *nor, loff_t ofs, u8 value)
+{
+	int ret;
+	struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_ASP_DYBWR, 0),
+		SPI_MEM_OP_ADDR(4, ofs, 0), /* 4 address bytes */
+		SPI_MEM_OP_NO_DUMMY,
+		SPI_MEM_OP_DATA_OUT(1, &value, 0)); /* write 1 byte */
+	spi_nor_setup_op(nor, &op, nor->reg_proto);
+	op.data.dir = SPI_MEM_DATA_OUT;
+
+	write_enable(nor);
+
+	ret = spi_mem_exec_op(nor->spi, &op);
+	if (ret) {
+		debug("%s: spi_mem_exec_op() ret %d\n", __func__, ret);
+		return ret < 0 ? ret : -1;
+	}
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret) {
+		debug("%s: spi_nor_wait_till_ready() ret %d\n", __func__, ret);
+		return ret < 0 ? ret : -1;
+	}
+
+	write_disable(nor);
+	return 0;
+}
+
+static int asp_is_unlocked(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+	int ret;
+	loff_t sec_first = ofs & ~(nor->sector_size - 1);
+	loff_t sec_last = (ofs + len - 1) & ~(nor->sector_size - 1);
+
+	/* Iterate over sectors */
+	for (loff_t sec = sec_first; sec <= sec_last; sec += nor->sector_size) {
+		/* Read DYB byte of that sector */
+		ret = _asp_read_dyb(nor, sec);
+		if (ret < 0) {
+			/* Error while reading DYB */
+			debug("%s: _asp_read_dyb() ret %d\n", __func__, ret);
+			return ret;
+		}
+		debug("%s: sector %llx=%x\n", __func__, sec, ret);
+		if (ret == ASP_DYB_LOCKED) {
+			/* At least 1 sector locked - return immediately */
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static int asp_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+	int ret;
+	loff_t sec_first, sec_last;
+
+	if (_asp_check_params(nor, ofs, len)) {
+		return -1;
+	}
+	if (!len) {
+		return 0;
+	}
+	sec_first = ofs & ~(nor->sector_size - 1);
+	sec_last = (ofs + len - 1) & ~(nor->sector_size - 1);
+
+	/* Iterate over sectors */
+	for (loff_t sec = sec_first; sec <= sec_last; sec += nor->sector_size) {
+		/* Skip if already locked */
+		if (!asp_is_unlocked(nor, sec, nor->sector_size)) {
+			debug("%s: sector %llx already locked\n", __func__, sec);
+			continue;
+		}
+		/* Lock the sector */
+		ret = _asp_write_dyb(nor, sec, ASP_DYB_LOCKED);
+		if (ret < 0) {
+			dev_err(nor->dev, "Error locking sector %llx\n", sec);
+			return ret;
+		}
+		debug("%s: Locked sector %llx\n", __func__, sec);
+	}
+	printf("Locked flash sectors %llx-%llx\n", sec_first, sec_last);
+	return 0;
+}
+
+static int asp_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
+{
+	int ret;
+	loff_t sec_first, sec_last;
+
+	if (_asp_check_params(nor, ofs, len)) {
+		return -1;
+	}
+	if (!len) {
+		return 0;
+	}
+	sec_first = ofs & ~(nor->sector_size - 1);
+	sec_last = (ofs + len - 1) & ~(nor->sector_size - 1);
+
+	/* Iterate over sectors */
+	for (loff_t sec = sec_first; sec <= sec_last; sec += nor->sector_size) {
+		/* Skip if already unlocked */
+		if (asp_is_unlocked(nor, sec, nor->sector_size)) {
+			debug("%s: sector %llx already unlocked\n", __func__, sec);
+			continue;
+		}
+		/* Unlock the sector */
+		ret = _asp_write_dyb(nor, sec, ASP_DYB_UNLOCKED);
+		if (ret < 0) {
+			dev_err(nor->dev, "Error unlocking sector %llx\n", sec);
+			return ret;
+		}
+		debug("%s: Unlocked sector %llx\n", __func__, sec);
+	}
+	printf("Unlocked flash sectors %llx-%llx\n", sec_first, sec_last);
+	return 0;
+}
+
 #endif
 
 static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
@@ -4570,6 +4722,17 @@ int spi_nor_scan(struct spi_nor *nor)
 		nor->flash_is_unlocked = sst26_is_unlocked;
 	}
 #endif
+
+#if defined(CONFIG_SPI_FLASH_SPANSION)
+	/* Advanced sector protection */
+	if (info->flags & SPI_NOR_HAS_ASP_LOCK) {
+		log_info("Enabling ASP for %s\n", nor->mtd_name);
+		nor->flash_lock = asp_lock;
+		nor->flash_unlock = asp_unlock;
+		nor->flash_is_unlocked = asp_is_unlocked;
+	}
+#endif
+
 #endif
 	if (info->flags & USE_FSR)
 		nor->flags |= SNOR_F_USE_FSR;
diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c
index 0383175beb5..48ec20d8ab1 100644
--- a/drivers/mtd/spi/spi-nor-ids.c
+++ b/drivers/mtd/spi/spi-nor-ids.c
@@ -374,12 +374,12 @@ const struct flash_info spi_nor_ids[] = {
 	{ INFO("s25sl064p",  0x010216, 0x4d00,  64 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
 	{ INFO6("s25fl256s0", 0x010219, 0x4d0080, 256 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
 	{ INFO6("s25fl256s1", 0x010219, 0x4d0180,  64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
-	{ INFO6("s25fl512s",  0x010220, 0x4d0080, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
+	{ INFO6("s25fl512s",  0x010220, 0x4d0080, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR | SPI_NOR_HAS_ASP_LOCK) },
 	{ INFO6("s25fs064s",  0x010217, 0x4d0181,  64 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
 	{ INFO6("s25fs128s",  0x012018, 0x4d0181,  64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
 	{ INFO6("s25fs256s",  0x010219, 0x4d0181,  64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
 	{ INFO6("s25fs512s",  0x010220, 0x4d0081, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
-	{ INFO("s25fl512s_256k",  0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
+	{ INFO("s25fl512s_256k", 0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR | SPI_NOR_HAS_ASP_LOCK) },
 	{ INFO("s25fl512s_64k",  0x010220, 0x4d01, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
 	{ INFO("s25fl512s_512k", 0x010220, 0x4f00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
 	{ INFO("s70fs01gs_256k", 0x010221, 0x4d00, 256 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 4eef4ab0488..2294a75b420 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -141,6 +141,8 @@
 #define SPINOR_OP_BRRD		0x16	/* Bank register read */
 #define SPINOR_OP_CLSR		0x30	/* Clear status register 1 */
 #define SPINOR_OP_EX4B_CYPRESS	0xB8	/* Exit 4-byte mode */
+#define SPINOR_ASP_DYBRD	0xe0	/* Advanced sector protection - DYB Read */
+#define SPINOR_ASP_DYBWR	0xe1	/* Advanced sector protection - DYB Write */
 
 /* Used for Micron flashes only. */
 #define SPINOR_OP_RD_EVCR	0x65	/* Read EVCR register */
-- 
2.46.1.windows.1


----
Christoph Reiter, MSc
Firmware Engineer
EVK DI Kerschhaggl GmbH
Josef-Krainer-Str. 35
8074Raaba-Graz
Austria
P  +43 316 461664 23
christoph.reiter at evk.biz
​
​Join us from August 27-29 at Retech 2025 in Seoul, South Korea, when everthing revolves around waste management, 
​recycling and environmental technologies. We look forward to meeting you at our booth D111.
Before printing, please think about the environment
The information contained in this message is confidential and may be legally privileged. The message is intended solely for the addressee(s).
​If you are not the intended recipient, you are hereby notified that any use, dissemination, or reproduction is strictly prohibited and may be unlawful.
If you are not the intended recipient, please contact the sender by return e-mail and destroy all copies of the original message. [v12190717]
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image537092.png
Type: image/png
Size: 87116 bytes
Desc: image537092.png
URL: <https://lists.denx.de/pipermail/u-boot/attachments/20250825/0695b271/attachment.png>


More information about the U-Boot mailing list