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@xxxxxxxx>
---
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;