[PATCH 2/2] mtd: spi-nor: implement OTP support for Winbond flashes

Michael Walle michael at walle.cc
Wed Jan 8 15:36:54 PST 2020


Use the new OTP ops to implement OTP access on Winbond flashes. Most
Winbond flashes provides up to four different OTP areas ("Security
Registers"). Newer flashes uses the first OTP area for SFDP data. Thus,
this only handles the last three areas and leave the first untouched.

This was tested on a Winbond W25Q32JW. Please note, that there is a
variant of the W25Q32JW which reuses the same JEDEC ID as the W25Q32FW
which could support all four OTP areas. But because we cannot distiguish
between these two, the driver only uses three areas on the W25Q32FW.

Signed-off-by: Michael Walle <michael at walle.cc>
---
 drivers/mtd/spi-nor/spi-nor.c | 132 ++++++++++++++++++++++++++++++++++
 include/linux/mtd/spi-nor.h   |  14 ++++
 2 files changed, 146 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 5eabaec70508..99d365cb63b1 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -2641,17 +2641,20 @@ static const struct flash_info spi_nor_ids[] = {
 		"w25q16dw", INFO(0xef6015, 0, 64 * 1024,  32,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q16jwim", INFO(0xef8015, 0, 64 * 1024,  32,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{ "w25x32", INFO(0xef3016, 0, 64 * 1024,  64, SECT_4K) },
 	{
 		"w25q16jv-im/jm", INFO(0xef7015, 0, 64 * 1024,  32,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{ "w25q20cl", INFO(0xef4012, 0, 64 * 1024,  4, SECT_4K) },
 	{ "w25q20bw", INFO(0xef5012, 0, 64 * 1024,  4, SECT_4K) },
@@ -2661,16 +2664,19 @@ static const struct flash_info spi_nor_ids[] = {
 		"w25q32dw", INFO(0xef6016, 0, 64 * 1024,  64,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q32jv", INFO(0xef7016, 0, 64 * 1024,  64,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q32jwim", INFO(0xef8016, 0, 64 * 1024,  64,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
 	{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
@@ -2678,26 +2684,31 @@ static const struct flash_info spi_nor_ids[] = {
 		"w25q64dw", INFO(0xef6017, 0, 64 * 1024, 128,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q64jwim", INFO(0xef8017, 0, 64 * 1024, 128,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q128fw", INFO(0xef6018, 0, 64 * 1024, 256,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q128jv", INFO(0xef7018, 0, 64 * 1024, 256,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{
 		"w25q128jwim", INFO(0xef8018, 0, 64 * 1024, 256,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+			OTP_INFO(256, 3, 0x1000, 0x1000)
 	},
 	{ "w25q80", INFO(0xef5014, 0, 64 * 1024,  16, SECT_4K) },
 	{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024,  16, SECT_4K) },
@@ -4645,6 +4656,125 @@ static int spi_nor_setup(struct spi_nor *nor,
 	return nor->params.setup(nor, hwcaps);
 }
 
+static int winbond_otp_read(struct spi_nor *nor, loff_t addr, uint64_t len,
+			    u8 *buf)
+{
+	u8 addr_width, read_opcode, read_dummy;
+	enum spi_nor_protocol read_proto;
+	int ret;
+
+	read_opcode = nor->read_opcode;
+	addr_width = nor->addr_width;
+	read_dummy = nor->read_dummy;
+	read_proto = nor->read_proto;
+
+	nor->read_opcode = SPINOR_OP_WB_RSECR;
+	nor->addr_width = 3;
+	nor->read_dummy = 8;
+	nor->read_proto = SNOR_PROTO_1_1_1;
+
+	ret = spi_nor_read_raw(nor, addr, len, buf);
+
+	nor->read_opcode = read_opcode;
+	nor->addr_width = addr_width;
+	nor->read_dummy = read_dummy;
+	nor->read_proto = read_proto;
+
+	return ret;
+}
+
+static int winbond_otp_write(struct spi_nor *nor, loff_t addr, uint64_t len,
+			     u8 *buf)
+{
+	u8 addr_width, program_opcode;
+	enum spi_nor_protocol write_proto;
+	int ret;
+
+	program_opcode = nor->program_opcode;
+	addr_width = nor->addr_width;
+	write_proto = nor->write_proto;
+
+	nor->program_opcode = SPINOR_OP_WB_PSECR;
+	nor->addr_width = 3;
+	nor->write_proto = SNOR_PROTO_1_1_1;
+
+	/*
+	 * We only support a write to one single page. For now all winbond
+	 * flashes only have one page per OTP region.
+	 */
+	ret = spi_nor_write_enable(nor);
+	if (ret)
+		goto out;
+
+	ret = spi_nor_write_data(nor, addr, len, buf);
+	if (ret < 0)
+		goto out;
+
+	ret = spi_nor_wait_till_ready(nor);
+
+out:
+	nor->program_opcode = program_opcode;
+	nor->addr_width = addr_width;
+	nor->write_proto = write_proto;
+
+	return ret;
+}
+
+static int _winbond_otp_lock_bit(unsigned int region)
+{
+	static const int lock_bits[] = { SR2_WB_LB1, SR2_WB_LB2, SR2_WB_LB3 };
+
+	if (region >= ARRAY_SIZE(lock_bits))
+		return -EINVAL;
+
+	return lock_bits[region];
+}
+
+static int winbond_otp_lock(struct spi_nor *nor, unsigned int region)
+{
+	int lock_bit;
+	u8 sr2;
+	int ret;
+
+	lock_bit = _winbond_otp_lock_bit(region);
+	if (lock_bit < 0)
+		return lock_bit;
+
+	ret = spi_nor_read_cr(nor, &sr2);
+	if (ret)
+		return ret;
+
+	/* check if its already locked */
+	if (sr2 & lock_bit)
+		return 0;
+
+	return spi_nor_write_16bit_cr_and_check(nor, sr2 | lock_bit);
+}
+
+static int winbond_otp_is_locked(struct spi_nor *nor, unsigned int region)
+{
+	int lock_bit;
+	u8 sr2;
+	int ret;
+
+	lock_bit = _winbond_otp_lock_bit(region);
+	if (lock_bit < 0)
+		return lock_bit;
+
+	ret = spi_nor_read_cr(nor, &sr2);
+	if (ret)
+		return ret;
+
+	return (sr2 & lock_bit);
+}
+
+static const struct spi_nor_otp_ops winbond_otp_ops = {
+	.read = winbond_otp_read,
+	.write = winbond_otp_write,
+	.lock = winbond_otp_lock,
+	.is_locked = winbond_otp_is_locked,
+};
+
 static void atmel_set_default_init(struct spi_nor *nor)
 {
 	nor->flags |= SNOR_F_HAS_LOCK;
@@ -4681,6 +4811,8 @@ static void st_micron_set_default_init(struct spi_nor *nor)
 static void winbond_set_default_init(struct spi_nor *nor)
 {
 	nor->params.set_4byte = winbond_set_4byte;
+	if (nor->params.otp_info.n_otps)
+		nor->params.otp_ops = &winbond_otp_ops;
 }
 
 /**
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index e427dcd72f79..cc847ed06ef3 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -121,6 +121,20 @@
 #define SPINOR_OP_RD_EVCR      0x65    /* Read EVCR register */
 #define SPINOR_OP_WD_EVCR      0x61    /* Write EVCR register */
 
+/* Used for Winbond flashes only. */
+#define SPINOR_OP_WB_ESECR	0x44	/* Erase Security registers */
+#define SPINOR_OP_WB_PSECR	0x42	/* Program Security registers */
+#define SPINOR_OP_WB_RSECR	0x48	/* Read Security registers */
+
+/*
+ * Warning: LB0 (and thus the security register 0) is used for the SFDP
+ * data on all newer flashes.
+ */
+#define SR2_WB_LB0		BIT(2)	/* Security Register Lock Bit 0 */
+#define SR2_WB_LB1		BIT(3)	/* Security Register Lock Bit 1 */
+#define SR2_WB_LB2		BIT(4)	/* Security Register Lock Bit 2 */
+#define SR2_WB_LB3		BIT(5)	/* Security Register Lock Bit 3 */
+
 /* Status Register bits. */
 #define SR_WIP			BIT(0)	/* Write in progress */
 #define SR_WEL			BIT(1)	/* Write enable latch */
-- 
2.20.1




More information about the linux-mtd mailing list