[PATCH 09/15] mtd: spi-nor: winbond: Prepare the introduction of W35TxxNW parts
From: Miquel Raynal
Date: Fri Oct 02 2026 - 08:25:23 EST
This is a new octal DTR family, they require:
- a callback for enter octal DTR modes,
- a callback to configure the number of dummy cycles (since the spi-nor
core picks up a variant for which the number of duty cycles does not
match the hardware default).
These chips have a flag regiter, but no SR2.
They also feature automatic error correction (SECDEC).
Signed-off-by: Miquel Raynal <miquel.raynal@xxxxxxxxxxx>
---
drivers/mtd/spi-nor/winbond.c | 96 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index f2daab39ea57..d33e693ed711 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -11,6 +11,13 @@
#define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */
#define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */
#define WINBOND_NOR_OP_SELDIE 0xc2 /* Select active die */
+#define WINBOND_NOR_OP_WR_VCR 0x81 /* Write VCR registers */
+#define WINBOND_NOR_VCR_IO_MODE_CFG_REG 0x00 /* IO mode configuration address (VCR) */
+#define WINBOND_NOR_VCR_IO_MODE_SSDR 0xFF
+#define WINBOND_NOR_VCR_IO_MODE_ODDR 0xE7
+#define WINBOND_NOR_VCR_DUMMY_CFG_REG 0x01 /* Dummy cycle configuration address (VCR) */
+#define WINBOND_NOR_OP_RD_FR 0x70 /* Read Flag registers */
+#define WINBOND_NOR_OP_CL_FR 0x50 /* Clear Flag registers */
#define WINBOND_NOR_WREAR_OP(buf) \
SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \
@@ -24,6 +31,12 @@
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_OP_DATA_OUT(1, buf, 0))
+#define WINBOND_NOR_WR_VCR_OP(addr, buf) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WR_VCR, 0), \
+ SPI_MEM_OP_ADDR(3, addr, 0), \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_DATA_OUT(1, buf, 0))
+
static bool is_w25qxxrv(const struct spi_nor *nor)
{
struct sfdp_header *sfdp_h = spi_nor_sfdp_get_header(nor);
@@ -252,6 +265,87 @@ static const struct spi_nor_fixups winbond_nor_ecc_configuration_fixups = {
.post_sfdp = winbond_nor_ecc_configuration_post_sfdp_fixups,
};
+static int winbond_w35txxnw_nor_write_vcr(struct spi_nor *nor, u8 addr, u8 val)
+{
+ u8 *buf = nor->bouncebuf;
+ struct spi_mem_op op;
+ int ret;
+
+ if (!nor->spimem)
+ return -EOPNOTSUPP;
+
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ return ret;
+
+ buf[0] = val;
+ buf[1] = val;
+ op = (struct spi_mem_op)WINBOND_NOR_WR_VCR_OP(addr, buf);
+ spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+ if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
+ op.addr.nbytes = 4;
+ op.data.nbytes = 2;
+ }
+
+ return spi_mem_exec_op(nor->spimem, &op);
+}
+
+static int winbond_w35txxnw_nor_set_dummy(struct spi_nor *nor, u8 dummy_cycles)
+{
+ return winbond_w35txxnw_nor_write_vcr(nor, WINBOND_NOR_VCR_DUMMY_CFG_REG,
+ dummy_cycles);
+}
+
+static int winbond_w35txxnw_nor_set_octal_dtr(struct spi_nor *nor, bool enable)
+{
+ enum spi_nor_protocol proto_aft;
+ u8 *buf = nor->bouncebuf;
+ u8 rdid_dummy_aft, val;
+ int ret;
+
+ if (enable) {
+ val = WINBOND_NOR_VCR_IO_MODE_ODDR;
+ proto_aft = SNOR_PROTO_8_8_8_DTR;
+ rdid_dummy_aft = 16;
+ } else {
+ val = WINBOND_NOR_VCR_IO_MODE_SSDR;
+ proto_aft = SNOR_PROTO_1_1_1;
+ rdid_dummy_aft = 0;
+ }
+
+ ret = winbond_w35txxnw_nor_write_vcr(nor, WINBOND_NOR_VCR_IO_MODE_CFG_REG, val);
+ if (ret)
+ return ret;
+
+ /* Read flash ID to make sure the switch was successful */
+ ret = spi_nor_read_id(nor, 0, rdid_dummy_aft, buf, proto_aft);
+ if (ret) {
+ dev_err(nor->dev, "Cannot read JEDEC ID after %s 8D-8D-8D mode (%d)\n",
+ enable ? "enabling" : "disabling", ret);
+ return ret;
+ }
+
+ if (memcmp(buf, nor->info->id->bytes, nor->info->id->len))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int winbond_w35txxnw_nor_late_init(struct spi_nor *nor)
+{
+ nor->params->set_octal_dtr = winbond_w35txxnw_nor_set_octal_dtr;
+ nor->params->set_dummy = winbond_w35txxnw_nor_set_dummy;
+ nor->params->opcodes.read_sr2 = 0;
+ nor->params->opcodes.write_sr1_and_sr2 = 0;
+
+ return 0;
+}
+
+static const struct spi_nor_fixups winbond_w35txxnw_nor_fixups = {
+ .post_sfdp = winbond_nor_ecc_configuration_post_sfdp_fixups,
+ .late_init = winbond_w35txxnw_nor_late_init,
+};
+
static const struct flash_info winbond_nor_parts[] = {
{
.id = SNOR_ID(0xef, 0x30, 0x10),
@@ -673,6 +767,8 @@ static const struct spi_nor_fixup winbond_fixups[] = {
.fixups = &winbond_nor_multi_die_fixups },
{ .id = SNOR_ID(0xef, 0x40, 0x22), .match = winbond_jv_match,
.fixups = &winbond_nor_multi_die_fixups },
+ { .id = SNOR_ID(0xef, 0x5b), .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
+ .fixups = &winbond_w35txxnw_nor_fixups },
{ .id = SNOR_ID(0xef, 0x60), .match = winbond_pw_with_ecc_match,
.fixups = &winbond_nor_ecc_configuration_fixups },
{ .id = SNOR_ID(0xef, 0x60), .match = winbond_pw_match,
--
2.55.0