From 85b6110155cab6b67c9072c19f21dcb620c2819d Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Fri, 10 Apr 2026 17:10:39 +0800 Subject: mtd: spi-nor: macronix: Document support for additional flash aliases Several Macronix SPI NOR flash devices share the same ID. For instance, the MX25L51245G and MX25L51273G share the same ID (0xc2201a) with the currently listed MX66L51235F. Add these equivalent part numbers to the comments above their corresponding flash_info entries. While this change does not affect the functional driver logic, it significantly improves the searchability of the kernel source tree. This helps users easily verify that their specific physical part is fully supported, preventing confusion and deterring redundant patch submissions that attempt to add duplicate entries for these alias parts. Signed-off-by: Cheng Ming Lin Reviewed-by: Michael Walle Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/macronix.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index e97f5cbd9aad..c557525b03e4 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -138,7 +138,7 @@ static const struct flash_info macronix_nor_parts[] = { .id = SNOR_ID(0xc2, 0x20, 0x19), .fixups = &mx25l25635_fixups }, { - /* MX66L51235F */ + /* MX25L51245G, MX25L51273G, MX66L51235F */ .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixup_flags = SPI_NOR_4B_OPCODES, .fixups = ¯onix_qpp4b_fixups, @@ -186,11 +186,11 @@ static const struct flash_info macronix_nor_parts[] = { .size = SZ_16M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* MX25U51245G */ + /* MX25U51245G, MX25U51293G */ .id = SNOR_ID(0xc2, 0x25, 0x3a), .fixups = ¯onix_qpp4b_fixups, }, { - /* MX66U1G45G */ + /* MX66U1G45G, MX66U1G93G */ .id = SNOR_ID(0xc2, 0x25, 0x3b), .fixups = ¯onix_qpp4b_fixups, }, { -- cgit v1.2.3 From 1dde2faa82fd69a4da5ab7a4635b51773b566d4d Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Fri, 5 Jun 2026 16:48:36 +0800 Subject: mtd: spi-nor: Add support for MX25L12833F and MX25L12845G Add support for Macronix MX25L12833F and MX25L12845G SPI NOR flashes. These parts share the same JEDEC ID (0xc2, 0x20, 0x18) as the legacy MX25L12805D. The newer flashes support SFDP and 1-4-4 Page Program in 3-byte address mode, but this 4PP capability is not defined in their SFDP tables. Conversely, the legacy MX25L12805D lacks SFDP support entirely and does not support 4PP. To safely enable 4PP for the newer flashes without breaking the legacy part, introduce a post_sfdp fixup. Since the legacy MX25L12805D does not support SFDP, it falls back to static parameters and will never execute the post_sfdp hook. The newer flashes will successfully parse the SFDP, trigger the hook, and safely append the SNOR_HWCAPS_PP_1_4_4 capability. Signed-off-by: Cheng Ming Lin Reviewed-by: Michael Walle Link: https://www.alldatasheet.com/datasheet-pdf/view/267916/MCNIX/MX25L12805D.html Link: https://patchwork.ozlabs.org/project/linux-mtd/cover/20260713091206.2414400-1-mwalle@kernel.org/" Tested-by: Russell Senior Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/macronix.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index c557525b03e4..c3b0bf38a650 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -83,6 +83,25 @@ mx25l3255e_late_init_fixups(struct spi_nor *nor) return 0; } +static int +mx25l12805d_4pp3b_post_sfdp_fixups(struct spi_nor *nor) +{ + struct spi_nor_flash_parameter *params = nor->params; + + /* + * JEDEC ID 0xc22018 is shared by MX25L12805D (no SFDP, no 4PP) and + * MX25L12833F/MX25L12845G (support SFDP and 4PP in 3-byte mode). + * The legacy 05D lacks SFDP and will not execute this hook. For + * the newer flashes, 3-byte 1-4-4 PP is not defined in SFDP, so + * we safely enable it here. + */ + params->hwcaps.mask |= SNOR_HWCAPS_PP_1_4_4; + spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP_1_4_4], + SPINOR_OP_PP_1_4_4, SNOR_PROTO_1_4_4); + + return 0; +} + static const struct spi_nor_fixups mx25l25635_fixups = { .post_bfpt = mx25l25635_post_bfpt_fixups, .post_sfdp = macronix_qpp4b_post_sfdp_fixups, @@ -96,6 +115,10 @@ static const struct spi_nor_fixups mx25l3255e_fixups = { .late_init = mx25l3255e_late_init_fixups, }; +static const struct spi_nor_fixups mx25l12805d_4pp3b_fixups = { + .post_sfdp = mx25l12805d_4pp3b_post_sfdp_fixups, +}; + static const struct flash_info macronix_nor_parts[] = { { .id = SNOR_ID(0xc2, 0x20, 0x10), @@ -130,9 +153,10 @@ static const struct flash_info macronix_nor_parts[] = { .size = SZ_8M, .no_sfdp_flags = SECT_4K, }, { - /* MX25L12805D */ + /* MX25L12805D, MX25L12833F, MX25L12845G */ .id = SNOR_ID(0xc2, 0x20, 0x18), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP, + .fixups = &mx25l12805d_4pp3b_fixups, }, { /* MX25L25635E, MX25L25645G */ .id = SNOR_ID(0xc2, 0x20, 0x19), -- cgit v1.2.3 From 01739ccea21b69818dc071096a249e6f84a6b13d Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Fri, 5 Jun 2026 16:48:37 +0800 Subject: mtd: spi-nor: macronix: Restore fallback parameters for MX25L12805D In a previous effort to drop flash_info fields and rely on SFDP, the static size and no_sfdp_flags were removed from the MX25L12805D entry (JEDEC ID 0xc22018). At that time, the legacy MX25L12805D was already EOL and unavailable for physical testing. Verification was inadvertently performed using the newer MX25L12833F, which shares the same JEDEC ID but supports SFDP. As a result, the probe succeeded during testing, leading to the mistaken removal of the fallback parameters. Since the actual MX25L12805D lacks SFDP support entirely, it strictly requires these static parameters. Restore .size = SZ_16M and .no_sfdp_flags = SECT_4K to this entry to fix the probe failure for the legacy part. Fixes: 947c86e481a0 ("mtd: spi-nor: macronix: Drop the redundant flash info fields") Cc: stable@vger.kernel.org Signed-off-by: Cheng Ming Lin Reviewed-by: Michael Walle Tested-by: Russell Senior Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/macronix.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index c3b0bf38a650..332e09c8332c 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -155,7 +155,9 @@ static const struct flash_info macronix_nor_parts[] = { }, { /* MX25L12805D, MX25L12833F, MX25L12845G */ .id = SNOR_ID(0xc2, 0x20, 0x18), + .size = SZ_16M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP, + .no_sfdp_flags = SECT_4K, .fixups = &mx25l12805d_4pp3b_fixups, }, { /* MX25L25635E, MX25L25645G */ -- cgit v1.2.3 From 0132eaac40518f6ebb42131e5005efc375473e8e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:39 +0200 Subject: mtd: spi-nor: spansion: s25fl256s0: remove SKIP_SFDP flag That flag was added in [1] to not "issue any non supported commands". But since we now have the generic SFDP driver, we'll issue the RDSFDP command on most flashes anyway and there was no single complaint. Remove the only user of this flag so we can rework the parameter initialization in the core. [1] https://lore.kernel.org/linux-mtd/20211207140254.87681-13-tudor.ambarus@microchip.com/ Reviewed-by: Tudor Ambarus Reviewed-by: Takahiro Kuwano Tested-by: Cheng Ming Lin Tested-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/spansion.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 65227d989de1..092450375d4e 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -868,7 +868,7 @@ static const struct flash_info spansion_nor_parts[] = { .name = "s25fl256s0", .size = SZ_32M, .sector_size = SZ_256K, - .no_sfdp_flags = SPI_NOR_SKIP_SFDP | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .mfr_flags = USE_CLSR, }, { .id = SNOR_ID(0x01, 0x02, 0x19, 0x4d, 0x00, 0x81), -- cgit v1.2.3 From a4cd8da7f2bda7562a7e156ee9c9e9b3ba98208e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:40 +0200 Subject: mtd: spi-nor: don't clear the SNOR_F_4B_OPCODES flag on failure This was introduced in commit 548ed6847f530 ("mtd: spi-nor: Add the SNOR_F_4B_OPCODES flag"). It looks like it was the rollback mechanism if parsing the SFDP failed, but was setting that flag first. Nowadays, that flag can only be set if spi_nor_parse_4bait() was successful. IOW, the flags won't be left in an undefined state if parsing SFDP fails. Remove the unneeded code to be able to rework spi_nor_init_params(). Reviewed-by: Miquel Raynal Reviewed-by: Tudor Ambarus Reviewed-by: Takahiro Kuwano Tested-by: Cheng Ming Lin Tested-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ccf4396cdcd0..9ef6381de521 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3066,10 +3066,8 @@ static void spi_nor_sfdp_init_params_deprecated(struct spi_nor *nor) memcpy(&sfdp_params, nor->params, sizeof(sfdp_params)); - if (spi_nor_parse_sfdp(nor)) { + if (spi_nor_parse_sfdp(nor)) memcpy(nor->params, &sfdp_params, sizeof(*nor->params)); - nor->flags &= ~SNOR_F_4B_OPCODES; - } } /** -- cgit v1.2.3 From e37ee8734881a7f92dbbeecf8cc129dd8a6a76b8 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:41 +0200 Subject: mtd: spi-nor: move cmd_ext_type into spi_nor_flash_parameter Right now the SFDP parsing code is modifying both members of struct spi_nor_flash_parameter and struct spi_nor. This may lead to inconsistencies if the parsing fails because only the flash parameters are rolled back. To fix this, move cmd_ext_type into the struct spi_nor_flash_parameter. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260601125438.3481722-1-mwalle%40kernel.org?part=3 Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 2 +- drivers/mtd/spi-nor/core.h | 2 ++ drivers/mtd/spi-nor/debugfs.c | 2 +- drivers/mtd/spi-nor/micron-st.c | 2 +- drivers/mtd/spi-nor/sfdp.c | 4 ++-- include/linux/mtd/spi-nor.h | 2 -- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 9ef6381de521..789f522b9b47 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -59,7 +59,7 @@ static u8 spi_nor_get_cmd_ext(const struct spi_nor *nor, const struct spi_mem_op *op) { - switch (nor->cmd_ext_type) { + switch (nor->params->cmd_ext_type) { case SPI_NOR_EXT_INVERT: return ~op->cmd.opcode; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index ba2d1a862c9d..6a77ea2901a3 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -370,6 +370,7 @@ struct spi_nor_otp { * in the array, the higher priority. * @page_programs: page program capabilities ordered by priority: the * higher index in the array, the higher priority. + * @cmd_ext_type: the command opcode extension type for DTR mode. * @erase_map: the erase map parsed from the SFDP Sector Map Parameter * Table. * @otp: SPI NOR OTP info. @@ -399,6 +400,7 @@ struct spi_nor_flash_parameter { struct spi_nor_hwcaps hwcaps; struct spi_nor_read_command reads[SNOR_CMD_READ_MAX]; struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX]; + enum spi_nor_cmd_ext cmd_ext_type; struct spi_nor_erase_map erase_map; struct spi_nor_otp otp; diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 288e2866daed..37738e2b262e 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -109,7 +109,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode); seq_printf(s, " program\t0x%02x\n", nor->program_opcode); - switch (nor->cmd_ext_type) { + switch (params->cmd_ext_type) { case SPI_NOR_EXT_NONE: str = "none"; break; diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index c75b0a1cd567..186362066c6b 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -177,7 +177,7 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor) spi_nor_set_pp_settings(&nor->params->page_programs[SNOR_CMD_PP_8_8_8_DTR], SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR); - nor->cmd_ext_type = SPI_NOR_EXT_REPEAT; + nor->params->cmd_ext_type = SPI_NOR_EXT_REPEAT; nor->params->rdsr_dummy = 8; nor->params->rdsr_addr_nbytes = 0; diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 4600983cb579..5b41ffec7374 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -655,11 +655,11 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* 8D-8D-8D command extension. */ switch (bfpt.dwords[SFDP_DWORD(18)] & BFPT_DWORD18_CMD_EXT_MASK) { case BFPT_DWORD18_CMD_EXT_REP: - nor->cmd_ext_type = SPI_NOR_EXT_REPEAT; + params->cmd_ext_type = SPI_NOR_EXT_REPEAT; break; case BFPT_DWORD18_CMD_EXT_INV: - nor->cmd_ext_type = SPI_NOR_EXT_INVERT; + params->cmd_ext_type = SPI_NOR_EXT_INVERT; break; case BFPT_DWORD18_CMD_EXT_RES: diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 4b92494827b1..e75c1b4cb4c0 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -366,7 +366,6 @@ struct spi_nor_flash_parameter; * @program_opcode: the program opcode * @sst_write_second: used by the SST write operation * @flags: flag options for the current SPI NOR (SNOR_F_*) - * @cmd_ext_type: the command opcode extension type for DTR mode. * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations @@ -408,7 +407,6 @@ struct spi_nor { enum spi_nor_protocol reg_proto; bool sst_write_second; u32 flags; - enum spi_nor_cmd_ext cmd_ext_type; struct sfdp *sfdp; struct dentry *debugfs_root; u8 dfs_sr_cache[2]; -- cgit v1.2.3 From 0ede46b4f5b38cb43225927abbf088dd568db54e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:42 +0200 Subject: mtd: spi-nor: move flags into spi_nor_flash_parameter Right now the SFDP parsing code is modifying both members of struct spi_nor_flash_parameter and struct spi_nor. This may lead to inconsistencies if the parsing fails because only the flash parameters are rolled back. To fix this, move the flags into the struct spi_nor_flash_parameter. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260601125438.3481722-1-mwalle%40kernel.org?part=3 Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 83 +++++++++++++++++++++++------------------ drivers/mtd/spi-nor/core.h | 2 + drivers/mtd/spi-nor/debugfs.c | 4 +- drivers/mtd/spi-nor/macronix.c | 2 +- drivers/mtd/spi-nor/micron-st.c | 6 +-- drivers/mtd/spi-nor/sfdp.c | 16 ++++---- drivers/mtd/spi-nor/spansion.c | 4 +- drivers/mtd/spi-nor/swp.c | 52 ++++++++++++++++---------- drivers/mtd/spi-nor/winbond.c | 4 +- include/linux/mtd/spi-nor.h | 2 - 10 files changed, 98 insertions(+), 77 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 789f522b9b47..916e2a1698f2 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -83,6 +83,7 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor, struct spi_mem_op *op, const enum spi_nor_protocol proto) { + struct spi_nor_flash_parameter *params = nor->params; u8 ext; op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(proto); @@ -116,7 +117,7 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor, op->cmd.nbytes = 2; } - if (proto == SNOR_PROTO_8_8_8_DTR && nor->flags & SNOR_F_SWAP16) + if (proto == SNOR_PROTO_8_8_8_DTR && params->flags & SNOR_F_SWAP16) op->data.swap16 = true; } @@ -633,7 +634,7 @@ int spi_nor_sr_ready(struct spi_nor *nor) */ static bool spi_nor_use_parallel_locking(struct spi_nor *nor) { - return nor->flags & SNOR_F_RWW; + return nor->params->flags & SNOR_F_RWW; } /* Locking helpers for status read operations */ @@ -860,12 +861,13 @@ static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1) */ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) { + struct spi_nor_flash_parameter *params = nor->params; int ret; u8 *sr_cr = nor->bouncebuf; u8 cr_written; /* Make sure we don't overwrite the contents of Status Register 2. */ - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, &sr_cr[1]); if (ret) return ret; @@ -906,7 +908,7 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) return -EIO; } - if (nor->flags & SNOR_F_NO_READ_CR) + if (params->flags & SNOR_F_NO_READ_CR) return 0; cr_written = sr_cr[1]; @@ -961,7 +963,7 @@ int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) return -EIO; } - if (nor->flags & SNOR_F_NO_READ_CR) + if (nor->params->flags & SNOR_F_NO_READ_CR) return 0; ret = spi_nor_read_cr(nor, &sr_cr[1]); @@ -1009,7 +1011,7 @@ static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *re return -EIO; } - if (nor->flags & SNOR_F_NO_READ_CR) + if (nor->params->flags & SNOR_F_NO_READ_CR) return 0; ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]); @@ -1035,7 +1037,7 @@ static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *re */ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) { - if (nor->flags & SNOR_F_HAS_16BIT_SR) + if (nor->params->flags & SNOR_F_HAS_16BIT_SR) return spi_nor_write_16bit_sr_and_check(nor, sr1); return spi_nor_write_sr1_and_check(nor, sr1); @@ -1052,7 +1054,7 @@ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) */ int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) { - if (nor->flags & SNOR_F_HAS_16BIT_SR) + if (nor->params->flags & SNOR_F_HAS_16BIT_SR) return spi_nor_write_16bit_sr_cr_and_check(nor, regs); return spi_nor_write_sr1_and_check(nor, regs[0]); @@ -1821,6 +1823,7 @@ static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr, static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) { struct spi_nor *nor = mtd_to_spi_nor(mtd); + struct spi_nor_flash_parameter *params = nor->params; u8 n_dice = nor->params->n_dice; bool multi_die_erase = false; u32 addr, len, rem; @@ -1852,7 +1855,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) return ret; /* chip (die) erase? */ - if ((len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) || + if ((len == mtd->size && !(params->flags & SNOR_F_NO_OP_CHIP_ERASE)) || multi_die_erase) { ret = spi_nor_erase_dice(nor, addr, len, die_size); if (ret) @@ -1942,7 +1945,7 @@ int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor) { int ret; - if (nor->flags & SNOR_F_NO_READ_CR) + if (nor->params->flags & SNOR_F_NO_READ_CR) return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1); ret = spi_nor_read_cr(nor, nor->bouncebuf); @@ -2502,7 +2505,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) * If the reset line is broken, we do not want to enter a stateful * mode. */ - if (nor->flags & SNOR_F_BROKEN_RESET) + if (params->flags & SNOR_F_BROKEN_RESET) *hwcaps &= ~(SNOR_HWCAPS_X_X_X | SNOR_HWCAPS_X_X_X_DTR); for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) { @@ -2526,13 +2529,13 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) } /* Some SPI controllers might not support CR read opcode. */ - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { struct spi_mem_op op = SPI_NOR_RDCR_OP(nor->bouncebuf); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); if (!spi_mem_supports_op(nor->spimem, &op)) - nor->flags |= SNOR_F_NO_READ_CR; + params->flags |= SNOR_F_NO_READ_CR; } } @@ -2749,6 +2752,8 @@ static int spi_nor_select_erase(struct spi_nor *nor) static int spi_nor_set_addr_nbytes(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; + if (nor->params->addr_nbytes) { nor->addr_nbytes = nor->params->addr_nbytes; } else if (nor->read_proto == SNOR_PROTO_8_8_8_DTR) { @@ -2783,8 +2788,8 @@ static int spi_nor_set_addr_nbytes(struct spi_nor *nor) } /* Set 4byte opcodes when possible. */ - if (nor->addr_nbytes == 4 && nor->flags & SNOR_F_4B_OPCODES && - !(nor->flags & SNOR_F_HAS_4BAIT)) + if (nor->addr_nbytes == 4 && params->flags & SNOR_F_4B_OPCODES && + !(params->flags & SNOR_F_HAS_4BAIT)) spi_nor_set_4byte_opcodes(nor); return 0; @@ -2947,39 +2952,40 @@ static void spi_nor_no_sfdp_init_params(struct spi_nor *nor) */ static void spi_nor_init_flags(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; struct device_node *np = spi_nor_get_flash_node(nor); const u16 flags = nor->info->flags; if (of_property_read_bool(np, "broken-flash-reset")) - nor->flags |= SNOR_F_BROKEN_RESET; + params->flags |= SNOR_F_BROKEN_RESET; if (of_property_read_bool(np, "no-wp")) - nor->flags |= SNOR_F_NO_WP; + params->flags |= SNOR_F_NO_WP; if (flags & SPI_NOR_SWP_IS_VOLATILE) - nor->flags |= SNOR_F_SWP_IS_VOLATILE; + params->flags |= SNOR_F_SWP_IS_VOLATILE; if (flags & SPI_NOR_HAS_LOCK) - nor->flags |= SNOR_F_HAS_LOCK; + params->flags |= SNOR_F_HAS_LOCK; if (flags & SPI_NOR_HAS_TB) { - nor->flags |= SNOR_F_HAS_SR_TB; + params->flags |= SNOR_F_HAS_SR_TB; if (flags & SPI_NOR_TB_SR_BIT6) - nor->flags |= SNOR_F_HAS_SR_TB_BIT6; + params->flags |= SNOR_F_HAS_SR_TB_BIT6; } if (flags & SPI_NOR_4BIT_BP) { - nor->flags |= SNOR_F_HAS_4BIT_BP; + params->flags |= SNOR_F_HAS_4BIT_BP; if (flags & SPI_NOR_BP3_SR_BIT6) - nor->flags |= SNOR_F_HAS_SR_BP3_BIT6; + params->flags |= SNOR_F_HAS_SR_BP3_BIT6; } if (flags & SPI_NOR_HAS_CMP) - nor->flags |= SNOR_F_HAS_SR2_CMP_BIT6; + params->flags |= SNOR_F_HAS_SR2_CMP_BIT6; if (flags & SPI_NOR_RWW && nor->params->n_banks > 1 && !nor->controller_ops) - nor->flags |= SNOR_F_RWW; + params->flags |= SNOR_F_RWW; } /** @@ -2992,13 +2998,14 @@ static void spi_nor_init_flags(struct spi_nor *nor) */ static void spi_nor_init_fixup_flags(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; const u8 fixup_flags = nor->info->fixup_flags; if (fixup_flags & SPI_NOR_4B_OPCODES) - nor->flags |= SNOR_F_4B_OPCODES; + params->flags |= SNOR_F_4B_OPCODES; if (fixup_flags & SPI_NOR_IO_MODE_EN_VOLATILE) - nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; } /** @@ -3043,7 +3050,7 @@ static int spi_nor_late_init_params(struct spi_nor *nor) * NOR protection support. When locking_ops are not provided, we pick * the default ones. */ - if (nor->flags & SNOR_F_HAS_LOCK && !nor->params->locking_ops) + if (params->flags & SNOR_F_HAS_LOCK && !nor->params->locking_ops) spi_nor_init_default_locking_ops(nor); if (params->n_banks > 1) @@ -3108,7 +3115,7 @@ static void spi_nor_init_default_params(struct spi_nor *nor) params->otp.org = info->otp; /* Default to 16-bit Write Status (01h) Command */ - nor->flags |= SNOR_F_HAS_16BIT_SR; + params->flags |= SNOR_F_HAS_16BIT_SR; /* Set SPI NOR sizes. */ params->writesize = 1; @@ -3229,7 +3236,7 @@ static int spi_nor_set_octal_dtr(struct spi_nor *nor, bool enable) nor->write_proto == SNOR_PROTO_8_8_8_DTR)) return 0; - if (!(nor->flags & SNOR_F_IO_MODE_EN_VOLATILE)) + if (!(nor->params->flags & SNOR_F_IO_MODE_EN_VOLATILE)) return 0; ret = nor->params->set_octal_dtr(nor, enable); @@ -3282,7 +3289,7 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) * reboots (e.g., crashes). Warn the user (or hopefully, system * designer) that this is bad. */ - WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET, + WARN_ONCE(nor->params->flags & SNOR_F_BROKEN_RESET, "enabling reset hack; may not recover from unexpected reboots\n"); } @@ -3303,6 +3310,7 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) static int spi_nor_init(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; int err; err = spi_nor_set_octal_dtr(nor, true); @@ -3330,13 +3338,13 @@ static int spi_nor_init(struct spi_nor *nor) spi_nor_cache_sr_lock_bits(nor, NULL); if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) || (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) && - nor->flags & SNOR_F_SWP_IS_VOLATILE)) { + params->flags & SNOR_F_SWP_IS_VOLATILE)) { spi_nor_try_unlock_all(nor); } if (nor->addr_nbytes == 4 && nor->read_proto != SNOR_PROTO_8_8_8_DTR && - !(nor->flags & SNOR_F_4B_OPCODES)) + !(params->flags & SNOR_F_4B_OPCODES)) return spi_nor_set_4byte_addr_mode(nor, true); return 0; @@ -3451,11 +3459,12 @@ static void spi_nor_put_device(struct mtd_info *mtd) static void spi_nor_restore(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; int ret; /* restore the addressing mode */ - if (nor->addr_nbytes == 4 && !(nor->flags & SNOR_F_4B_OPCODES) && - nor->flags & SNOR_F_BROKEN_RESET) { + if (nor->addr_nbytes == 4 && !(params->flags & SNOR_F_4B_OPCODES) && + params->flags & SNOR_F_BROKEN_RESET) { ret = spi_nor_set_4byte_addr_mode(nor, false); if (ret) /* @@ -3466,7 +3475,7 @@ static void spi_nor_restore(struct spi_nor *nor) dev_err(nor->dev, "Failed to exit 4-byte address mode, err = %d\n", ret); } - if (nor->flags & SNOR_F_SOFT_RESET) + if (params->flags & SNOR_F_SOFT_RESET) spi_nor_soft_reset(nor); } @@ -3583,7 +3592,7 @@ static int spi_nor_set_mtd_info(struct spi_nor *nor) mtd->type = MTD_NORFLASH; mtd->flags = MTD_CAP_NORFLASH; /* Unset BIT_WRITEABLE to enable JFFS2 write buffer for ECC'd NOR */ - if (nor->flags & SNOR_F_ECC) + if (nor->params->flags & SNOR_F_ECC) mtd->flags &= ~MTD_BIT_WRITEABLE; if (nor->info->flags & SPI_NOR_NO_ERASE) mtd->flags |= MTD_NO_ERASE; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 6a77ea2901a3..cc6601f51e63 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -348,6 +348,7 @@ struct spi_nor_otp { * * @bank_size: the flash memory bank density in bytes. * @size: the total flash memory density in bytes. + * @flags: flag options for the current SPI NOR (SNOR_F_*) * @writesize Minimal writable flash unit size. Defaults to 1. Set to * ECC unit size for ECC-ed flashes. * @page_size: the page size of the SPI NOR flash memory. @@ -386,6 +387,7 @@ struct spi_nor_otp { struct spi_nor_flash_parameter { u64 bank_size; u64 size; + u32 flags; u32 writesize; u32 page_size; u8 addr_nbytes; diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 37738e2b262e..095085a723e7 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -99,7 +99,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) seq_printf(s, "address nbytes\t%u\n", nor->addr_nbytes); seq_puts(s, "flags\t\t"); - spi_nor_print_flags(s, nor->flags, snor_f_names, + spi_nor_print_flags(s, params->flags, snor_f_names, ARRAY_SIZE(snor_f_names)); seq_puts(s, "\n"); @@ -144,7 +144,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) } } - if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { + if (!(params->flags & SNOR_F_NO_OP_CHIP_ERASE)) { string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); } diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 332e09c8332c..9610d9e14e4c 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -40,7 +40,7 @@ mx25l25635_post_bfpt_fixups(struct spi_nor *nor, * its BFPT table. */ if (bfpt->dwords[SFDP_DWORD(5)] & BFPT_DWORD5_FAST_READ_4_4_4) - nor->flags |= SNOR_F_4B_OPCODES; + nor->params->flags |= SNOR_F_4B_OPCODES; return 0; } diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 186362066c6b..ca2ce18050df 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; return 0; } @@ -632,8 +632,8 @@ static int micron_st_nor_ready(struct spi_nor *nor) static void micron_st_nor_default_init(struct spi_nor *nor) { - nor->flags |= SNOR_F_HAS_LOCK; - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->flags |= SNOR_F_HAS_LOCK; + nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; nor->params->quad_enable = NULL; } diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 5b41ffec7374..49e6113557be 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -577,17 +577,17 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Read Configuration Register (35h) instruction is not * supported. */ - nor->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR; + params->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR; params->quad_enable = spi_nor_sr2_bit1_quad_enable; break; case BFPT_DWORD15_QER_SR1_BIT6: - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + params->flags &= ~SNOR_F_HAS_16BIT_SR; params->quad_enable = spi_nor_sr1_bit6_quad_enable; break; case BFPT_DWORD15_QER_SR2_BIT7: - nor->flags &= ~SNOR_F_HAS_16BIT_SR; + params->flags &= ~SNOR_F_HAS_16BIT_SR; params->quad_enable = spi_nor_sr2_bit7_quad_enable; break; @@ -598,7 +598,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Register 2, so let's be cautious and keep the default * assumption of a 16-bit Write Status (01h) command. */ - nor->flags |= SNOR_F_HAS_16BIT_SR; + params->flags |= SNOR_F_HAS_16BIT_SR; params->quad_enable = spi_nor_sr2_bit1_quad_enable; break; @@ -620,7 +620,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* Soft Reset support. */ if (bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_SWRST_EN_RST) - nor->flags |= SNOR_F_SOFT_RESET; + params->flags |= SNOR_F_SOFT_RESET; /* Stop here if not JESD216 rev C or later. */ if (bfpt_header->length == BFPT_DWORD_MAX_JESD216B) @@ -673,7 +673,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* Byte order in 8D-8D-8D mode */ if (bfpt.dwords[SFDP_DWORD(18)] & BFPT_DWORD18_BYTE_ORDER_SWAPPED) - nor->flags |= SNOR_F_SWAP16; + params->flags |= SNOR_F_SWAP16; return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt); } @@ -1160,7 +1160,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor, * SFDP compliant memories. */ params->addr_nbytes = 4; - nor->flags |= SNOR_F_4B_OPCODES | SNOR_F_HAS_4BAIT; + params->flags |= SNOR_F_4B_OPCODES | SNOR_F_HAS_4BAIT; /* fall through */ out: @@ -1306,7 +1306,7 @@ static int spi_nor_parse_sccr(struct spi_nor *nor, if (FIELD_GET(SCCR_DWORD22_OCTAL_DTR_EN_VOLATILE, dwords[SFDP_DWORD(22)])) - nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; out: kfree(dwords); diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 092450375d4e..fc5da04aff07 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -537,7 +537,7 @@ static void cypress_nor_ecc_init(struct spi_nor *nor) * same ECC data unit without an erase are not allowed. */ nor->params->writesize = 16; - nor->flags |= SNOR_F_ECC; + nor->params->flags |= SNOR_F_ECC; } static int @@ -1139,7 +1139,7 @@ static int spansion_nor_late_init(struct spi_nor *nor) u8 mfr_flags = nor->info->mfr_flags; if (params->size > SZ_16M) { - nor->flags |= SNOR_F_4B_OPCODES; + params->flags |= SNOR_F_4B_OPCODES; /* No small sector erase for 4-byte command set */ nor->erase_opcode = SPINOR_OP_SE; nor->mtd.erasesize = nor->info->sector_size ?: diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 235070b215d1..62720045a9ab 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -13,12 +13,13 @@ static u8 spi_nor_get_sr_bp_mask(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; u8 mask = SR_BP2 | SR_BP1 | SR_BP0; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6) + if (params->flags & SNOR_F_HAS_SR_BP3_BIT6) return mask | SR_BP3_BIT6; - if (nor->flags & SNOR_F_HAS_4BIT_BP) + if (params->flags & SNOR_F_HAS_4BIT_BP) return mask | SR_BP3; return mask; @@ -26,9 +27,11 @@ static u8 spi_nor_get_sr_bp_mask(struct spi_nor *nor) static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) { - if (nor->flags & SNOR_F_HAS_SR_TB_BIT6) + struct spi_nor_flash_parameter *params = nor->params; + + if (params->flags & SNOR_F_HAS_SR_TB_BIT6) return SR_TB_BIT6; - else if (nor->flags & SNOR_F_HAS_SR_TB) + else if (params->flags & SNOR_F_HAS_SR_TB) return SR_TB_BIT5; else return 0; @@ -36,8 +39,10 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor) { - if (!(nor->flags & SNOR_F_NO_READ_CR) && - nor->flags & SNOR_F_HAS_SR2_CMP_BIT6) + struct spi_nor_flash_parameter *params = nor->params; + + if (!(params->flags & SNOR_F_NO_READ_CR) && + params->flags & SNOR_F_HAS_SR2_CMP_BIT6) return SR2_CMP_BIT6; else return 0; @@ -67,15 +72,16 @@ u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, u64 *len) { + struct spi_nor_flash_parameter *params = nor->params; u64 min_prot_len; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor); u8 bp, val = sr[0] & bp_mask; - bool tb = (nor->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0; + bool tb = (params->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0; bool cmp = sr[1] & cmp_mask; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) + if (params->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; bp = val >> SR_BP_SHIFT; @@ -153,10 +159,11 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow) { + struct spi_nor_flash_parameter *params = nor->params; u8 mask = spi_nor_get_sr_bp_mask(nor); u8 val = pow << SR_BP_SHIFT; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) + if (params->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) val = (val & ~SR_BP3) | SR_BP3_BIT6; if (val & ~mask) @@ -200,6 +207,7 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, */ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) { + struct spi_nor_flash_parameter *params = nor->params; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor); @@ -212,7 +220,7 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) sr_cr[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { if (spi_nor_read_cr(nor, nor->bouncebuf)) return; } @@ -261,13 +269,14 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) */ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) { + struct spi_nor_flash_parameter *params = nor->params; u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {}; u8 *best_status_new = status_new; loff_t ofs_old, ofs_new, ofs_new_cmp; u64 len_old, len_new, len_new_cmp; loff_t lock_len; - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, + bool can_be_top = true, can_be_bottom = params->flags & SNOR_F_HAS_SR_TB, can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; @@ -279,7 +288,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) status_old[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, nor->bouncebuf); if (ret) return ret; @@ -313,7 +322,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) lock_len = ofs + len; if (lock_len == nor->params->size) - pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); + pow = (params->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; @@ -359,7 +368,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) * wrongly tied to GND (that includes internal pull-downs). * WP# pin hard strapped to GND can be a valid use case. */ - if (!(nor->flags & SNOR_F_NO_WP)) + if (!(params->flags & SNOR_F_NO_WP)) best_status_new[0] |= SR_SRWD; spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); @@ -378,7 +387,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old))) return -EINVAL; - if (nor->flags & SNOR_F_NO_READ_CR) + if (params->flags & SNOR_F_NO_READ_CR) ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); else ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); @@ -397,13 +406,14 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) */ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) { + struct spi_nor_flash_parameter *params = nor->params; u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {}; u8 *best_status_new = status_new; loff_t ofs_old, ofs_new, ofs_new_cmp; u64 len_old, len_new, len_new_cmp; loff_t lock_len; - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB, + bool can_be_top = true, can_be_bottom = params->flags & SNOR_F_HAS_SR_TB, can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; @@ -415,7 +425,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) status_old[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, nor->bouncebuf); if (ret) return ret; @@ -512,7 +522,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; - if (nor->flags & SNOR_F_NO_READ_CR) + if (params->flags & SNOR_F_NO_READ_CR) ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); else ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); @@ -533,6 +543,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) */ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) { + struct spi_nor_flash_parameter *params = nor->params; u8 sr_cr[2] = {}; int ret; @@ -542,7 +553,7 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) sr_cr[0] = nor->bouncebuf[0]; - if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (!(params->flags & SNOR_F_NO_READ_CR)) { ret = spi_nor_read_cr(nor, nor->bouncebuf); if (ret) return ret; @@ -628,9 +639,10 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, u64 len) */ void spi_nor_try_unlock_all(struct spi_nor *nor) { + struct spi_nor_flash_parameter *params = nor->params; int ret; - if (!(nor->flags & SNOR_F_HAS_LOCK)) + if (!(params->flags & SNOR_F_HAS_LOCK)) return; dev_dbg(nor->dev, "Unprotecting entire flash array\n"); diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 8ebdbcec0b3f..6b99d9488c40 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -64,7 +64,7 @@ w25q256_post_bfpt_fixups(struct spi_nor *nor, */ if (bfpt_header->major == SFDP_JESD216_MAJOR && bfpt_header->minor == SFDP_JESD216A_MINOR) - nor->flags |= SNOR_F_4B_OPCODES; + nor->params->flags |= SNOR_F_4B_OPCODES; return 0; } @@ -84,7 +84,7 @@ winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor, * both incorrect and impractical, as the chip features a CMP bit for its * locking scheme that lays in the Control Register, and needs to be read. */ - nor->flags &= ~SNOR_F_NO_READ_CR; + nor->params->flags &= ~SNOR_F_NO_READ_CR; return 0; } diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index e75c1b4cb4c0..099c5016b485 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -365,7 +365,6 @@ struct spi_nor_flash_parameter; * @read_dummy: the dummy needed by the read operation * @program_opcode: the program opcode * @sst_write_second: used by the SST write operation - * @flags: flag options for the current SPI NOR (SNOR_F_*) * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations @@ -406,7 +405,6 @@ struct spi_nor { enum spi_nor_protocol write_proto; enum spi_nor_protocol reg_proto; bool sst_write_second; - u32 flags; struct sfdp *sfdp; struct dentry *debugfs_root; u8 dfs_sr_cache[2]; -- cgit v1.2.3 From 69d9d7dabbebd91cd8aba4e23caabe9b34d2722c Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:43 +0200 Subject: mtd: spi-nor: move spi_nor_post_bfpt_fixups() into sfdp Move spi_nor_post_bfpt_fixups() into the sfdp module because it's really only needed there. While at it, add some documentation. Reviewed-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 20 -------------------- drivers/mtd/spi-nor/core.h | 4 ---- drivers/mtd/spi-nor/sfdp.c | 31 +++++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 916e2a1698f2..7ea1661a3b77 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2581,26 +2581,6 @@ void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, map->n_regions = 1; } -int spi_nor_post_bfpt_fixups(struct spi_nor *nor, - const struct sfdp_parameter_header *bfpt_header, - const struct sfdp_bfpt *bfpt) -{ - int ret; - - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->post_bfpt) { - ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header, - bfpt); - if (ret) - return ret; - } - - if (nor->info->fixups && nor->info->fixups->post_bfpt) - return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt); - - return 0; -} - static int spi_nor_select_read(struct spi_nor *nor, u32 shared_hwcaps) { diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index cc6601f51e63..01bbdc79922e 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -684,10 +684,6 @@ void spi_nor_mask_erase_type(struct spi_nor_erase_type *erase); void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map, u8 erase_mask, u64 flash_size); -int spi_nor_post_bfpt_fixups(struct spi_nor *nor, - const struct sfdp_parameter_header *bfpt_header, - const struct sfdp_bfpt *bfpt); - void spi_nor_init_default_locking_ops(struct spi_nor *nor); bool spi_nor_has_default_locking_ops(struct spi_nor *nor); void spi_nor_try_unlock_all(struct spi_nor *nor); diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 49e6113557be..7b569e15b9be 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -401,6 +401,37 @@ static void spi_nor_regions_sort_erase_types(struct spi_nor_erase_map *map) } } +/** + * spi_nor_post_bfpt_fixups() - Updates the flash's parameters and settings + * after BFPT has been parsed. Called only for flashes that define JESD216 SFDP + * tables. + * @nor: pointer to a 'struct spi_nor' + * @bfpt_header: pointer to the 'struct sfdp_parameter_header' describing + * the Basic Flash Parameter Table length and version + * + * Used to tweak various flash parameters when information provided by the SFDP + * tables are wrong. + */ +static int spi_nor_post_bfpt_fixups(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt) +{ + int ret; + + if (nor->manufacturer && nor->manufacturer->fixups && + nor->manufacturer->fixups->post_bfpt) { + ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header, + bfpt); + if (ret) + return ret; + } + + if (nor->info->fixups && nor->info->fixups->post_bfpt) + return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt); + + return 0; +} + /** * spi_nor_parse_bfpt() - read and parse the Basic Flash Parameter Table. * @nor: pointer to a 'struct spi_nor' -- cgit v1.2.3 From d395d4b8e76b1dfdb2f46b29c62e7f1f4850c9b2 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:44 +0200 Subject: mtd: spi-nor: spansion: s25fs256t: move ARCFN check into .late_init The SFPD parsing is optional. It not meant to fail if something is wrong with the chip (except for the SFDP itself). Thus it doesn't make any sense to return -ENODEV. Move that check to late_init(), which might fail and abort the flash probing. Tested-by: Takahiro Kuwano Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/spansion.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index fc5da04aff07..40d3236c723b 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -545,7 +545,6 @@ s25fs256t_post_bfpt_fixup(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { - struct spi_mem_op op; int ret; /* Assign 4-byte address mode method that is not determined in BFPT */ @@ -555,19 +554,6 @@ s25fs256t_post_bfpt_fixup(struct spi_nor *nor, if (ret) return ret; - /* Read Architecture Configuration Register (ARCFN) */ - op = (struct spi_mem_op) - CYPRESS_NOR_RD_ANY_REG_OP(nor->params->addr_mode_nbytes, - SPINOR_REG_CYPRESS_ARCFN, 1, - nor->bouncebuf); - ret = spi_nor_read_any_reg(nor, &op, nor->reg_proto); - if (ret) - return ret; - - /* ARCFN value must be 0 if uniform sector is selected */ - if (nor->bouncebuf[0]) - return -ENODEV; - return 0; } @@ -598,6 +584,22 @@ static int s25fs256t_post_sfdp_fixup(struct spi_nor *nor) static int s25fs256t_late_init(struct spi_nor *nor) { + struct spi_mem_op op; + int ret; + + /* Read Architecture Configuration Register (ARCFN) */ + op = (struct spi_mem_op) + CYPRESS_NOR_RD_ANY_REG_OP(nor->params->addr_mode_nbytes, + SPINOR_REG_CYPRESS_ARCFN, 1, + nor->bouncebuf); + ret = spi_nor_read_any_reg(nor, &op, nor->reg_proto); + if (ret) + return ret; + + /* ARCFN value must be 0 if uniform sector is selected */ + if (nor->bouncebuf[0]) + return -ENODEV; + cypress_nor_ecc_init(nor); return 0; -- cgit v1.2.3 From d20029474a76a06a2a92427453c2326cc3e98dfa Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:45 +0200 Subject: mtd: spi-nor: push the rollback mechanism into the sfdp module Right now, the core is handling the rollback of the parameters. But it doesn't have the knowledge what has to be rolled back. And in fact, havent rolled back everything. Push it down to the called function and make it mandatory, that this function has no side effects if it fails. Funny enough, there is a comment in the SFDP table handling code that each table parser is responsible to roll back any changes. But none of them did. So while add it, expand the logic to that and roll it back for them. There is one simple rule though: SFDP parsing and fixups may only modify the spi_nor_flash_parameters. Reviewed-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 20 +------------------- drivers/mtd/spi-nor/sfdp.c | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 7ea1661a3b77..7cce0ee3e836 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3039,24 +3039,6 @@ static int spi_nor_late_init_params(struct spi_nor *nor) return 0; } -/** - * spi_nor_sfdp_init_params_deprecated() - Deprecated way of initializing flash - * parameters and settings based on JESD216 SFDP standard. - * @nor: pointer to a 'struct spi_nor'. - * - * The method has a roll-back mechanism: in case the SFDP parsing fails, the - * legacy flash parameters and settings will be restored. - */ -static void spi_nor_sfdp_init_params_deprecated(struct spi_nor *nor) -{ - struct spi_nor_flash_parameter sfdp_params; - - memcpy(&sfdp_params, nor->params, sizeof(sfdp_params)); - - if (spi_nor_parse_sfdp(nor)) - memcpy(nor->params, &sfdp_params, sizeof(*nor->params)); -} - /** * spi_nor_init_params_deprecated() - Deprecated way of initializing flash * parameters and settings. @@ -3076,7 +3058,7 @@ static void spi_nor_init_params_deprecated(struct spi_nor *nor) SPI_NOR_QUAD_READ | SPI_NOR_OCTAL_READ | SPI_NOR_OCTAL_DTR_READ)) - spi_nor_sfdp_init_params_deprecated(nor); + spi_nor_parse_sfdp(nor); } /** diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 7b569e15b9be..69656e9050a7 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1464,12 +1464,16 @@ int spi_nor_check_sfdp_signature(struct spi_nor *nor) * runtime the main parameters needed to perform basic SPI flash operations such * as Fast Read, Page Program or Sector Erase commands. * + * Because the parsing is optional, all the settings have to be reverted. IOW, + * nothing of struct spi_nor shall be changed. + * * Return: 0 on success, -errno otherwise. */ int spi_nor_parse_sfdp(struct spi_nor *nor) { const struct sfdp_parameter_header *param_header, *bfpt_header; struct sfdp_parameter_header *param_headers = NULL; + struct spi_nor_flash_parameter params, params2; struct sfdp_header header; struct device *dev = nor->dev; struct sfdp *sfdp; @@ -1477,6 +1481,12 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) size_t psize; int i, err; + /* + * Get a backup of all the parameter to roll back to in case of an + * error. + */ + memcpy(¶ms, nor->params, sizeof(params)); + /* Get the SFDP header. */ err = spi_nor_read_sfdp_dma_unsafe(nor, 0, sizeof(header), &header); if (err < 0) @@ -1598,6 +1608,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) /* Parse optional parameter tables. */ for (i = 0; i < header.nph; i++) { + memcpy(¶ms2, nor->params, sizeof(params2)); param_header = ¶m_headers[i]; switch (SFDP_PARAM_HEADER_ID(param_header)) { @@ -1631,15 +1642,19 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) /* * Let's not drop all information we extracted so far * if optional table parsers fail. In case of failing, - * each optional parser is responsible to roll back to - * the previously known spi_nor data. + * roll back to the previously known + * spi_nor_flash_parameter data. */ err = 0; + memcpy(nor->params, ¶ms2, sizeof(*nor->params)); } } err = spi_nor_post_sfdp_fixups(nor); exit: kfree(param_headers); + if (err) + memcpy(nor->params, ¶ms, sizeof(*nor->params)); + return err; } -- cgit v1.2.3 From 90a8ed3c63b905dcd375585da6f4851d88cc343d Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:46 +0200 Subject: mtd: spi-nor: rework flash parameter initialization Rework how the flash parameters are initialized. It used to make a difference whether a flash is in the in-kernel database, whether it supports multi I/O and so on. Recently, that makes more and more problems because flash IDs are reused among legacy flashes (w/o SFDP support) and newer flashes [1,2,3,4]. Simplify the whole parameter initialization, by starting with the parameters we have in the in-kernel database and the try to parse SFDP (and let it override any settings). This has the consequence that all the flashes now get a RDSFPD opcode which might be an unknown opcode from the perspective of the flash. With the generic SPI NOR flash driver, this was already the case for any flashes not listed in the in-kernel database. So far, there was not a single complaint. [1] https://lore.kernel.org/linux-mtd/CAAyq3SYX9UPwhC_Ume_S2yxhQwimRvB=Y6O_+FFqokhNmw7jQg@mail.gmail.com/ [2] https://lore.kernel.org/linux-mtd/DGB4745HRCFI.1DRYTHXURWZJO@kernel.org/ [3] https://lore.kernel.org/linux-mtd/DD10GE4EOCD7.CPTN7198QFUV@kernel.org/ [4] https://lore.kernel.org/linux-mtd/DD6SI06QNEE4.2YCRTWJHEAAQM@kernel.org/ Tested-by: Cheng Ming Lin Tested-by: Miquel Raynal # w/ W25Q02JVFIM Tested-by: Takahiro Kuwano # w/ s25fl128s0, s25fl128s1, s25fl256s0 Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 53 ++++++++++++---------------------------------- drivers/mtd/spi-nor/core.h | 5 +---- 2 files changed, 15 insertions(+), 43 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 7cce0ee3e836..2f36e0b9cd30 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2853,11 +2853,10 @@ static void spi_nor_manufacturer_init_params(struct spi_nor *nor) /** * spi_nor_no_sfdp_init_params() - Initialize the flash's parameters and - * settings based on nor->info->sfdp_flags. This method should be called only by - * flashes that do not define SFDP tables. If the flash supports SFDP but the - * information is wrong and the settings from this function can not be retrieved - * by parsing SFDP, one should instead use the fixup hooks and update the wrong - * bits. + * settings based on nor->info->sfdp_flags. + * If the flash supports SFDP but the information is wrong and the settings from + * this function can not be retrieved by parsing SFDP, one should instead use + * the fixup hooks and update the wrong bits. * @nor: pointer to a 'struct spi_nor'. */ static void spi_nor_no_sfdp_init_params(struct spi_nor *nor) @@ -3039,28 +3038,6 @@ static int spi_nor_late_init_params(struct spi_nor *nor) return 0; } -/** - * spi_nor_init_params_deprecated() - Deprecated way of initializing flash - * parameters and settings. - * @nor: pointer to a 'struct spi_nor'. - * - * The method assumes that flash doesn't support SFDP so it initializes flash - * parameters in spi_nor_no_sfdp_init_params() which later on can be overwritten - * when parsing SFDP, if supported. - */ -static void spi_nor_init_params_deprecated(struct spi_nor *nor) -{ - spi_nor_no_sfdp_init_params(nor); - - spi_nor_manufacturer_init_params(nor); - - if (nor->info->no_sfdp_flags & (SPI_NOR_DUAL_READ | - SPI_NOR_QUAD_READ | - SPI_NOR_OCTAL_READ | - SPI_NOR_OCTAL_DTR_READ)) - spi_nor_parse_sfdp(nor); -} - /** * spi_nor_init_default_params() - Default initialization of flash parameters * and settings. Done for all flashes, regardless is they define SFDP tables @@ -3121,7 +3098,8 @@ static void spi_nor_init_default_params(struct spi_nor *nor) * * 1/ Default flash parameters initialization. The initializations are done * based on nor->info data: - * spi_nor_info_init_params() + * spi_nor_init_default_params() + * spi_nor_no_sfdp_init_params() * * which can be overwritten by: * 2/ Manufacturer flash parameters initialization. The initializations are @@ -3132,7 +3110,7 @@ static void spi_nor_init_default_params(struct spi_nor *nor) * which can be overwritten by: * 3/ SFDP flash parameters initialization. JESD216 SFDP is a standard and * should be more accurate that the above. - * spi_nor_parse_sfdp() or spi_nor_no_sfdp_init_params() + * spi_nor_parse_sfdp() * * Please note that there is a ->post_bfpt() fixup hook that can overwrite * the flash parameters and settings immediately after parsing the Basic @@ -3158,17 +3136,14 @@ static int spi_nor_init_params(struct spi_nor *nor) return -ENOMEM; spi_nor_init_default_params(nor); + spi_nor_no_sfdp_init_params(nor); + spi_nor_manufacturer_init_params(nor); - if (spi_nor_needs_sfdp(nor)) { - ret = spi_nor_parse_sfdp(nor); - if (ret) { - dev_err(nor->dev, "BFPT parsing failed. Please consider using SPI_NOR_SKIP_SFDP when declaring the flash\n"); - return ret; - } - } else if (nor->info->no_sfdp_flags & SPI_NOR_SKIP_SFDP) { - spi_nor_no_sfdp_init_params(nor); - } else { - spi_nor_init_params_deprecated(nor); + ret = spi_nor_parse_sfdp(nor); + if (ret && spi_nor_needs_sfdp(nor)) { + dev_err(nor->dev, + "SFDP parsing failed. You need to manually declare the flash parameters.\n"); + return ret; } ret = spi_nor_late_init_params(nor); diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 01bbdc79922e..26ea154adfc1 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -500,9 +500,7 @@ struct spi_nor_id { * be used with SPI_NOR_HAS_LOCK. * * @no_sfdp_flags: flags that indicate support that can be discovered via SFDP. - * Used when SFDP tables are not defined in the flash. These - * flags are used together with the SPI_NOR_SKIP_SFDP flag. - * SPI_NOR_SKIP_SFDP: skip parsing of SFDP tables. + * Used when SFDP tables are not defined in the flash. * SECT_4K: SPINOR_OP_BE_4K works uniformly. * SPI_NOR_DUAL_READ: flash supports Dual Read. * SPI_NOR_QUAD_READ: flash supports Quad Read. @@ -549,7 +547,6 @@ struct flash_info { #define SPI_NOR_HAS_CMP BIT(10) u8 no_sfdp_flags; -#define SPI_NOR_SKIP_SFDP BIT(0) #define SECT_4K BIT(1) #define SPI_NOR_DUAL_READ BIT(3) #define SPI_NOR_QUAD_READ BIT(4) -- cgit v1.2.3 From b9f34a95a81021fa1f3c9cb8cfd458e66824cad6 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Mon, 20 Jul 2026 23:11:03 +0900 Subject: mtd: spi-nor: sfdp: check the length of the xSPI Profile 1.0 table The spi_nor_parse_profile1() sizes its buffer from the table length the flash reports in the SFDP parameter header. But it then reads DWORD1, DWORD4 and DWORD5 without ever checking the table is that long. So if a flash reports a length of one, the buffer is only four bytes while DWORD4 and DWORD5 sit at byte offsets 12 and 16. With a length of zero kmalloc() returns ZERO_SIZE_PTR rather than an error, so the NULL check doesn't catch it and the first read dereferences it. And the value doesn't just get thrown away. It ends up as the dummy cycle count for 8D-8D-8D fast reads. To fix this, reject a table that's too short for the highest DWORD the parser reads, the way spi_nor_parse_4bait() already does. The table is optional, so this isn't fatal. The spi_nor_parse_sfdp() warns and carries on. Fixes: fb27f198971a ("mtd: spi-nor: sfdp: parse xSPI Profile 1.0 table") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: HyeongJun An Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 69656e9050a7..372c08164612 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1206,6 +1206,7 @@ out: #define PROFILE1_DWORD5_DUMMY_166MHZ GENMASK(31, 27) #define PROFILE1_DWORD5_DUMMY_133MHZ GENMASK(21, 17) #define PROFILE1_DWORD5_DUMMY_100MHZ GENMASK(11, 7) +#define SFDP_PROFILE1_DWORD_MIN 5 /** * spi_nor_parse_profile1() - parse the xSPI Profile 1.0 table @@ -1223,6 +1224,9 @@ static int spi_nor_parse_profile1(struct spi_nor *nor, int ret; u8 dummy, opcode; + if (profile1_header->length < SFDP_PROFILE1_DWORD_MIN) + return -EINVAL; + len = profile1_header->length * sizeof(*dwords); dwords = kmalloc(len, GFP_KERNEL); if (!dwords) -- cgit v1.2.3 From 1413f38f37194673502e61cd3749e091c031370b Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Mon, 20 Jul 2026 23:11:04 +0900 Subject: mtd: spi-nor: sfdp: check the length of the SCCR map The spi_nor_parse_sccr() sizes its buffer from the table length the flash reports in the SFDP parameter header. But it then reads DWORD1 and DWORD22 without ever checking the table is that long. So if a flash reports a length of one, the buffer is only four bytes while DWORD22 sits at byte offset 84. With a length of zero kmalloc() returns ZERO_SIZE_PTR rather than an error, so the NULL check doesn't catch it and the first read dereferences it. To fix this, reject a table that's too short for the highest DWORD the parser reads, the way spi_nor_parse_4bait() already does. The table is optional, so this isn't fatal. The spi_nor_parse_sfdp() warns and carries on. The spi_nor_parse_sccr_mc() doesn't need the same check. It works out the number of dice from the length it allocated with, so its highest index stays inside the buffer. Fixes: 7ab8b810757a ("mtd: spi-nor: sfdp: Add support for SCCR map for multi-chip device") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: HyeongJun An Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 372c08164612..2a1ed17fcc62 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1297,6 +1297,7 @@ out: } #define SCCR_DWORD22_OCTAL_DTR_EN_VOLATILE BIT(31) +#define SFDP_SCCR_DWORD_MIN 22 /** * spi_nor_parse_sccr() - Parse the Status, Control and Configuration Register @@ -1315,6 +1316,9 @@ static int spi_nor_parse_sccr(struct spi_nor *nor, size_t len; int ret; + if (sccr_header->length < SFDP_SCCR_DWORD_MIN) + return -EINVAL; + len = sccr_header->length * sizeof(*dwords); dwords = kmalloc(len, GFP_KERNEL); if (!dwords) -- cgit v1.2.3 From c63c890a12c0e5959190a486f1b8409f90d35f6c Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Fri, 31 Jul 2026 16:57:19 +0900 Subject: mtd: spi-nor: remove unused NO_OP_CHIP_ERASE flag NO_OP_CHIP_ERASE flag is no longer used. Remove definition and references of the flag in core and debugfs. Reviewed-by: Tudor Ambarus Signed-off-by: Takahiro Kuwano Reviewed-by: Miquel Raynal Reviewed-by: Michael Walle [mw: rebased onto spi-nor/next] Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 4 +--- drivers/mtd/spi-nor/core.h | 6 ++++-- drivers/mtd/spi-nor/debugfs.c | 7 ++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 2f36e0b9cd30..69b77191c498 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1823,7 +1823,6 @@ static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr, static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) { struct spi_nor *nor = mtd_to_spi_nor(mtd); - struct spi_nor_flash_parameter *params = nor->params; u8 n_dice = nor->params->n_dice; bool multi_die_erase = false; u32 addr, len, rem; @@ -1855,8 +1854,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) return ret; /* chip (die) erase? */ - if ((len == mtd->size && !(params->flags & SNOR_F_NO_OP_CHIP_ERASE)) || - multi_die_erase) { + if (len == mtd->size || multi_die_erase) { ret = spi_nor_erase_dice(nor, addr, len, die_size); if (ret) goto erase_err; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 26ea154adfc1..d59ec9cad986 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -121,10 +121,12 @@ SPI_MEM_OP_NO_ADDR, \ SPI_MEM_OP_NO_DATA) -/* Keep these in sync with the list in debugfs.c */ +/* + * This could contain holes, if adding a new flag use the first free spot. + * Keep the flags in sync with the list in debugfs.c + */ enum spi_nor_option_flags { SNOR_F_HAS_SR_TB = BIT(0), - SNOR_F_NO_OP_CHIP_ERASE = BIT(1), SNOR_F_BROKEN_RESET = BIT(2), SNOR_F_4B_OPCODES = BIT(3), SNOR_F_HAS_4BAIT = BIT(4), diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 095085a723e7..437a0e0cb4dd 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -14,7 +14,6 @@ #define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name static const char *const snor_f_names[] = { SNOR_F_NAME(HAS_SR_TB), - SNOR_F_NAME(NO_OP_CHIP_ERASE), SNOR_F_NAME(BROKEN_RESET), SNOR_F_NAME(4B_OPCODES), SNOR_F_NAME(HAS_4BAIT), @@ -144,10 +143,8 @@ static int spi_nor_params_show(struct seq_file *s, void *data) } } - if (!(params->flags & SNOR_F_NO_OP_CHIP_ERASE)) { - string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); - seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); - } + string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); seq_puts(s, "\nsector map\n"); seq_puts(s, " region (in hex) | erase mask | overlaid\n"); -- cgit v1.2.3 From 3c2464c70284eb5443a18ea7ee2f815a06d91a5b Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 11 Aug 2026 08:44:44 +0200 Subject: mtd: spi-nor: xmc: add locking to XM25QH128A Add support for locking to the XM25QH128A part. Reported-by: Wei, Takino Tested-by: Wei, Takino # on Fedora 44 / 7.1.6-201 Reviewed-by: Takahiro Kuwano Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/xmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/xmc.c b/drivers/mtd/spi-nor/xmc.c index d5a06054b0dd..d3b52eb93e2c 100644 --- a/drivers/mtd/spi-nor/xmc.c +++ b/drivers/mtd/spi-nor/xmc.c @@ -19,6 +19,7 @@ static const struct flash_info xmc_nor_parts[] = { .name = "XM25QH128A", .size = SZ_16M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP | SPI_NOR_HAS_TB, }, }; -- cgit v1.2.3 From f594a33f9721f08facd98d0a31adc14ff6da9d70 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 11 Aug 2026 17:56:23 +0200 Subject: mtd: spi-nor: Unprepare legacy controllers upon RWW wait interruption All the parallel locking helpers (spi_nor_prep_and_lock(), spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call spi_nor_prep() then wait with a killable sleep. If the sleep gets killed, we leave the controller in its prepared state. Call ->unprepare() in these cases. Fixes: 74df43b3f626 ("mtd: spi-nor: Enhance locking to support reads while writes") Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Acked-by: Tudor Ambarus Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 69b77191c498..fca0d65a6f06 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1348,6 +1348,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor) ret = wait_event_killable(nor->rww.wait, spi_nor_rww_start_exclusive(nor)); + if (ret) + spi_nor_unprep(nor); + return ret; } @@ -1419,6 +1422,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le ret = wait_event_killable(nor->rww.wait, spi_nor_rww_start_pe(nor, start, len)); + if (ret) + spi_nor_unprep(nor); + return ret; } @@ -1492,6 +1498,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le ret = wait_event_killable(nor->rww.wait, spi_nor_rww_start_rd(nor, start, len)); + if (ret) + spi_nor_unprep(nor); + return ret; } -- cgit v1.2.3 From e42bfc080d96eb36520f5b4f50b50767837bc1e0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:54 +0200 Subject: mtd: spi-nor: Fix spacing in function call Remove the double space. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 2a1ed17fcc62..cc403ae97a83 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -484,7 +484,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, bfpt_header->length * sizeof(u32)); addr = SFDP_PARAM_HEADER_PTP(bfpt_header); memset(&bfpt, 0, sizeof(bfpt)); - err = spi_nor_read_sfdp_dma_unsafe(nor, addr, len, &bfpt); + err = spi_nor_read_sfdp_dma_unsafe(nor, addr, len, &bfpt); if (err < 0) return err; -- cgit v1.2.3 From 0fb499b527cdcdb87188fbe54684e258bb98b9f1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:55 +0200 Subject: mtd: spi-nor: Garbage collection during sfdp parsing SFDP parsing works as follows: 1- The header is read then parsed 2- If correct, the sfdp structure and the sfdp content table are allocated then filled 3- The mandatory BFPT table is parsed 4- The optional tables are then parsed as well Any failure in steps 1-3 leads to an error and an early return. The failure is not always fatal though, since the deprecated will fallback to the non SFDP definition (in the ID table, for legacy chips). In this case, the devm_ memory allocations for the SFDP table structure and table will remain in memory for the lifetime of the device but will never ever be accessed anymore (since they are incorrect). In this case, we shall free the memory manually since the device is not going out of scope anytime soon. The assignation of nor->sfdp is also cleared in case of failure, to avoid carrying a dangling pointer upon partial failure. This is particularly important since the presence of valid SFDP support is checked using the following condition: `if (!nor->sfdp)`. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index cc403ae97a83..24e4d07b18c9 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1539,7 +1539,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) psize, param_headers); if (err < 0) { dev_dbg(dev, "failed to read SFDP parameter headers\n"); - goto exit; + goto free_param_headers; } } @@ -1567,7 +1567,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) sfdp = devm_kzalloc(dev, sizeof(*sfdp), GFP_KERNEL); if (!sfdp) { err = -ENOMEM; - goto exit; + goto free_param_headers; } /* @@ -1581,16 +1581,13 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) sizeof(*sfdp->dwords), GFP_KERNEL); if (!sfdp->dwords) { err = -ENOMEM; - devm_kfree(dev, sfdp); - goto exit; + goto free_sfdp; } err = spi_nor_read_sfdp(nor, 0, sfdp_size, sfdp->dwords); if (err < 0) { dev_dbg(dev, "failed to read SFDP data\n"); - devm_kfree(dev, sfdp->dwords); - devm_kfree(dev, sfdp); - goto exit; + goto free_dwords; } nor->sfdp = sfdp; @@ -1612,7 +1609,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) err = spi_nor_parse_bfpt(nor, bfpt_header); if (err) - goto exit; + goto clear_sfdp_ptr; /* Parse optional parameter tables. */ for (i = 0; i < header.nph; i++) { @@ -1659,7 +1656,20 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) } err = spi_nor_post_sfdp_fixups(nor); -exit: + if (err) + goto clear_sfdp_ptr; + + kfree(param_headers); + + return 0; + +clear_sfdp_ptr: + nor->sfdp = NULL; +free_dwords: + devm_kfree(dev, sfdp->dwords); +free_sfdp: + devm_kfree(dev, sfdp); +free_param_headers: kfree(param_headers); if (err) memcpy(nor->params, ¶ms, sizeof(*nor->params)); -- cgit v1.2.3 From 4df380710b8c8a076923050a948b41715ef09b5f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:56 +0200 Subject: mtd: spi-nor: Init flags before manufacturer late_init() hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current init order is: - The manufacturer-wide late_init() hook is called. - The core initializes some chip flags - The chip specific late_init() hook is called. This does not seem very consistent since both late_init() may need to touch flags, which is then not working as expected since the order is a bit inconsistent. Moving spi_nor_init_flags() earlier, right before the manufacturer late_init() call, ensures the hook sees the flags after their initialization, and can therefore apply changes to them. Since spi_nor_init_flags() only reads ->info->flags and DT properties, it does not depend on anything a late_init() hook produces. On the other hand, no manufacturer late_init() hook currently touches any output of spi_nor_init_flags() either. Reordering seems safe™. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index fca0d65a6f06..d9821620f59b 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3007,6 +3007,9 @@ static int spi_nor_late_init_params(struct spi_nor *nor) struct spi_nor_flash_parameter *params = nor->params; int ret; + /* Needed by some late_init hooks */ + spi_nor_init_flags(nor); + if (nor->manufacturer && nor->manufacturer->fixups && nor->manufacturer->fixups->late_init) { ret = nor->manufacturer->fixups->late_init(nor); @@ -3014,9 +3017,6 @@ static int spi_nor_late_init_params(struct spi_nor *nor) return ret; } - /* Needed by some flashes late_init hooks. */ - spi_nor_init_flags(nor); - if (nor->info->fixups && nor->info->fixups->late_init) { ret = nor->info->fixups->late_init(nor); if (ret) -- cgit v1.2.3 From 9bae0fe02ea8abf11ab8d86ca2ca6b907a874077 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:57 +0200 Subject: mtd: spi-nor: winbond: Move W25Q01NW to its right place Chips are sorted by families, and inside the families, by density. The second ID byte indicates the family, the third indicates the density. Move W25Q01NW to the right place. Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 6b99d9488c40..13a2fcd703e8 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -300,6 +300,12 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x20), .name = "w25q512nwq", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), + }, { + /* W25Q01NWxxIQ */ + .id = SNOR_ID(0xef, 0x60, 0x21), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + .fixups = &winbond_rdcr_fixup, }, { .id = SNOR_ID(0xef, 0x70, 0x15), .name = "w25q16jv-im/jm", @@ -365,12 +371,6 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x20), .name = "w25q512nwm", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), - }, { - /* W25Q01NWxxIQ */ - .id = SNOR_ID(0xef, 0x60, 0x21), - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | - SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_rdcr_fixup, }, { /* W25Q01NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x21), -- cgit v1.2.3 From 0f3a2567a10609b720bffea15e80de53c1f686fa Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:58 +0200 Subject: mtd: spi-nor: winbond: Normalize names Winbond chips cannot just be named eg. "W25Q02JV". There are always more letters after that name, some of them matter. - W is the Winbond prefix - 25Q is the type of device -> 25Q indicates a quad capable SPI NOR -> 25H indicates an automotive grade chip - 02J is the density -> in this case 2Gib - V is the voltage -> V means 3.3V -> W means 1.8V - The next letters (either 2 or 3 letters) are completely irrelevant for the software and purely indicate the hardware package. - The penultimate letter is the temperature grade -> I for Industrial grade -> J means "above" industrial - Q, N or M indicate factory states (like QE=1) and also the DTR capability. -> Q and N typically have the same IDs -> M chips typically have a different ID As a result, W25Q01NWxxIQ is irrelevant and would better be named W25Q01NW-Q, W25Q02JV is irrelevant and would better be named W25Q02JV-M, etc. Make that list uniform before adding more IDs/names. Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 13a2fcd703e8..ace9e620bfa6 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -301,7 +301,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q512nwq", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { - /* W25Q01NWxxIQ */ + /* W25Q01NW-Q */ .id = SNOR_ID(0xef, 0x60, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, @@ -334,7 +334,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x19), .name = "w25q256jvm", }, { - /* W25Q02JV */ + /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups, }, { @@ -372,28 +372,28 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q512nwm", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { - /* W25Q01NWxxIM */ + /* W25Q01NW-M */ .id = SNOR_ID(0xef, 0x80, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25Q02NWxxIM */ + /* W25Q02NW-M */ .id = SNOR_ID(0xef, 0x80, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .fixups = &winbond_rdcr_fixup, }, { - /* W25H512NWxxAM */ + /* W25H512NW-M */ .id = SNOR_ID(0xef, 0xa0, 0x20), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25H01NWxxAM */ + /* W25H01NW-M */ .id = SNOR_ID(0xef, 0xa0, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25H02NWxxAM */ + /* W25H02NW-M */ .id = SNOR_ID(0xef, 0xa0, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, -- cgit v1.2.3 From 2267f0161e60d3052e94a63f9c71a45d77be1d27 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:59 +0200 Subject: mtd: spi-nor: winbond: Properly document the chip names While the .name entry is a legacy field, is highly imprecise, and even wrong sometimes due to ID reuse, it is part of the sysfs ABI, we cannot remove it for existing chips. However, we can double the entry with the now usual comment with the newly instated naming scheme to indicate what chips are covered by each entry. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index ace9e620bfa6..1fd05a3f743d 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -218,16 +218,19 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_1M, .no_sfdp_flags = SECT_4K, }, { + /* W25Q32JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x16), .name = "w25q32", .size = SZ_4M, .no_sfdp_flags = SECT_4K, }, { + /* W25Q64JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x17), .name = "w25q64", .size = SZ_8M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q128JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x18), /* Flavors w/ and w/o SFDP. */ .name = "w25q128", @@ -236,18 +239,20 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixups = &w25q128_fixups, }, { + /* W25Q256JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x19), .name = "w25q256", .size = SZ_32M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixups = &w25q256_fixups, }, { + /* W25Q512JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x20), .name = "w25q512jvq", .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q01JV */ + /* W25Q01JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups, }, { @@ -285,52 +290,60 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q128FW-G/Q, W25Q128JW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x18), .name = "w25q128fw", .size = SZ_16M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q256JW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x19), .name = "w25q256jw", .size = SZ_32M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q512NW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x20), .name = "w25q512nwq", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { - /* W25Q01NW-Q */ + /* W25Q01NW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .fixups = &winbond_rdcr_fixup, }, { + /* W25Q16JV-M */ .id = SNOR_ID(0xef, 0x70, 0x15), .name = "w25q16jv-im/jm", .size = SZ_2M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q32JV-M */ .id = SNOR_ID(0xef, 0x70, 0x16), .name = "w25q32jv", .size = SZ_4M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q64JV-M */ .id = SNOR_ID(0xef, 0x70, 0x17), .name = "w25q64jvm", .size = SZ_8M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K, }, { + /* W25Q128JV-M */ .id = SNOR_ID(0xef, 0x70, 0x18), .name = "w25q128jv", .size = SZ_16M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q256JV-M */ .id = SNOR_ID(0xef, 0x70, 0x19), .name = "w25q256jvm", }, { @@ -343,6 +356,7 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q32JW-M */ .id = SNOR_ID(0xef, 0x80, 0x16), .name = "w25q32jwm", .size = SZ_4M, @@ -350,24 +364,28 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { + /* W25Q64JW-M */ .id = SNOR_ID(0xef, 0x80, 0x17), .name = "w25q64jwm", .size = SZ_8M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q128JW-M */ .id = SNOR_ID(0xef, 0x80, 0x18), .name = "w25q128jwm", .size = SZ_16M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q256JW-M */ .id = SNOR_ID(0xef, 0x80, 0x19), .name = "w25q256jwm", .size = SZ_32M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q512NW-M */ .id = SNOR_ID(0xef, 0x80, 0x20), .name = "w25q512nwm", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), -- cgit v1.2.3 From 9f071bb8fadc1f09c4d5f075d3c1f91b6f6bf0c9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:00 +0200 Subject: mtd: spi-nor: winbond: Make the RDCR fixup Winbond wide The top level paragraph of the QER field in the JESD216B mentions: "In this standard, [...] Status register 2 refers to the byte read using instruction 35h. Status register 2 is the second byte transferred in a Write Status (01h) command. [...]" Value 100b, named in Linux BFPT_DWORD15_QER_SR2_BIT1_NO_RD, does not mention anything about reads and only brings details about writes. This has been interpreted in the spi-nor core by the absence of read capability, but there is no explicit reason for that, except that there were probably some very old chips which didn't support command 35h. All quad capable Winbond chips carry a CMP SWP bit in SR2. SR2 is readable with command 35h. In practice, all Winbond families but the W25X family have support for this feature, so re-enable it Winbond-wide in a late vendor fixup, except for the {EF, 30, xx} family. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 44 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 1fd05a3f743d..acd3c38ca2f4 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -73,26 +73,6 @@ static const struct spi_nor_fixups w25q256_fixups = { .post_bfpt = w25q256_post_bfpt_fixups, }; -static int -winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor, - const struct sfdp_parameter_header *bfpt_header, - const struct sfdp_bfpt *bfpt) -{ - /* - * W25H02NW, unlike its W25H512NW nor W25H01NW cousins, improperly sets - * the QE BFPT configuration bits, indicating a non readable CR. This is - * both incorrect and impractical, as the chip features a CMP bit for its - * locking scheme that lays in the Control Register, and needs to be read. - */ - nor->params->flags &= ~SNOR_F_NO_READ_CR; - - return 0; -} - -static const struct spi_nor_fixups winbond_rdcr_fixup = { - .post_bfpt = winbond_rdcr_post_bfpt_fixup, -}; - /** * winbond_nor_select_die() - Set active die. * @nor: pointer to 'struct spi_nor'. @@ -313,7 +293,6 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_rdcr_fixup, }, { /* W25Q16JV-M */ .id = SNOR_ID(0xef, 0x70, 0x15), @@ -399,7 +378,6 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_rdcr_fixup, }, { /* W25H512NW-M */ .id = SNOR_ID(0xef, 0xa0, 0x20), @@ -415,8 +393,13 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0xa0, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_rdcr_fixup, - }, + }, { + /* + * Catch all entry to make sure all chips solely relying + * on SFDP still go through the manufacturer hooks. + */ + .id = SNOR_ID(0xef), + } }; /** @@ -508,6 +491,19 @@ static int winbond_nor_late_init(struct spi_nor *nor) */ params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode; + /* + * All W25Q/W25H chips do set the BFPT_DWORD15_QER_SR2_BIT1_NO_RD bit in + * their SFDP tables. The historical spi-nor assumption in this case has + * been to declare CR reads as unsupported, whereas the Jedec + * specification doesn't clearly state that. In practice, all these + * chips do support reading back the CR, which is needed for SWP support, + * so make sure that capability remains enabled (needed for SWP). + * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx) + * which actually does not support this feature. + */ + if (nor->id[1] > 0x30) + nor->params->flags &= ~SNOR_F_NO_READ_CR; + return 0; } -- cgit v1.2.3 From 66c36f35633dd5b6fb8412697229996d14973d9b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:01 +0200 Subject: mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn that little 1% write throughput. Before: $ flash_speed /dev/mtd0 -d -c10 eraseblock write speed is 560 KiB/s page write speed is 540 KiB/s 2 page write speed is 550 KiB/s After: eraseblock write speed is 578 KiB/s page write speed is 557 KiB/s 2 page write speed is 567 KiB/s The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook is added to make sure the capability remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index acd3c38ca2f4..0d7f74fa7cd9 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -203,6 +203,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q32", .size = SZ_4M, .no_sfdp_flags = SECT_4K, + .flags = SPI_NOR_QUAD_PP, }, { /* W25Q64JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x17), @@ -504,6 +505,22 @@ static int winbond_nor_late_init(struct spi_nor *nor) if (nor->id[1] > 0x30) nor->params->flags &= ~SNOR_F_NO_READ_CR; + /* + * Winbond has reused many IDs, up to four times at this + * stage. In general, most of the chips with SFDP support are + * correctly described by the ID table, but the non-SFDP chips, + * however, are known to not feature as many capabilities. Make + * sure we filter out those capabilities to keep backward + * compatibility with these devices manufactured until ~2016. + */ + if (!nor->sfdp) { + struct spi_nor_flash_parameter *p = nor->params; + + /* SPI_NOR_QUAD_PP was unsupported */ + p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4; + spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0); + } + return 0; } -- cgit v1.2.3 From 8218e55d31725f5768f54e91e6b865c53e7295f8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:02 +0200 Subject: mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn that little 1% write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 539 KiB/s page write speed is 535 KiB/s 2 page write speed is 536 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 556 KiB/s page write speed is 549 KiB/s 2 page write speed is 552 KiB/s The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the capability remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 0d7f74fa7cd9..2b0ec584f38f 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -210,6 +210,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q64", .size = SZ_8M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_QUAD_PP, }, { /* W25Q128JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x18), -- cgit v1.2.3 From e9f96f43214094c4b9227acffca55b9abbf2f30b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:03 +0200 Subject: mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad page program capability The benefit is not massive @25MHz, yet this feature is not advertized because these chips do not implement the 4BAIT table. Flag the quad page program capability manually and earn a few % of write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 536 KiB/s page write speed is 532 KiB/s 2 page write speed is 532 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 552 KiB/s page write speed is 547 KiB/s 2 page write speed is 549 KiB/s The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the capability remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 2b0ec584f38f..78a4254357e6 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -217,7 +217,7 @@ static const struct flash_info winbond_nor_parts[] = { /* Flavors w/ and w/o SFDP. */ .name = "w25q128", .size = SZ_16M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixups = &w25q128_fixups, }, { -- cgit v1.2.3 From fc9f75c1457b9ea8f80db77c8292b2ed84116589 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:04 +0200 Subject: mtd: spi-nor: winbond: W25Q32JV-M: Add quad page program capability This chip does not advertise it correctly but it supports 1-1-4 page programs. Before: $ flash_speed /dev/mtd0 -d -c10 eraseblock write speed is 2612 KiB/s page write speed is 2509 KiB/s 2 page write speed is 2560 KiB/s After: $ flash_speed /dev/mtd0 -c10 -d eraseblock write speed is 3033 KiB/s page write speed is 2895 KiB/s 2 page write speed is 2976 KiB/s The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the capability remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 78a4254357e6..484a2d87a4b3 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -307,8 +307,8 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x16), .name = "w25q32jv", .size = SZ_4M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, }, { /* W25Q64JV-M */ .id = SNOR_ID(0xef, 0x70, 0x17), -- cgit v1.2.3 From ab7242e3dc6ec73f657f03d03e0f97277664d910 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:05 +0200 Subject: mtd: spi-nor: winbond: W25Q64JV-M: Add quad page program capability The benefit is massive @25MHz, but the chip does not implement the 4BAIT table to advertize this capability, so enable it manually:. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 558 KiB/s page write speed is 553 KiB/s 2 page write speed is 555 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 3047 KiB/s page write speed is 2909 KiB/s 2 page write speed is 2976 KiB/s The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the capability remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 484a2d87a4b3..48b0bb643b15 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -314,8 +314,8 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x17), .name = "w25q64jvm", .size = SZ_8M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, }, { /* W25Q128JV-M */ .id = SNOR_ID(0xef, 0x70, 0x18), -- cgit v1.2.3 From 9b65f84c62f0377dc2311919fc63b37ba0e9c03a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:06 +0200 Subject: mtd: spi-nor: winbond: W25Q128JV-M: Add quad page program capability This chip does not advertise it correctly but it supports 1-1-4 page programs, add the flag manually: Before: $ flash_speed /dev/mtd0 -d -c10 eraseblock write speed is 553 KiB/s page write speed is 548 KiB/s 2 page write speed is 550 KiB/s After: $ flash_speed /dev/mtd0 -c10 -d eraseblock write speed is 1628 KiB/s page write speed is 1600 KiB/s 2 page write speed is 1612 KiB/s The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the capability remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 48b0bb643b15..f8f103296c82 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -321,8 +321,8 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x18), .name = "w25q128jv", .size = SZ_16M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, }, { /* W25Q256JV-M */ .id = SNOR_ID(0xef, 0x70, 0x19), -- cgit v1.2.3 From c9aee7f457c3e37ca8753c10e13653ff297e9f4a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:07 +0200 Subject: mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad page program capability I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but all the chips with a density <= 256Mib are older revisions of the JV chips which do not yet implement the 4BAIT extension table, as such, they cannot advertise their quad page program capability. We can reasonably expect that these chips follow the exact same rules, so add the missing flag. The QUAD_PP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index f8f103296c82..cc428a11c99f 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -226,6 +226,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q256", .size = SZ_32M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_QUAD_PP, .fixups = &w25q256_fixups, }, { /* W25Q512JV-Q/N */ @@ -300,8 +301,8 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x15), .name = "w25q16jv-im/jm", .size = SZ_2M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, }, { /* W25Q32JV-M */ .id = SNOR_ID(0xef, 0x70, 0x16), @@ -327,6 +328,7 @@ static const struct flash_info winbond_nor_parts[] = { /* W25Q256JV-M */ .id = SNOR_ID(0xef, 0x70, 0x19), .name = "w25q256jvm", + .flags = SPI_NOR_QUAD_PP, }, { /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), -- cgit v1.2.3 From 151aa1a5953adaec73c793c25a6b881e98e12a73 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:08 +0200 Subject: mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag these capabilities. The CMP capability was historically not supported by non-SFDP chips, so let's make sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index cc428a11c99f..0bec3887b570 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -203,7 +203,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q32", .size = SZ_4M, .no_sfdp_flags = SECT_4K, - .flags = SPI_NOR_QUAD_PP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { /* W25Q64JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x17), @@ -522,6 +522,9 @@ static int winbond_nor_late_init(struct spi_nor *nor) /* SPI_NOR_QUAD_PP was unsupported */ p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4; spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0); + + /* SPI_NOR_HAS_CMP was unsupported */ + nor->params->flags &= ~SNOR_F_HAS_SR2_CMP_BIT6; } return 0; -- cgit v1.2.3 From 3b1fba74306b3a0d0d4052c00940b6e3c74dd2ca Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:09 +0200 Subject: mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag these capabilities. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 0bec3887b570..d8cc7626b4bd 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -210,7 +210,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q64", .size = SZ_8M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .flags = SPI_NOR_QUAD_PP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { /* W25Q128JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x18), -- cgit v1.2.3 From 4a6aab1f7335dd8a54b768fdbcbcce0ad454d2fa Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:10 +0200 Subject: mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill locking information The SFDP table does not advertize the CMP locking capabilities of the device, flag it. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index d8cc7626b4bd..a8b1fffe083c 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -217,8 +217,8 @@ static const struct flash_info winbond_nor_parts[] = { /* Flavors w/ and w/o SFDP. */ .name = "w25q128", .size = SZ_16M, - .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .fixups = &w25q128_fixups, }, { /* W25Q256JV-Q/N */ -- cgit v1.2.3 From 08ce407217371994f738ab6c391f8af0f8a2eb2b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:11 +0200 Subject: mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag these capabilities. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index a8b1fffe083c..17559bfbd465 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -234,6 +234,8 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q512jvq", .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { /* W25Q01JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x21), -- cgit v1.2.3 From 5eaaa72edec1bd5517808a8cb16d4c334ec69aff Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:12 +0200 Subject: mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag these capabilities. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 17559bfbd465..98178344e334 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -239,6 +239,8 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25Q01JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x21), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .fixups = &winbond_nor_multi_die_fixups, }, { .id = SNOR_ID(0xef, 0x50, 0x12), -- cgit v1.2.3 From 2e9abc448c2b6bc22d9d77eb20fcaa2cc05da6f9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:13 +0200 Subject: mtd: spi-nor: winbond: W25Q32JV-M: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing CMP capability. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 98178344e334..356263dbef58 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -313,7 +313,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q32jv", .size = SZ_4M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { /* W25Q64JV-M */ .id = SNOR_ID(0xef, 0x70, 0x17), -- cgit v1.2.3 From 860fd661dff43cd4d85eec4e7ad1c0bbfa9d1c98 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:14 +0200 Subject: mtd: spi-nor: winbond: W25Q64JV-M: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag these capabilities. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 356263dbef58..b4f83a50fd9a 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -320,7 +320,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q64jvm", .size = SZ_8M, .no_sfdp_flags = SECT_4K, - .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { /* W25Q128JV-M */ .id = SNOR_ID(0xef, 0x70, 0x18), -- cgit v1.2.3 From e2e9dcab5505e8e4fc55404d9f329c5933001588 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:15 +0200 Subject: mtd: spi-nor: winbond: W25Q128JV-M: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag these capabilities. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index b4f83a50fd9a..e3c67db28c90 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -327,7 +327,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q128jv", .size = SZ_16M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { /* W25Q256JV-M */ .id = SNOR_ID(0xef, 0x70, 0x19), -- cgit v1.2.3 From 0d8f65aa9a44574e0cabf89aa6aa37a7e3a19cfe Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:16 +0200 Subject: mtd: spi-nor: winbond: W25Q02JV-M: Fill locking information The SFDP table is pretty complete except for the locking capabilities which must be filled manually. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index e3c67db28c90..0c854c975eb6 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -336,6 +336,8 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .fixups = &winbond_nor_multi_die_fixups, }, { .id = SNOR_ID(0xef, 0x71, 0x19), -- cgit v1.2.3 From d5212797429d43c5ad3859f5b14683e52ab686a7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:17 +0200 Subject: mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill locking information I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but all the other chips from this family have the same locking pattern, so we can reasonably expect that these chips follow the exact same rules. The CMP capability was historically not supported by non-SFDP chips, so a Winbond wide late hook already makes sure the bit remains disabled for them, for backward compatibility reasons. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 0c854c975eb6..90fe9617d784 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -226,7 +226,8 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q256", .size = SZ_32M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .flags = SPI_NOR_QUAD_PP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | + SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .fixups = &w25q256_fixups, }, { /* W25Q512JV-Q/N */ @@ -306,7 +307,7 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q16jv-im/jm", .size = SZ_2M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { /* W25Q32JV-M */ .id = SNOR_ID(0xef, 0x70, 0x16), @@ -332,7 +333,8 @@ static const struct flash_info winbond_nor_parts[] = { /* W25Q256JV-M */ .id = SNOR_ID(0xef, 0x70, 0x19), .name = "w25q256jvm", - .flags = SPI_NOR_QUAD_PP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | + SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), -- cgit v1.2.3 From 6648e64c6efe6a0b11a080ef1a87e26147085774 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:18 +0200 Subject: mtd: spi-nor: winbond: W25Q512JV-M: New chip Add support for W25Q512JV-M. It is a 3.3V chip with usual dual/quad capabilities. The SFDP table is pretty complete except for the locking capabilities which must be filled manually. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 90fe9617d784..5b81de305d8e 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -335,6 +335,11 @@ static const struct flash_info winbond_nor_parts[] = { .name = "w25q256jvm", .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + }, { + /* W25Q512JV-M */ + .id = SNOR_ID(0xef, 0x70, 0x20), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), -- cgit v1.2.3 From 41d96c2da5970f7e25b7818710d7732f5035cdb4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:19 +0200 Subject: mtd: spi-nor: winbond: W25Q01JV-M: New chip Add support for W25Q01JV-M. It is a 3.3V chip with usual dual/quad capabilities. The SFDP table is pretty complete except for the locking capabilities which must be filled manually. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 5b81de305d8e..f59587b859a9 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -340,6 +340,12 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x20), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + }, { + /* W25Q01JV-M */ + .id = SNOR_ID(0xef, 0x70, 0x21), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + .fixups = &winbond_nor_multi_die_fixups, }, { /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), -- cgit v1.2.3 From a885b694afb4acd914e4a9f94365d828432ca066 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:38 +0200 Subject: mtd: spi-nor: Rename BFPT_DWORD15_QER_SR2_BIT1_BUGGY The _BUGGY suffix is not super accurate. There is a behavior that indeed seems strange, but it is somewhat documented. The fact is, with this QER field the status register is considered 16-bit wide and if one writes the first 8 bits (SR1) without the next 8 bits (SR2) then the content of SR2 will be assumed to be 0x00 and will be cleared. Rename it BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR to align with the other namings (_NO_RD) and clarify the meaning of this field. No functional change. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 2 +- drivers/mtd/spi-nor/sfdp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 24e4d07b18c9..ab57fa60f718 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -598,7 +598,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, params->quad_enable = NULL; break; - case BFPT_DWORD15_QER_SR2_BIT1_BUGGY: + case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR: /* * Writing only one byte to the Status Register has the * side-effect of clearing Status Register 2. diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h index f74a0eb339ea..d976c6c9a4ad 100644 --- a/drivers/mtd/spi-nor/sfdp.h +++ b/drivers/mtd/spi-nor/sfdp.h @@ -84,7 +84,7 @@ struct sfdp_bfpt { */ #define BFPT_DWORD15_QER_MASK GENMASK(22, 20) #define BFPT_DWORD15_QER_NONE (0x0UL << 20) /* Micron */ -#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY (0x1UL << 20) +#define BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR (0x1UL << 20) #define BFPT_DWORD15_QER_SR1_BIT6 (0x2UL << 20) /* Macronix */ #define BFPT_DWORD15_QER_SR2_BIT7 (0x3UL << 20) #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD (0x4UL << 20) -- cgit v1.2.3 From 01771ff25fec9d24663a0087723621e97e6d8484 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:39 +0200 Subject: mtd: spi-nor: Create a structure containing the flash opcodes Create a structure named opcodes in the flash parameters structures to store all the flash specific opcodes. They are typically discovered dynamically through SFDP parsing. Move the die_erase opcode inside. Later on we will add new fields there, like the supported opcodes for accessing the status registers, which are almost vendor specific. No fonctional change. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 6 +++--- drivers/mtd/spi-nor/core.h | 13 +++++++++++-- drivers/mtd/spi-nor/debugfs.c | 2 +- drivers/mtd/spi-nor/micron-st.c | 4 ++-- drivers/mtd/spi-nor/spansion.c | 4 ++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index d9821620f59b..886f3c956309 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1142,7 +1142,7 @@ static int spi_nor_erase_die(struct spi_nor *nor, loff_t addr, size_t die_size) if (nor->spimem) { struct spi_mem_op op = - SPI_NOR_DIE_ERASE_OP(nor->params->die_erase_opcode, + SPI_NOR_DIE_ERASE_OP(nor->params->opcodes.die_erase, nor->addr_nbytes, addr, multi_die); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); @@ -3023,8 +3023,8 @@ static int spi_nor_late_init_params(struct spi_nor *nor) return ret; } - if (!nor->params->die_erase_opcode) - nor->params->die_erase_opcode = SPINOR_OP_CHIP_ERASE; + if (!nor->params->opcodes.die_erase) + nor->params->opcodes.die_erase = SPINOR_OP_CHIP_ERASE; /* Default method kept for backward compatibility. */ if (!params->set_4byte_addr_mode) diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index d59ec9cad986..4b36d99ce8f6 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -342,6 +342,16 @@ struct spi_nor_otp { const struct spi_nor_otp_ops *ops; }; +/** + * struct spi_nor_opcodes - SPI NOR flash specific opcodes. + * List of variable opcodes used by the chip. + * + * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE + */ +struct spi_nor_opcodes { + u8 die_erase; +}; + /** * struct spi_nor_flash_parameter - SPI NOR flash parameters and settings. * Includes legacy flash parameters and settings that can be overwritten @@ -365,7 +375,6 @@ struct spi_nor_otp { * command in octal DTR mode. * @n_banks: number of banks. * @n_dice: number of dice in the flash memory. - * @die_erase_opcode: die erase opcode. Defaults to SPINOR_OP_CHIP_ERASE. * @vreg_offset: volatile register offset for each die. * @hwcaps: describes the read and page program hardware * capabilities. @@ -398,7 +407,6 @@ struct spi_nor_flash_parameter { u8 rdsr_addr_nbytes; u8 n_banks; u8 n_dice; - u8 die_erase_opcode; u32 *vreg_offset; struct spi_nor_hwcaps hwcaps; @@ -406,6 +414,7 @@ struct spi_nor_flash_parameter { struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX]; enum spi_nor_cmd_ext cmd_ext_type; + struct spi_nor_opcodes opcodes; struct spi_nor_erase_map erase_map; struct spi_nor_otp otp; diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 437a0e0cb4dd..5a7372e7f802 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -144,7 +144,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) } string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); - seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); + seq_printf(s, " %02x (%s)\n", params->opcodes.die_erase, buf); seq_puts(s, "\nsector map\n"); seq_puts(s, " region (in hex) | erase mask | overlaid\n"); diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index ca2ce18050df..3c89bd9b575c 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -131,7 +131,7 @@ static int micron_st_nor_four_die_late_init(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; - params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE; + params->opcodes.die_erase = SPINOR_OP_MT_DIE_ERASE; params->n_dice = 4; /* @@ -147,7 +147,7 @@ static int micron_st_nor_two_die_late_init(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; - params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE; + params->opcodes.die_erase = SPINOR_OP_MT_DIE_ERASE; params->n_dice = 2; /* diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 40d3236c723b..998fea1626bf 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -677,7 +677,7 @@ static int s25hx_t_late_init(struct spi_nor *nor) cypress_nor_ecc_init(nor); if (params->n_dice > 1) - params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + params->opcodes.die_erase = SPINOR_OP_CYPRESS_DIE_ERASE; return 0; } @@ -765,7 +765,7 @@ static int s28hx_t_late_init(struct spi_nor *nor) cypress_nor_ecc_init(nor); if (params->n_dice > 1) - params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + params->opcodes.die_erase = SPINOR_OP_CYPRESS_DIE_ERASE; return 0; } -- cgit v1.2.3 From 63489002d397ebf136548c79af68a8e05b30d45c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:40 +0200 Subject: mtd: spi-nor: Refactor Read Status/Write Status support SPI NOR has a lot of history. Additions over additions, the subtleties of the JESD216 specification, their implementations by the manufacturers and the hardware mistakes have generated a gigantic maze, let's try to understand what is really needed. The specification explains the QE (Quad Enable) bitfield as describing where the bit to enable the Quad capability is, but also how to set it. Unfortunately, the specification is not precise about what opcodes are supported exactly in all the cases. There is an introduction that basically states: - Opcode 0x05 reads SR1 - Opcode 0x35 reads SR2 - Opcode 0x01 writes SR1, and then SR2 if another byte is written Then the bitfield, among indicating the location of the QE bit, may indicate: - Reading SR1 and SR2 in one operation is not possible (loops over the content of SR1) - Reading SR2 directly is possible - Only writing SR1 zeroes SR2. One problem with the current implementation, is that it only focuses on the QE bit. A quad_enable function was created for each case, even though in practice, the logic was always the same: read, modify, write, read back and verify. One problem comes when other features need to play with the status registers, like software block protection or OTP: you never know how to properly handle the QE bit, nor where it is, nor how to read/write the Status registers. This lead to approximations/guessing (in swp.c, otp.c and obviously in legacy controller drivers like atmel.c) but also to the implementation of a gazillon of helpers for reading/writing/checking the status registers. In addition, I believe some design decisions had a negative impact over the years. - All possible situations had to be flagged by the core. This is likely wrong, because we no longer know why we need specific quirks. It was ineherent to the state of the SPI NOR core before the great cleanup that had happened the past few years. I believe this creates confusion in the core today, and we should push this to vendor fixups instead. As an example, in 2023 Hsin-Yi was facing an issue because his chip was setting the wrong QER value, leading to RDCR being prevented, thus falling into a condition blindly setting a random QE bit (I strongy believe it is done like that for wrong reasons). His chip actually had RDCR support! The correct fix should have been to mark the capability in a device fixup instead of handling this in the core. Link: https://lore.kernel.org/lkml/CAJMQK-hR0eaO0b4Vd0U8_KAndLyZapqdHjVLAoe42rWi9rdLkA@mail.gmail.com/ - SFDP parsing is over cautious. I believe BFPT_DWORD15_QER_SR2_BIT1_NO_RD is abusive (nothing states that RDCR is not supported), and BFPT_DWORD15_QER_SR2_BIT1 is also out of specification when forcing 16-bit Status writes. - SNOR_F_HAS_16BIT_SR is only imposing 16bit Status writes, whereas reads can still be 8-bit wide. - The usage of helpers verifying the writes was also spread for IMHO no really good reason. Why shouldn't we trust spi operations when it comes to Status Registers? We do not read back our page reads, so why status registers should be treated with so much care, if it's not because we are unsure of what is being done? My proposal includes a check when it comes to the QE bit (done once) but we don't need these checks otherwise. If the QE bit was written properly, there are high chances that the other register accesses will just be fine, no? Asde from my main quest, I also observed no good reason to ask the read/write status register callers to use nor->bouncebuf while the low-level helpers could do it themselves (we are talking about one or two bytes being copied). So after these observations, my proposal is the following: - Create private low level helpers that just read or write a status register. They are flexible, we can give the opcode (which varies based on the SFDP QER field) and the length (1 or 2). - Create public generic accessors which will be used to read/write sr1 and/or sr2. This is where all the cleverness shall be. The helpers use the available opcodes for a given chip in order to fullfill the request. - Provide a single generic ->quad_enable() hook which generically does all the steps mentioned above (read, modify, write, read back and verify). - Create a list of opcodes for all 6 possible situations: {read, write} {sr1, sr2, sr1 and sr2}. These opcodes are filled/cleared based on the QER field. An opcode set to 0 indicates the absence of support (there is no 0x00 opcode in SPI NOR). I tried my best to analyze the current behavior and to mimic it as much as possible, but this is a risky cleanup. However, if we go for this, it will be *much* easier in the future to handle all kind of chip variations. We won't be limited to a couple of flags anymore, but rather we'll be able to just disable a read or write capability using a single line. Known deviations: - The Atmel manufacturer driver drives non SFDP chips which by default were receiving the HAS_16B_WR flag, forcing SR2 writes all the times, but it was deliberately making single bytes writes to SR1 for its locking operations. The flag is likely wrong for them, so I removed it. - I am proposing on purpose a single quad_enable helper. It should match all the cases, with 1 identified difference which I believe is harmless: in the SR2_BIT7 case, the helper would make an extra SR1 read/write which was not done before. This entry explicitly supports reading and writing the two registers. Note: no manufacturer has been identified to actually use that entry yet. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- Documentation/driver-api/mtd/spi-nor.rst | 2 +- drivers/mtd/spi-nor/atmel.c | 50 ++- drivers/mtd/spi-nor/core.c | 612 ++++++++++--------------------- drivers/mtd/spi-nor/core.h | 59 ++- drivers/mtd/spi-nor/debugfs.c | 2 - drivers/mtd/spi-nor/gigadevice.c | 6 +- drivers/mtd/spi-nor/issi.c | 3 +- drivers/mtd/spi-nor/macronix.c | 12 +- drivers/mtd/spi-nor/micron-st.c | 4 +- drivers/mtd/spi-nor/otp.c | 16 +- drivers/mtd/spi-nor/sfdp.c | 41 ++- drivers/mtd/spi-nor/spansion.c | 16 +- drivers/mtd/spi-nor/sst.c | 5 +- drivers/mtd/spi-nor/swp.c | 99 +++-- drivers/mtd/spi-nor/winbond.c | 4 +- include/linux/mtd/spi-nor.h | 4 - 16 files changed, 365 insertions(+), 570 deletions(-) diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst index 747a326fb6c0..5441fa7d4f04 100644 --- a/Documentation/driver-api/mtd/spi-nor.rst +++ b/Documentation/driver-api/mtd/spi-nor.rst @@ -118,7 +118,7 @@ section, after the ``---`` marker. write size 1 page size 256 address nbytes 3 - flags HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | SWP_IS_VOLATILE + flags HAS_LOCK | SOFT_RESET | SWP_IS_VOLATILE opcodes read 0xeb diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c index 82c592f0a1e1..8741c0bda497 100644 --- a/drivers/mtd/spi-nor/atmel.c +++ b/drivers/mtd/spi-nor/atmel.c @@ -23,18 +23,28 @@ static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len) static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len) { + /* Write 0x00 to the status register to disable write protection */ + u8 sr = 0; int ret; /* We only support unlocking the whole flash array */ if (ofs || len != nor->params->size) return -EINVAL; - /* Write 0x00 to the status register to disable write protection */ - ret = spi_nor_write_sr_and_check(nor, 0); + ret = spi_nor_write_sr1(nor, &sr); + if (ret) + return ret; + + ret = spi_nor_read_sr1(nor, &sr); if (ret) + return ret; + + if (sr) { dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n"); + return -EIO; + } - return ret; + return 0; } static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) @@ -50,6 +60,7 @@ static const struct spi_nor_locking_ops at25fs_nor_locking_ops = { static int at25fs_nor_late_init(struct spi_nor *nor) { + nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR; nor->params->locking_ops = &at25fs_nor_locking_ops; return 0; @@ -69,6 +80,7 @@ static const struct spi_nor_fixups at25fs_nor_fixups = { * Return: 0 on success, -error otherwise. */ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, + u64 len, bool is_protect) { int ret; @@ -78,19 +90,24 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, if (ofs || len != nor->params->size) return -EINVAL; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - sr = nor->bouncebuf[0]; - /* SRWD bit needs to be cleared, otherwise the protection doesn't change */ if (sr & SR_SRWD) { sr &= ~SR_SRWD; - ret = spi_nor_write_sr_and_check(nor, sr); - if (ret) { - dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n"); + ret = spi_nor_write_sr1(nor, &sr); + if (ret) + return ret; + + ret = spi_nor_read_sr1(nor, &sr); + if (ret) return ret; + + if (sr & SR_SRWD) { + dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n"); + return -EIO; } } @@ -108,14 +125,7 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK; } - nor->bouncebuf[0] = sr; - - /* - * We cannot use the spi_nor_write_sr_and_check() because this command - * isn't really setting any bits, instead it is an pseudo command for - * "Global Unprotect" or "Global Protect" - */ - return spi_nor_write_sr(nor, nor->bouncebuf, 1); + return spi_nor_write_sr1(nor, &sr); } static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len) @@ -131,16 +141,17 @@ static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len) static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs, u64 len) { + u8 sr; int ret; if (ofs >= nor->params->size || (ofs + len) > nor->params->size) return -EINVAL; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK); + return ((sr & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK); } static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = { @@ -151,6 +162,7 @@ static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = { static int atmel_nor_global_protection_late_init(struct spi_nor *nor) { + nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR; nor->params->locking_ops = &atmel_nor_global_protection_ops; return 0; diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 886f3c956309..1f2a58acae1e 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -444,75 +444,6 @@ int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id, return ret; } -/** - * spi_nor_read_sr() - Read the Status Register. - * @nor: pointer to 'struct spi_nor'. - * @sr: pointer to a DMA-able buffer where the value of the - * Status Register will be written. Should be at least 2 bytes. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDSR_OP(sr); - - if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { - op.addr.nbytes = nor->params->rdsr_addr_nbytes; - op.dummy.nbytes = nor->params->rdsr_dummy; - /* - * We don't want to read only one byte in DTR mode. So, - * read 2 and then discard the second byte. - */ - op.data.nbytes = 2; - } - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR, sr, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading SR\n", ret); - - return ret; -} - -/** - * spi_nor_read_cr() - Read the Configuration Register using the - * SPINOR_OP_RDCR (35h) command. - * @nor: pointer to 'struct spi_nor' - * @cr: pointer to a DMA-able buffer where the value of the - * Configuration Register will be written. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_read_cr(struct spi_nor *nor, u8 *cr) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDCR_OP(cr); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDCR, cr, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading CR\n", ret); - - return ret; -} - /** * spi_nor_set_4byte_addr_mode_en4b_ex4b() - Enter/Exit 4-byte address mode * using SPINOR_OP_EN4B/SPINOR_OP_EX4B. Typically used by @@ -618,12 +549,13 @@ int spi_nor_set_4byte_addr_mode_brwr(struct spi_nor *nor, bool enable) int spi_nor_sr_ready(struct spi_nor *nor) { int ret; + u8 sr; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - return !(nor->bouncebuf[0] & SR_WIP); + return !(sr & SR_WIP); } /** @@ -785,344 +717,285 @@ int spi_nor_global_block_unlock(struct spi_nor *nor) } /** - * spi_nor_write_sr() - Write the Status Register. + * spi_nor_read_sr_ll() - Low-level Status Registers read. * @nor: pointer to 'struct spi_nor'. - * @sr: pointer to DMA-able buffer to write to the Status Register. - * @len: number of bytes to write to the Status Register. + * @opcode: opcode for the status register read operation. + * @sr: pointer to buffer for storing the content of the status registers. + * @len: number of status registers to read (1 or 2). * * Return: 0 on success, -errno otherwise. */ -int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) +int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr, + unsigned int len) { - int ret; + int ret, i; - ret = spi_nor_write_enable(nor); - if (ret) - return ret; + if (len > 2) + return -EINVAL; + + for (i = 0; i < len; i++) + nor->bouncebuf[i] = 0; if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len); + struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, len); + + if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { + op.addr.nbytes = nor->params->rdsr_addr_nbytes; + op.dummy.nbytes = nor->params->rdsr_dummy; + /* + * We don't want to read only one byte in DTR mode. So, + * read 2 and then discard the second byte. + */ + op.data.nbytes = 2; + } spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR, sr, - len); + ret = spi_nor_controller_ops_read_reg(nor, opcode, nor->bouncebuf, len); } - if (ret) { - dev_dbg(nor->dev, "error %d writing SR\n", ret); - return ret; - } + memcpy(sr, nor->bouncebuf, len); - return spi_nor_wait_till_ready(nor); -} - -/** - * spi_nor_write_sr1_and_check() - Write one byte to the Status Register 1 and - * ensure that the byte written match the received value. - * @nor: pointer to a 'struct spi_nor'. - * @sr1: byte value to be written to the Status Register. - * - * Return: 0 on success, -errno otherwise. - */ -static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1) -{ - int ret; - - nor->bouncebuf[0] = sr1; - - ret = spi_nor_write_sr(nor, nor->bouncebuf, 1); if (ret) - return ret; - - ret = spi_nor_read_sr(nor, nor->bouncebuf); - if (ret) - return ret; + dev_dbg(nor->dev, "Error %d reading Status Registers\n", ret); - if (nor->bouncebuf[0] != sr1) { - dev_dbg(nor->dev, "SR1: read back test failed\n"); - return -EIO; - } - - return 0; + return ret; } /** - * spi_nor_write_16bit_sr_and_check() - Write the Status Register 1 and the - * Status Register 2 in one shot. Ensure that the byte written in the Status - * Register 1 match the received value, and that the 16-bit Write did not - * affect what was already in the Status Register 2. - * @nor: pointer to a 'struct spi_nor'. - * @sr1: byte value to be written to the Status Register 1. + * spi_nor_write_sr_ll() - Low-level Status Registers write. + * @nor: pointer to 'struct spi_nor'. + * @opcode: opcode for the status register write operation. + * @sr: pointer to status registers buffer to write. + * @len: number of status registers to write. * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) +static int spi_nor_write_sr_ll(struct spi_nor *nor, u8 opcode, const u8 *sr, + unsigned int len) { - struct spi_nor_flash_parameter *params = nor->params; - int ret; - u8 *sr_cr = nor->bouncebuf; - u8 cr_written; - - /* Make sure we don't overwrite the contents of Status Register 2. */ - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) - return ret; - } else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 || - spi_nor_get_protocol_width(nor->write_proto) == 4) && - nor->params->quad_enable) { - /* - * If the Status Register 2 Read command (35h) is not - * supported, we should at least be sure we don't - * change the value of the SR2 Quad Enable bit. - * - * When the Quad Enable method is set and the buswidth is 4, we - * can safely assume that the value of the QE bit is one, as a - * consequence of the nor->params->quad_enable() call. - * - * According to the JESD216 revB standard, BFPT DWORDS[15], - * bits 22:20, the 16-bit Write Status (01h) command is - * available just for the cases in which the QE bit is - * described in SR2 at BIT(1). - */ - sr_cr[1] = SR2_QUAD_EN_BIT1; - } else { - sr_cr[1] = 0; - } + int ret, i; - sr_cr[0] = sr1; + if (len > 2) + return -EINVAL; - ret = spi_nor_write_sr(nor, sr_cr, 2); + ret = spi_nor_write_enable(nor); if (ret) return ret; - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; + for (i = 0; i < len; i++) + nor->bouncebuf[i] = sr[i]; - if (sr1 != sr_cr[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } + if (nor->spimem) { + struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode, + nor->bouncebuf, len); - if (params->flags & SNOR_F_NO_READ_CR) - return 0; + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - cr_written = sr_cr[1]; + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, opcode, + nor->bouncebuf, len); + } - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) + if (ret) { + dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret); return ret; - - if (cr_written != sr_cr[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; } - return 0; + return spi_nor_wait_till_ready(nor); } /** - * spi_nor_write_16bit_cr_and_check() - Write the Status Register 1 and the - * Configuration Register in one shot. Ensure that the byte written in the - * Configuration Register match the received value, and that the 16-bit Write - * did not affect what was already in the Status Register 1. - * @nor: pointer to a 'struct spi_nor'. - * @cr: byte value to be written to the Configuration Register. + * spi_nor_read_sr1() - Read SR1 only + * Useful for: + * - The core to offer a generic "read SR1 and SR2" capability + * - Manufacturer drivers (since they know the chip SR layout) + * - Polling the BUSY bit in the core * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr1: pointer to a valid SR1 buffer + * + * Return 0 or errno. */ -int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) +int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1) { - int ret; - u8 *sr_cr = nor->bouncebuf; - u8 sr_written; - - /* Keep the current value of the Status Register 1. */ - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; - - sr_cr[1] = cr; - - ret = spi_nor_write_sr(nor, sr_cr, 2); - if (ret) - return ret; - - sr_written = sr_cr[0]; - - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; - - if (sr_written != sr_cr[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return 0; + return spi_nor_read_sr_ll(nor, nor->params->opcodes.read_sr1, sr1, 1); +} - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) - return ret; +/** + * spi_nor_read_sr2() - Read SR2 only + * Useful for: + * - The core to offer a generic "read SR1 and SR2" capability + * - Manufacturer drivers (since they know the chip SR layout) + * - SR2 based OTP configuration + * + * @nor: the spi_nor structure + * @sr2: pointer to a valid SR2 buffer + * + * Return 0 or errno. + */ +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +{ + struct spi_nor_flash_parameter *params = nor->params; - if (cr != sr_cr[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } + if (!params->opcodes.read_sr2) + return -EINVAL; - return 0; + return spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1); } /** - * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the - * Configuration Register in one shot. Ensure that the bytes written in both - * registers match the received value. - * @nor: pointer to a 'struct spi_nor'. - * @regs: two-byte array with values to be written to the status and - * configuration registers. + * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2 + * General purpose helper. * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. */ -static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr) { - u8 written_regs[2]; int ret; - written_regs[0] = regs[0]; - written_regs[1] = regs[1]; - nor->bouncebuf[0] = regs[0]; - nor->bouncebuf[1] = regs[1]; - - ret = spi_nor_write_sr(nor, nor->bouncebuf, 2); + ret = spi_nor_read_sr1(nor, &sr[0]); if (ret) return ret; - ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]); - if (ret) - return ret; - - if (written_regs[0] != nor->bouncebuf[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return 0; - - ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]); - if (ret) - return ret; - - if (written_regs[1] != nor->bouncebuf[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } - - return 0; + return spi_nor_read_sr2(nor, &sr[1]); } /** - * spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that - * the byte written match the received value without affecting other bits in the - * Status Register 1 and 2. - * @nor: pointer to a 'struct spi_nor'. - * @sr1: byte value to be written to the Status Register. + * spi_nor_write_sr1() - Write SR1 only + * Useful for: + * - Manufacturer drivers (since they know the chip SR layout) * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr1: pointer to a valid SR1 buffer + * + * Return 0 or errno. */ -int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1) { - if (nor->params->flags & SNOR_F_HAS_16BIT_SR) - return spi_nor_write_16bit_sr_and_check(nor, sr1); + if (WARN_ONCE(!nor->params->opcodes.write_sr1, + "Restricted helper use, write SR1 not supported")) + return -EIO; - return spi_nor_write_sr1_and_check(nor, sr1); + return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr1, sr1, 1); } /** - * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that - * the byte written match the received value. Same for the Control Register if - * available. - * @nor: pointer to a 'struct spi_nor'. - * @regs: byte array to be written to the registers. + * spi_nor_write_sr2() - Write SR2 only + * Useful for: + * - Manufacturer drivers (since they know the chip SR layout) + * - SR2 based OTP configuration * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr2: pointer to a valid SR2 buffer + * + * Return 0 or errno. */ -int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) { - if (nor->params->flags & SNOR_F_HAS_16BIT_SR) - return spi_nor_write_16bit_sr_cr_and_check(nor, regs); + if (WARN_ONCE(!nor->params->opcodes.write_sr2, + "Restricted helper use, write SR2 not supported")) + return -EIO; - return spi_nor_write_sr1_and_check(nor, regs[0]); + return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr2, sr2, 1); } /** - * spi_nor_write_sr2() - Write the Status Register 2 using the - * SPINOR_OP_WRSR2 (3eh) command. - * @nor: pointer to 'struct spi_nor'. - * @sr2: pointer to DMA-able buffer to write to the Status Register 2. + * spi_nor_write_sr1_and_sr2() - Write SR1 then SR2 + * General purpose helper, always safe to call. Will expectedly ignore + * SR2 on certain chips. * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. */ -static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) +static int spi_nor_write_sr1_and_sr2(struct spi_nor *nor, const u8 *sr) { + struct spi_nor_flash_parameter *params = nor->params; int ret; - ret = spi_nor_write_enable(nor); + if (params->opcodes.write_sr1_and_sr2) + return spi_nor_write_sr_ll(nor, + params->opcodes.write_sr1_and_sr2, + sr, 2); + + ret = spi_nor_write_sr1(nor, &sr[0]); if (ret) return ret; - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2); + if (params->opcodes.write_sr2) + return spi_nor_write_sr2(nor, &sr[1]); - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + return 0; +} - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR2, - sr2, 1); - } +/** + * spi_nor_write_sr1_and_sr2_and_check() - Write SR1 then SR2, then read + * them back and verifies + * General purpose helper, always safe to call. Will expectedly ignore + * SR2 on certain chips. + * + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. + */ +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr) +{ + u8 tmp[2]; + int ret; - if (ret) { - dev_dbg(nor->dev, "error %d writing SR2\n", ret); + ret = spi_nor_write_sr1_and_sr2(nor, sr); + if (ret) return ret; - } - return spi_nor_wait_till_ready(nor); + ret = spi_nor_read_sr1_and_sr2(nor, tmp); + if (ret) + return ret; + + if (sr[0] != tmp[0] || sr[1] != tmp[1]) + return -EIO; + + return 0; } /** - * spi_nor_read_sr2() - Read the Status Register 2 using the - * SPINOR_OP_RDSR2 (3fh) command. - * @nor: pointer to 'struct spi_nor'. - * @sr2: pointer to DMA-able buffer where the value of the - * Status Register 2 will be written. + * spi_nor_generic_quad_enable() - Read the status registers, + * apply the QE bit mask, + * write the status registers, + * read them back and check the content. + * + * @nor: pointer to 'struct spi_nor' * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +static int spi_nor_generic_quad_enable(struct spi_nor *nor) { + u8 *qe_mask = nor->params->qe_mask; + u8 sr[2] = {}; int ret; - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2); + if (!qe_mask[0] && !qe_mask[1]) + return 0; - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_nor_read_sr1_and_sr2(nor, sr); + if (ret) + return ret; - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR2, sr2, - 1); - } + if (sr[0] & qe_mask[0] || sr[1] & qe_mask[1]) + return 0; - if (ret) - dev_dbg(nor->dev, "error %d reading SR2\n", ret); + sr[0] |= qe_mask[0]; + sr[1] |= qe_mask[1]; - return ret; + return spi_nor_write_sr1_and_sr2_and_check(nor, sr); } /** @@ -1914,106 +1787,6 @@ erase_err: return ret; } -/** - * spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status - * Register 1. - * @nor: pointer to a 'struct spi_nor' - * - * Bit 6 of the Status Register 1 is the QE bit for Macronix like QSPI memories. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor) -{ - int ret; - - ret = spi_nor_read_sr(nor, nor->bouncebuf); - if (ret) - return ret; - - if (nor->bouncebuf[0] & SR1_QUAD_EN_BIT6) - return 0; - - nor->bouncebuf[0] |= SR1_QUAD_EN_BIT6; - - return spi_nor_write_sr1_and_check(nor, nor->bouncebuf[0]); -} - -/** - * spi_nor_sr2_bit1_quad_enable() - set the Quad Enable BIT(1) in the Status - * Register 2. - * @nor: pointer to a 'struct spi_nor'. - * - * Bit 1 of the Status Register 2 is the QE bit for Spansion like QSPI memories. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor) -{ - int ret; - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1); - - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - if (nor->bouncebuf[0] & SR2_QUAD_EN_BIT1) - return 0; - - nor->bouncebuf[0] |= SR2_QUAD_EN_BIT1; - - return spi_nor_write_16bit_cr_and_check(nor, nor->bouncebuf[0]); -} - -/** - * spi_nor_sr2_bit7_quad_enable() - set QE bit in Status Register 2. - * @nor: pointer to a 'struct spi_nor' - * - * Set the Quad Enable (QE) bit in the Status Register 2. - * - * This is one of the procedures to set the QE bit described in the SFDP - * (JESD216 rev B) specification but no manufacturer using this procedure has - * been identified yet, hence the name of the function. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor) -{ - u8 *sr2 = nor->bouncebuf; - int ret; - u8 sr2_written; - - /* Check current Quad Enable bit value. */ - ret = spi_nor_read_sr2(nor, sr2); - if (ret) - return ret; - if (*sr2 & SR2_QUAD_EN_BIT7) - return 0; - - /* Update the Quad Enable bit. */ - *sr2 |= SR2_QUAD_EN_BIT7; - - ret = spi_nor_write_sr2(nor, sr2); - if (ret) - return ret; - - sr2_written = *sr2; - - /* Read back and check it. */ - ret = spi_nor_read_sr2(nor, sr2); - if (ret) - return ret; - - if (*sr2 != sr2_written) { - dev_dbg(nor->dev, "SR2: Read back test failed\n"); - return -EIO; - } - - return 0; -} - static const struct spi_nor_manufacturer *manufacturers[] = { &spi_nor_atmel, &spi_nor_eon, @@ -2504,6 +2277,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) { struct spi_nor_flash_parameter *params = nor->params; unsigned int cap; + u8 opcode; /* X-X-X modes are not supported yet, mask them all. */ *hwcaps &= ~SNOR_HWCAPS_X_X_X; @@ -2535,14 +2309,15 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) *hwcaps &= ~BIT(cap); } - /* Some SPI controllers might not support CR read opcode. */ - if (!(params->flags & SNOR_F_NO_READ_CR)) { - struct spi_mem_op op = SPI_NOR_RDCR_OP(nor->bouncebuf); + /* Some SPI controllers might not support reading SR2 */ + opcode = nor->params->opcodes.read_sr2; + if (opcode) { + struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, 1); spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); if (!spi_mem_supports_op(nor->spimem, &op)) - params->flags |= SNOR_F_NO_READ_CR; + nor->params->opcodes.read_sr2 = 0; } } @@ -3057,11 +2832,14 @@ static void spi_nor_init_default_params(struct spi_nor *nor) const struct flash_info *info = nor->info; struct device_node *np = spi_nor_get_flash_node(nor); - params->quad_enable = spi_nor_sr2_bit1_quad_enable; - params->otp.org = info->otp; + /* Default to 16-bit Read/Write Status commands */ + params->opcodes.read_sr1 = SPINOR_OP_RDSR; + params->opcodes.read_sr2 = SPINOR_OP_RDCR; + params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR; + params->quad_enable = spi_nor_generic_quad_enable; + params->qe_mask[1] = BIT(1); - /* Default to 16-bit Write Status (01h) Command */ - params->flags |= SNOR_F_HAS_16BIT_SR; + params->otp.org = info->otp; /* Set SPI NOR sizes. */ params->writesize = 1; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 4b36d99ce8f6..b8a0db02e0f1 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -37,36 +37,18 @@ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_NO_DATA) -#define SPI_NOR_RDSR_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0), \ +#define SPI_NOR_RDSR_OP(opcode, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ SPI_MEM_OP_NO_ADDR, \ SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_IN(1, buf, 0)) + SPI_MEM_OP_DATA_IN(len, buf, 0)) -#define SPI_NOR_WRSR_OP(buf, len) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0), \ +#define SPI_NOR_WRSR_OP(opcode, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \ SPI_MEM_OP_NO_ADDR, \ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_DATA_OUT(len, buf, 0)) -#define SPI_NOR_RDSR2_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_OUT(1, buf, 0)) - -#define SPI_NOR_WRSR2_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_OUT(1, buf, 0)) - -#define SPI_NOR_RDCR_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_IN(1, buf, 0)) - #define SPI_NOR_EN4B_EX4B_OP(enable) \ SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0), \ SPI_MEM_OP_NO_ADDR, \ @@ -131,8 +113,6 @@ enum spi_nor_option_flags { SNOR_F_4B_OPCODES = BIT(3), SNOR_F_HAS_4BAIT = BIT(4), SNOR_F_HAS_LOCK = BIT(5), - SNOR_F_HAS_16BIT_SR = BIT(6), - SNOR_F_NO_READ_CR = BIT(7), SNOR_F_HAS_SR_TB_BIT6 = BIT(8), SNOR_F_HAS_4BIT_BP = BIT(9), SNOR_F_HAS_SR_BP3_BIT6 = BIT(10), @@ -347,9 +327,19 @@ struct spi_nor_otp { * List of variable opcodes used by the chip. * * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE + * @read_sr1: opcode for reading SR1 alone + * @read_sr2: opcode for reading SR2 alone + * @write_sr1: opcode for writing SR1 alone + * @write_sr2: opcode for writing SR2 alone + * @write_sr1_and_sr2: opcode for writing SR1 then SR2 in one operation */ struct spi_nor_opcodes { u8 die_erase; + u8 read_sr1; + u8 read_sr2; + u8 write_sr1; + u8 write_sr2; + u8 write_sr1_and_sr2; }; /** @@ -388,6 +378,7 @@ struct spi_nor_opcodes { * @otp: SPI NOR OTP info. * @set_octal_dtr: enables or disables SPI NOR octal DTR mode. * @quad_enable: enables SPI NOR quad mode. + * @qe_mask: two bytes mask used to set/clear the QE bit * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode. * @ready: (optional) flashes might use a different mechanism * than reading the status register to indicate they @@ -420,6 +411,7 @@ struct spi_nor_flash_parameter { int (*set_octal_dtr)(struct spi_nor *nor, bool enable); int (*quad_enable)(struct spi_nor *nor); + u8 qe_mask[2]; int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable); int (*ready)(struct spi_nor *nor); @@ -645,18 +637,17 @@ int spi_nor_wait_till_ready(struct spi_nor *nor); int spi_nor_global_block_unlock(struct spi_nor *nor); int spi_nor_prep_and_lock(struct spi_nor *nor); void spi_nor_unlock_and_unprep(struct spi_nor *nor); -int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor); -int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor); -int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor); int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id, enum spi_nor_protocol reg_proto); -int spi_nor_read_sr(struct spi_nor *nor, u8 *sr); int spi_nor_sr_ready(struct spi_nor *nor); -int spi_nor_read_cr(struct spi_nor *nor, u8 *cr); -int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len); -int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1); -int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr); -int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs); +int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr, + unsigned int len); +int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1); +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2); +int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr); +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1); +int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2); +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr); ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 5a7372e7f802..51c0582f793a 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -18,8 +18,6 @@ static const char *const snor_f_names[] = { SNOR_F_NAME(4B_OPCODES), SNOR_F_NAME(HAS_4BAIT), SNOR_F_NAME(HAS_LOCK), - SNOR_F_NAME(HAS_16BIT_SR), - SNOR_F_NAME(NO_READ_CR), SNOR_F_NAME(HAS_SR_TB_BIT6), SNOR_F_NAME(HAS_4BIT_BP), SNOR_F_NAME(HAS_SR_BP3_BIT6), diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c index ef1edd0add70..4070a692e968 100644 --- a/drivers/mtd/spi-nor/gigadevice.c +++ b/drivers/mtd/spi-nor/gigadevice.c @@ -23,8 +23,10 @@ gd25q256_post_bfpt(struct spi_nor *nor, * GD25Q256E | SFDP_JESD216_MAJOR | SFDP_JESD216B_MINOR */ if (bfpt_header->major == SFDP_JESD216_MAJOR && - bfpt_header->minor == SFDP_JESD216_MINOR) - nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; + bfpt_header->minor == SFDP_JESD216_MINOR) { + nor->params->qe_mask[0] = BIT(6); + nor->params->qe_mask[1] = 0; + } return 0; } diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c index 18d9a00aa22e..04db42b5141e 100644 --- a/drivers/mtd/spi-nor/issi.c +++ b/drivers/mtd/spi-nor/issi.c @@ -131,7 +131,8 @@ static const struct flash_info issi_nor_parts[] = { static void issi_nor_default_init(struct spi_nor *nor) { - nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; + nor->params->qe_mask[0] = BIT(6); + nor->params->qe_mask[1] = 0; } static const struct spi_nor_fixups issi_fixups = { diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 9610d9e14e4c..954e7df21ff9 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -65,11 +65,12 @@ mx25l3255e_late_init_fixups(struct spi_nor *nor) /* * SFDP of MX25L3255E is JESD216, which does not include the Quad - * Enable bit Requirement in BFPT. As a result, during BFPT parsing, - * the quad_enable method is not set to spi_nor_sr1_bit6_quad_enable. - * Therefore, it is necessary to correct this setting by late_init. + * Enable bit Requirement in BFPT. As a result, during BFPT parsing + * the quad_enable mask is reset. Therefore, it is necessary to + * correct this setting by late_init. */ - params->quad_enable = spi_nor_sr1_bit6_quad_enable; + params->qe_mask[0] = BIT(6); + params->qe_mask[1] = 0; /* * In addition, MX25L3255E also supports 1-4-4 page program in 3-byte @@ -340,7 +341,8 @@ static int macronix_nor_set_octal_dtr(struct spi_nor *nor, bool enable) static void macronix_nor_default_init(struct spi_nor *nor) { - nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; + nor->params->qe_mask[0] = BIT(6); + nor->params->qe_mask[1] = 0; } static int macronix_nor_late_init(struct spi_nor *nor) diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 3c89bd9b575c..21d0ceac1ed7 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { - nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->opcodes.write_sr1 = 0; return 0; } @@ -633,7 +633,7 @@ static int micron_st_nor_ready(struct spi_nor *nor) static void micron_st_nor_default_init(struct spi_nor *nor) { nor->params->flags |= SNOR_F_HAS_LOCK; - nor->params->flags &= ~SNOR_F_HAS_16BIT_SR; + nor->params->opcodes.write_sr1 = 0; nor->params->quad_enable = NULL; } diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 7d0b145d78d8..1eeb33a20091 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -175,24 +175,24 @@ static int spi_nor_otp_lock_bit_cr(unsigned int region) */ int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region) { - u8 *cr = nor->bouncebuf; int ret, lock_bit; + u8 sr[2]; lock_bit = spi_nor_otp_lock_bit_cr(region); if (lock_bit < 0) return lock_bit; - ret = spi_nor_read_cr(nor, cr); + ret = spi_nor_read_sr1_and_sr2(nor, sr); if (ret) return ret; /* no need to write the register if region is already locked */ - if (cr[0] & lock_bit) + if (sr[1] & lock_bit) return 0; - cr[0] |= lock_bit; + sr[1] |= lock_bit; - return spi_nor_write_16bit_cr_and_check(nor, cr[0]); + return spi_nor_write_sr1_and_sr2_and_check(nor, sr); } /** @@ -207,18 +207,18 @@ int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region) */ int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region) { - u8 *cr = nor->bouncebuf; int ret, lock_bit; + u8 sr2; lock_bit = spi_nor_otp_lock_bit_cr(region); if (lock_bit < 0) return lock_bit; - ret = spi_nor_read_cr(nor, cr); + ret = spi_nor_read_sr2(nor, &sr2); if (ret) return ret; - return cr[0] & lock_bit; + return sr2 & lock_bit; } static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index ab57fa60f718..20156914e254 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -592,10 +592,21 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, val >>= BFPT_DWORD11_PAGE_SIZE_SHIFT; params->page_size = 1U << val; + /* + * The standard declares various read and write status methods, some of + * them will be overloaded based on the QER field. + */ + params->opcodes.read_sr1 = SPINOR_OP_RDSR; + params->opcodes.read_sr2 = SPINOR_OP_RDCR; + params->opcodes.write_sr1 = SPINOR_OP_WRSR; + params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR; + + params->qe_mask[0] = 0; + params->qe_mask[1] = 0; + /* Quad Enable Requirements. */ switch (bfpt.dwords[SFDP_DWORD(15)] & BFPT_DWORD15_QER_MASK) { case BFPT_DWORD15_QER_NONE: - params->quad_enable = NULL; break; case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR: @@ -603,23 +614,28 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Writing only one byte to the Status Register has the * side-effect of clearing Status Register 2. */ + fallthrough; case BFPT_DWORD15_QER_SR2_BIT1_NO_RD: /* * Read Configuration Register (35h) instruction is not - * supported. + * supported. 16-bit writes expected. */ - params->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR; - params->quad_enable = spi_nor_sr2_bit1_quad_enable; + params->opcodes.read_sr2 = 0; + params->opcodes.write_sr1 = 0; + params->qe_mask[1] = BIT(1); break; case BFPT_DWORD15_QER_SR1_BIT6: - params->flags &= ~SNOR_F_HAS_16BIT_SR; - params->quad_enable = spi_nor_sr1_bit6_quad_enable; + params->opcodes.read_sr2 = 0; + params->opcodes.write_sr1_and_sr2 = 0; + params->qe_mask[0] = BIT(6); break; case BFPT_DWORD15_QER_SR2_BIT7: - params->flags &= ~SNOR_F_HAS_16BIT_SR; - params->quad_enable = spi_nor_sr2_bit7_quad_enable; + params->opcodes.read_sr2 = SPINOR_OP_RDSR2; + params->opcodes.write_sr1_and_sr2 = 0; + params->opcodes.write_sr2 = SPINOR_OP_WRSR2; + params->qe_mask[1] = BIT(7); break; case BFPT_DWORD15_QER_SR2_BIT1: @@ -629,9 +645,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, * Register 2, so let's be cautious and keep the default * assumption of a 16-bit Write Status (01h) command. */ - params->flags |= SNOR_F_HAS_16BIT_SR; - - params->quad_enable = spi_nor_sr2_bit1_quad_enable; + params->opcodes.write_sr1 = 0; + params->qe_mask[1] = BIT(1); break; default: @@ -639,6 +654,10 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, break; } + /* opcodes sanity check */ + WARN_ON(!params->opcodes.read_sr1 || + (!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2)); + dword = bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_4B_ADDR_MODE_MASK; if (SFDP_MASK_CHECK(dword, BFPT_DWORD16_4B_ADDR_MODE_BRWR)) params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr; diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 998fea1626bf..9c27c00a3a04 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -399,8 +399,9 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor, nor->bouncebuf); bool is3byte, is4byte; int ret; + u8 sr; - ret = spi_nor_read_sr(nor, &nor->bouncebuf[1]); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; @@ -408,7 +409,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor, if (ret) return ret; - is3byte = (nor->bouncebuf[0] == nor->bouncebuf[1]); + is3byte = (nor->bouncebuf[0] == sr); op = (struct spi_mem_op) CYPRESS_NOR_RD_ANY_REG_OP(4, SPINOR_REG_CYPRESS_STR1V, 0, @@ -417,7 +418,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor, if (ret) return ret; - is4byte = (nor->bouncebuf[0] == nor->bouncebuf[1]); + is4byte = (nor->bouncebuf[0] == sr); if (is3byte == is4byte) return -EIO; @@ -1105,13 +1106,14 @@ static const struct flash_info spansion_nor_parts[] = { static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor) { int ret; + u8 sr; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1(nor, &sr); if (ret) return ret; - if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) { - if (nor->bouncebuf[0] & SR_E_ERR) + if (sr & (SR_E_ERR | SR_P_ERR)) { + if (sr & SR_E_ERR) dev_err(nor->dev, "Erase Error occurred\n"); else dev_err(nor->dev, "Programming Error occurred\n"); @@ -1131,7 +1133,7 @@ static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor) return -EIO; } - return !(nor->bouncebuf[0] & SR_WIP); + return !(sr & SR_WIP); } static int spansion_nor_late_init(struct spi_nor *nor) diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index db02c14ba16f..77a0bc41d4e5 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -21,16 +21,17 @@ static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len) static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len) { int ret; + u8 cr; /* We only support unlocking the entire flash array. */ if (ofs != 0 || len != nor->params->size) return -EINVAL; - ret = spi_nor_read_cr(nor, nor->bouncebuf); + ret = spi_nor_read_sr2(nor, &cr); if (ret) return ret; - if (!(nor->bouncebuf[0] & SST26VF_CR_BPNV)) { + if (!(cr & SST26VF_CR_BPNV)) { dev_dbg(nor->dev, "Any block has been permanently locked\n"); return -EINVAL; } diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 62720045a9ab..7e667e4ca84d 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -41,7 +41,7 @@ static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; - if (!(params->flags & SNOR_F_NO_READ_CR) && + if (params->opcodes.read_sr2 && params->flags & SNOR_F_HAS_SR2_CMP_BIT6) return SR2_CMP_BIT6; else @@ -201,13 +201,51 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, return 0; } +/* + * Make sure we do our best to guess SR2. This has historically only be needed + * for swp.c, so let's keep this extra carefulness in this file. + */ +static int spi_nor_read_sr2_careful(struct spi_nor *nor, u8 *sr2) +{ + struct spi_nor_flash_parameter *params = nor->params; + int ret; + + if (params->opcodes.read_sr2) { + ret = spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1); + if (ret) + return ret; + } else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 || + spi_nor_get_protocol_width(nor->write_proto) == 4) && + nor->params->quad_enable) { + /* + * Make sure the QE bit is persistently kept. qe_mask[1] will be + * 0 if the QE bit is in SR1. + */ + *sr2 = params->qe_mask[1]; + } else { + return 0; + } + + return 0; +} + +static int spi_nor_read_sr1_and_sr2_careful(struct spi_nor *nor, u8 *sr) +{ + int ret; + + ret = spi_nor_read_sr1(nor, &sr[0]); + if (ret) + return ret; + + return spi_nor_read_sr2_careful(nor, &sr[1]); +} + /* * Keep a local cache containing all lock-related bits for debugfs use only. * This way, debugfs never needs to access the flash directly. */ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) { - struct spi_nor_flash_parameter *params = nor->params; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor); @@ -215,17 +253,9 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) if (!sr) { - if (spi_nor_read_sr(nor, nor->bouncebuf)) + if (spi_nor_read_sr1_and_sr2_careful(nor, sr_cr)) return; - sr_cr[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - if (spi_nor_read_cr(nor, nor->bouncebuf)) - return; - } - - sr_cr[1] = nor->bouncebuf[0]; sr = sr_cr; } @@ -282,20 +312,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) int ret; u8 pow; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1_and_sr2_careful(nor, status_old); if (ret) return ret; - status_old[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - status_old[1] = nor->bouncebuf[0]; - } - /* If nothing in our range is unlocked, we don't need to do anything */ if (spi_nor_is_locked_sr(nor, ofs, len, status_old)) return 0; @@ -387,10 +407,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old))) return -EINVAL; - if (params->flags & SNOR_F_NO_READ_CR) - ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); - else - ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); + ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new); if (ret) return ret; @@ -419,20 +436,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) int ret; u8 pow; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1_and_sr2_careful(nor, status_old); if (ret) return ret; - status_old[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - status_old[1] = nor->bouncebuf[0]; - } - /* If nothing in our range is locked, we don't need to do anything */ if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old)) return 0; @@ -522,10 +529,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; - if (params->flags & SNOR_F_NO_READ_CR) - ret = spi_nor_write_sr_and_check(nor, best_status_new[0]); - else - ret = spi_nor_write_sr_cr_and_check(nor, best_status_new); + ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new); if (ret) return ret; @@ -543,24 +547,13 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) */ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) { - struct spi_nor_flash_parameter *params = nor->params; u8 sr_cr[2] = {}; int ret; - ret = spi_nor_read_sr(nor, nor->bouncebuf); + ret = spi_nor_read_sr1_and_sr2_careful(nor, sr_cr); if (ret) return ret; - sr_cr[0] = nor->bouncebuf[0]; - - if (!(params->flags & SNOR_F_NO_READ_CR)) { - ret = spi_nor_read_cr(nor, nor->bouncebuf); - if (ret) - return ret; - - sr_cr[1] = nor->bouncebuf[0]; - } - return spi_nor_is_locked_sr(nor, ofs, len, sr_cr); } diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index f59587b859a9..df4688c637a2 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -520,12 +520,12 @@ static int winbond_nor_late_init(struct spi_nor *nor) * been to declare CR reads as unsupported, whereas the Jedec * specification doesn't clearly state that. In practice, all these * chips do support reading back the CR, which is needed for SWP support, - * so make sure that capability remains enabled (needed for SWP). + * so make sure that capability remains enabled. * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx) * which actually does not support this feature. */ if (nor->id[1] > 0x30) - nor->params->flags &= ~SNOR_F_NO_READ_CR; + params->opcodes.read_sr2 = SPINOR_OP_RDCR; /* * Winbond has reused many IDs, up to four times at this diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 099c5016b485..a901ddfac405 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -113,20 +113,16 @@ #define SR_E_ERR BIT(5) #define SR_P_ERR BIT(6) -#define SR1_QUAD_EN_BIT6 BIT(6) - #define SR_BP_SHIFT 2 /* Enhanced Volatile Configuration Register bits */ #define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */ /* Status Register 2 bits. */ -#define SR2_QUAD_EN_BIT1 BIT(1) #define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */ #define SR2_LB2 BIT(4) /* Security Register Lock Bit 2 */ #define SR2_LB3 BIT(5) /* Security Register Lock Bit 3 */ #define SR2_CMP_BIT6 BIT(6) -#define SR2_QUAD_EN_BIT7 BIT(7) /* Supported SPI protocols */ #define SNOR_PROTO_INST_MASK GENMASK(23, 16) -- cgit v1.2.3 From 9f1feb4162387295b0d8de6c879f00357852609b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:41 +0200 Subject: mtd: spi-nor: Add support for the new JESD216 rev F QER field Revision F of the JEDEC JESD216 standard includes yet another supported setting in the QER 3-bit bitfield: 110b. This setting implies that the QE bit is bit 1 of SR2 (which is an already supported case) but this time it makes it very clear that both reading and writing to SR2 is supported, but not with the usual opcodes. The write operation uses an alternate WRSR2 opcode: 0x31 (instead of 0x3E) and both the reads and writes manipulate a single register at a time. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 6 ++++++ drivers/mtd/spi-nor/sfdp.h | 8 ++++++++ include/linux/mtd/spi-nor.h | 1 + 3 files changed, 15 insertions(+) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 20156914e254..23c11fbc68c3 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -649,6 +649,12 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, params->qe_mask[1] = BIT(1); break; + case BFPT_DWORD15_QER_SR2_BIT1_1B: + params->qe_mask[1] = BIT(1); + params->opcodes.write_sr1_and_sr2 = 0; + params->opcodes.write_sr2 = SPINOR_OP_WRSR2_ALT; + break; + default: dev_dbg(nor->dev, "BFPT QER reserved value used\n"); break; diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h index d976c6c9a4ad..e8ed814c6530 100644 --- a/drivers/mtd/spi-nor/sfdp.h +++ b/drivers/mtd/spi-nor/sfdp.h @@ -12,6 +12,7 @@ #define SFDP_JESD216_MINOR 0 #define SFDP_JESD216A_MINOR 5 #define SFDP_JESD216B_MINOR 6 +#define SFDP_JESD216F_MINOR 10 /* SFDP DWORDS are indexed from 1 but C arrays are indexed from 0. */ #define SFDP_DWORD(i) ((i) - 1) @@ -81,6 +82,12 @@ struct sfdp_bfpt { * instruction 35h. QE is set via Write Status instruction 01h with * two data bytes where bit 1 of the second byte is one. * [...] + * (from JESD216 rev F) + * - 110b: QE is bit 1 of status register 2. Status register 1 is read using + * Read Status instruction 05h. Status register2 is read using + * instruction 35h, and status register 3 is read using instruction 15h. + * QE is set via Write Status Register instruction 31h with one data + * byte. */ #define BFPT_DWORD15_QER_MASK GENMASK(22, 20) #define BFPT_DWORD15_QER_NONE (0x0UL << 20) /* Micron */ @@ -89,6 +96,7 @@ struct sfdp_bfpt { #define BFPT_DWORD15_QER_SR2_BIT7 (0x3UL << 20) #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD (0x4UL << 20) #define BFPT_DWORD15_QER_SR2_BIT1 (0x5UL << 20) /* Spansion */ +#define BFPT_DWORD15_QER_SR2_BIT1_1B (0x6UL << 20) /* Winbond */ #define BFPT_DWORD16_EN4B_MASK GENMASK(31, 24) #define BFPT_DWORD16_EN4B_ALWAYS_4B BIT(30) diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index a901ddfac405..539eb514588c 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -25,6 +25,7 @@ #define SPINOR_OP_WRSR 0x01 /* Write status register 1 */ #define SPINOR_OP_RDSR2 0x3f /* Read status register 2 */ #define SPINOR_OP_WRSR2 0x3e /* Write status register 2 */ +#define SPINOR_OP_WRSR2_ALT 0x31 /* Write status register 2 (alternative) */ #define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */ #define SPINOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */ #define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual Output SPI) */ -- cgit v1.2.3 From af9bc9f9c5dcbc1a351ac00d9a4b86400bc442b8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:42 +0200 Subject: mtd: spi-nor: Create the concept of fixup table with match function Manufacturer ID tables increase and fixup() hooks proliferate. Having one possible structure per chip was fine until the fixups started being more and more common and needed, to some extend due to ID reuses. Mixing fixups and chips becomes hard and requires extra helpers to sort which ones are needed for a given chip, which every time this happens requires a lot of rework. Replace the two-level fixup association (a manufacturer wide hook and a per flash_info hook) with a per-manufacturer list of fixups that can be looked up by flash ID and/or match function. The match logic works as follows: - If there is an ID, it must match - If there is a match function, it must match (cumulative) - If there is no identifier (no ID nor any match function), it's a catch-all entry typically used for flagging manufacturer fixups. Suggested-by: Michael Walle Tested-by: Takahiro Kuwano Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/atmel.c | 23 +++++++----- drivers/mtd/spi-nor/core.c | 59 ++++++++++++++++++++++-------- drivers/mtd/spi-nor/core.h | 23 ++++++++++-- drivers/mtd/spi-nor/everspin.c | 7 +++- drivers/mtd/spi-nor/gigadevice.c | 7 +++- drivers/mtd/spi-nor/issi.c | 22 ++++++++--- drivers/mtd/spi-nor/macronix.c | 25 ++++++++----- drivers/mtd/spi-nor/micron-st.c | 43 ++++++++++++++++------ drivers/mtd/spi-nor/sfdp.c | 79 ++++++++++++++++++++++++++-------------- drivers/mtd/spi-nor/spansion.c | 41 ++++++++++++--------- drivers/mtd/spi-nor/sst.c | 11 ++++-- drivers/mtd/spi-nor/winbond.c | 17 ++++++--- 12 files changed, 248 insertions(+), 109 deletions(-) diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c index 8741c0bda497..bccc702a2c4a 100644 --- a/drivers/mtd/spi-nor/atmel.c +++ b/drivers/mtd/spi-nor/atmel.c @@ -194,47 +194,40 @@ static const struct flash_info atmel_nor_parts[] = { .size = SZ_512K, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K, - .fixups = &atmel_nor_global_protection_fixups, }, { .id = SNOR_ID(0x1f, 0x45, 0x01), .name = "at26df081a", .size = SZ_1M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K, - .fixups = &atmel_nor_global_protection_fixups }, { .id = SNOR_ID(0x1f, 0x46, 0x01), .name = "at26df161a", .size = SZ_2M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K, - .fixups = &atmel_nor_global_protection_fixups }, { .id = SNOR_ID(0x1f, 0x47, 0x00), .name = "at25df321", .size = SZ_4M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K, - .fixups = &atmel_nor_global_protection_fixups }, { .id = SNOR_ID(0x1f, 0x47, 0x01), .name = "at25df321a", .size = SZ_4M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K, - .fixups = &atmel_nor_global_protection_fixups }, { .id = SNOR_ID(0x1f, 0x47, 0x08), .name = "at25ff321a", .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, - .fixups = &atmel_nor_global_protection_fixups }, { .id = SNOR_ID(0x1f, 0x48, 0x00), .name = "at25df641", .size = SZ_8M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K, - .fixups = &atmel_nor_global_protection_fixups }, { .id = SNOR_ID(0x1f, 0x66, 0x01), .name = "at25fs010", @@ -242,14 +235,12 @@ static const struct flash_info atmel_nor_parts[] = { .size = SZ_128K, .flags = SPI_NOR_HAS_LOCK, .no_sfdp_flags = SECT_4K, - .fixups = &at25fs_nor_fixups }, { .id = SNOR_ID(0x1f, 0x66, 0x04), .name = "at25fs040", .size = SZ_512K, .flags = SPI_NOR_HAS_LOCK, .no_sfdp_flags = SECT_4K, - .fixups = &at25fs_nor_fixups }, { .id = SNOR_ID(0x1f, 0x87, 0x01), .size = SZ_4M, @@ -257,8 +248,22 @@ static const struct flash_info atmel_nor_parts[] = { }, }; +static const struct spi_nor_fixup atmel_fixups[] = { + { .id = SNOR_ID(0x1f, 0x44, 0x01), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x45, 0x01), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x46, 0x01), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x47, 0x00), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x47, 0x01), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x47, 0x08), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x48, 0x00), .fixups = &atmel_nor_global_protection_fixups }, + { .id = SNOR_ID(0x1f, 0x66, 0x01), .fixups = &at25fs_nor_fixups }, + { .id = SNOR_ID(0x1f, 0x66, 0x04), .fixups = &at25fs_nor_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_atmel = { .name = "atmel", .parts = atmel_nor_parts, .nparts = ARRAY_SIZE(atmel_nor_parts), + .fixups = atmel_fixups, + .nfixups = ARRAY_SIZE(atmel_fixups), }; diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 1f2a58acae1e..322683738ef1 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2618,6 +2618,26 @@ static int spi_nor_setup(struct spi_nor *nor, return spi_nor_set_addr_nbytes(nor); } +bool spi_nor_fixup_match(const struct spi_nor *nor, + const struct spi_nor_fixup *fixup) +{ + const struct spi_nor_id *id = nor->info ? nor->info->id : NULL; + + /* Filter by ID first, if available */ + if (fixup->id) { + if (!id || fixup->id->len > id->len || + memcmp(id->bytes, fixup->id->bytes, fixup->id->len)) + return false; + } + + /* Further filter with the match callback, if provided */ + if (fixup->match) + return fixup->match(nor); + + /* Either there was an ID and it matched, or it is a catch-all entry */ + return true; +} + /** * spi_nor_manufacturer_init_params() - Initialize the flash's parameters and * settings based on MFR register and ->default_init() hook. @@ -2625,12 +2645,19 @@ static int spi_nor_setup(struct spi_nor *nor, */ static void spi_nor_manufacturer_init_params(struct spi_nor *nor) { - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->default_init) - nor->manufacturer->fixups->default_init(nor); + const struct spi_nor_fixup *fixups; + unsigned int i; + + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; + + fixups = nor->manufacturer->fixups; - if (nor->info->fixups && nor->info->fixups->default_init) - nor->info->fixups->default_init(nor); + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (fixups[i].fixups->default_init && + spi_nor_fixup_match(nor, &fixups[i])) + fixups[i].fixups->default_init(nor); + } } /** @@ -2780,22 +2807,24 @@ static void spi_nor_init_fixup_flags(struct spi_nor *nor) static int spi_nor_late_init_params(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; + const struct spi_nor_fixup *fixups; + unsigned int i; int ret; /* Needed by some late_init hooks */ spi_nor_init_flags(nor); - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->late_init) { - ret = nor->manufacturer->fixups->late_init(nor); - if (ret) - return ret; - } + if (nor->manufacturer && nor->manufacturer->fixups) { + fixups = nor->manufacturer->fixups; - if (nor->info->fixups && nor->info->fixups->late_init) { - ret = nor->info->fixups->late_init(nor); - if (ret) - return ret; + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (fixups[i].fixups->late_init && + spi_nor_fixup_match(nor, &fixups[i])) { + ret = fixups[i].fixups->late_init(nor); + if (ret) + return ret; + } + } } if (!nor->params->opcodes.die_erase) diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index b8a0db02e0f1..28069f62429c 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -452,6 +452,19 @@ struct spi_nor_fixups { int (*late_init)(struct spi_nor *nor); }; +/** + * struct spi_nor_fixup - SPI NOR fixup registration. + * @id: (optional) flash ID this fixup applies to, may only match the + * ID prefix, eg. just the first few bytes to match a whole family + * @match: (optional) custom match function (can be used together with @id) + * @fixups: the fixup hooks to apply when this entry matches + */ +struct spi_nor_fixup { + const struct spi_nor_id *id; + bool (*match)(const struct spi_nor *nor); + const struct spi_nor_fixups *fixups; +}; + /** * struct spi_nor_id - SPI NOR flash ID. * @@ -564,7 +577,6 @@ struct flash_info { u8 mfr_flags; const struct spi_nor_otp_organization *otp; - const struct spi_nor_fixups *fixups; }; #define SNOR_ID(...) \ @@ -586,13 +598,16 @@ struct flash_info { * @name: manufacturer name * @parts: array of parts supported by this manufacturer * @nparts: number of entries in the parts array - * @fixups: hooks called at various points in time during spi_nor_scan() + * @fixups: list of fixups, each matched by ID (or a custom match function), + * applied to any part of this manufacturer. + * @nfixups: number of entries in the fixups array */ struct spi_nor_manufacturer { const char *name; const struct flash_info *parts; unsigned int nparts; - const struct spi_nor_fixups *fixups; + const struct spi_nor_fixup *fixups; + unsigned int nfixups; }; /** @@ -623,6 +638,8 @@ extern const struct spi_nor_manufacturer spi_nor_xmc; extern const struct attribute_group *spi_nor_sysfs_groups[]; +bool spi_nor_fixup_match(const struct spi_nor *nor, + const struct spi_nor_fixup *fixup); void spi_nor_spimem_setup_op(const struct spi_nor *nor, struct spi_mem_op *op, const enum spi_nor_protocol proto); diff --git a/drivers/mtd/spi-nor/everspin.c b/drivers/mtd/spi-nor/everspin.c index add37104d673..864dffba0e9c 100644 --- a/drivers/mtd/spi-nor/everspin.c +++ b/drivers/mtd/spi-nor/everspin.c @@ -44,9 +44,14 @@ static const struct spi_nor_fixups everspin_nor_fixups = { .default_init = everspin_nor_default_init, }; +static const struct spi_nor_fixup everspin_fixups[] = { + { .fixups = &everspin_nor_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_everspin = { .name = "everspin", .parts = everspin_nor_parts, .nparts = ARRAY_SIZE(everspin_nor_parts), - .fixups = &everspin_nor_fixups, + .fixups = everspin_fixups, + .nfixups = ARRAY_SIZE(everspin_fixups), }; diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c index 4070a692e968..f76fd0dedd0a 100644 --- a/drivers/mtd/spi-nor/gigadevice.c +++ b/drivers/mtd/spi-nor/gigadevice.c @@ -64,7 +64,6 @@ static const struct flash_info gigadevice_nor_parts[] = { .id = SNOR_ID(0xc8, 0x40, 0x19), .name = "gd25q256", .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6, - .fixups = &gd25q256_fixups, .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0xc8, 0x60, 0x16), @@ -87,8 +86,14 @@ static const struct flash_info gigadevice_nor_parts[] = { }, }; +static const struct spi_nor_fixup gigadevice_fixups[] = { + { .id = SNOR_ID(0xc8, 0x40, 0x19), .fixups = &gd25q256_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_gigadevice = { .name = "gigadevice", .parts = gigadevice_nor_parts, .nparts = ARRAY_SIZE(gigadevice_nor_parts), + .fixups = gigadevice_fixups, + .nfixups = ARRAY_SIZE(gigadevice_fixups), }; diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c index 04db42b5141e..7af850eea9ff 100644 --- a/drivers/mtd/spi-nor/issi.c +++ b/drivers/mtd/spi-nor/issi.c @@ -52,13 +52,11 @@ static const struct flash_info issi_nor_parts[] = { .sector_size = SZ_32K, .size = SZ_64K, .no_sfdp_flags = SECT_4K, - .fixups = &pm25lv_nor_fixups }, { .name = "pm25lv010", .sector_size = SZ_32K, .size = SZ_128K, .no_sfdp_flags = SECT_4K, - .fixups = &pm25lv_nor_fixups }, { .id = SNOR_ID(0x7f, 0x9d, 0x20), .name = "is25cd512", @@ -103,7 +101,6 @@ static const struct flash_info issi_nor_parts[] = { }, { .id = SNOR_ID(0x9d, 0x60, 0x19), .name = "is25lp256", - .fixups = &is25lp256_fixups, .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0x9d, 0x70, 0x16), @@ -124,7 +121,6 @@ static const struct flash_info issi_nor_parts[] = { .id = SNOR_ID(0x9d, 0x70, 0x19), .name = "is25wp256", .flags = SPI_NOR_QUAD_PP, - .fixups = &is25lp256_fixups, .fixup_flags = SPI_NOR_4B_OPCODES, } }; @@ -139,9 +135,25 @@ static const struct spi_nor_fixups issi_fixups = { .default_init = issi_nor_default_init, }; +/* PM25LV parts have no JEDEC ID and are likely matched by name */ +static bool issi_pm25lv_match(const struct spi_nor *nor) +{ + const char *name = nor->info ? nor->info->name : NULL; + + return name && strstarts(name, "pm25lv"); +} + +static const struct spi_nor_fixup issi_fixup_list[] = { + { .fixups = &issi_fixups }, + { .match = issi_pm25lv_match, .fixups = &pm25lv_nor_fixups }, + { .id = SNOR_ID(0x9d, 0x60, 0x19), .fixups = &is25lp256_fixups }, + { .id = SNOR_ID(0x9d, 0x70, 0x19), .fixups = &is25lp256_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_issi = { .name = "issi", .parts = issi_nor_parts, .nparts = ARRAY_SIZE(issi_nor_parts), - .fixups = &issi_fixups, + .fixups = issi_fixup_list, + .nfixups = ARRAY_SIZE(issi_fixup_list), }; diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 954e7df21ff9..a96bd0d7c2ec 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -159,24 +159,19 @@ static const struct flash_info macronix_nor_parts[] = { .size = SZ_16M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K, - .fixups = &mx25l12805d_4pp3b_fixups, }, { /* MX25L25635E, MX25L25645G */ .id = SNOR_ID(0xc2, 0x20, 0x19), - .fixups = &mx25l25635_fixups }, { /* MX25L51245G, MX25L51273G, MX66L51235F */ .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixup_flags = SPI_NOR_4B_OPCODES, - .fixups = ¯onix_qpp4b_fixups, }, { /* MX66L1G45G */ .id = SNOR_ID(0xc2, 0x20, 0x1b), - .fixups = ¯onix_qpp4b_fixups, }, { /* MX66L2G45G */ .id = SNOR_ID(0xc2, 0x20, 0x1c), - .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x23, 0x14), .name = "mx25v8035f", @@ -215,15 +210,12 @@ static const struct flash_info macronix_nor_parts[] = { }, { /* MX25U51245G, MX25U51293G */ .id = SNOR_ID(0xc2, 0x25, 0x3a), - .fixups = ¯onix_qpp4b_fixups, }, { /* MX66U1G45G, MX66U1G93G */ .id = SNOR_ID(0xc2, 0x25, 0x3b), - .fixups = ¯onix_qpp4b_fixups, }, { /* MX66U2G45G */ .id = SNOR_ID(0xc2, 0x25, 0x3c), - .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x26, 0x18), .name = "mx25l12855e", @@ -255,7 +247,6 @@ static const struct flash_info macronix_nor_parts[] = { }, { /* MX25L3255E */ .id = SNOR_ID(0xc2, 0x9e, 0x16), - .fixups = &mx25l3255e_fixups, }, /* * This spares us of adding new flash entries for flashes that can be @@ -359,9 +350,23 @@ static const struct spi_nor_fixups macronix_nor_fixups = { .late_init = macronix_nor_late_init, }; +static const struct spi_nor_fixup macronix_fixups[] = { + { .fixups = ¯onix_nor_fixups }, + { .id = SNOR_ID(0xc2, 0x20, 0x18), .fixups = &mx25l12805d_4pp3b_fixups }, + { .id = SNOR_ID(0xc2, 0x20, 0x19), .fixups = &mx25l25635_fixups }, + { .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x20, 0x1b), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x20, 0x1c), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x25, 0x3a), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x25, 0x3b), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x25, 0x3c), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x9e, 0x16), .fixups = &mx25l3255e_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_macronix = { .name = "macronix", .parts = macronix_nor_parts, .nparts = ARRAY_SIZE(macronix_nor_parts), - .fixups = ¯onix_nor_fixups, + .fixups = macronix_fixups, + .nfixups = ARRAY_SIZE(macronix_fixups), }; diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 21d0ceac1ed7..dc1b52bd34df 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -206,13 +206,11 @@ static const struct flash_info micron_nor_parts[] = { .id = SNOR_ID(0x2c, 0x5b, 0x1a), .mfr_flags = USE_FSR, .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE, - .fixups = &mt35xu512aba_fixups, }, { /* MT35XU01GBBA */ .id = SNOR_ID(0x2c, 0x5b, 0x1b), .mfr_flags = USE_FSR, .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE, - .fixups = &mt35_two_die_fixups, }, { .id = SNOR_ID(0x2c, 0x5b, 0x1c), .name = "mt35xu02g", @@ -221,7 +219,6 @@ static const struct flash_info micron_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ, .mfr_flags = USE_FSR, .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE, - .fixups = &mt35_two_die_fixups, }, }; @@ -233,6 +230,17 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor, return 0; } +/* + * n25q00a parts share the first same 3 ID bytes with mt25qu01g. + * In order to not mix the fixups, further filter out using the part name. + */ +static bool n25q00a_match(const struct spi_nor *nor) +{ + const char *name = nor->info ? nor->info->name : NULL; + + return name && !strncmp(name, "n25q00a", 7); +} + static const struct spi_nor_fixups mt25qu512a_fixups = { .post_bfpt = mt25qu512a_post_bfpt_fixup, }; @@ -426,14 +434,12 @@ static const struct flash_info st_nor_parts[] = { SPI_NOR_BP3_SR_BIT6, .no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ, .mfr_flags = USE_FSR, - .fixups = &n25q00_fixups, }, { .id = SNOR_ID(0x20, 0xba, 0x22), .name = "mt25ql02g", .size = SZ_256M, .no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ, .mfr_flags = USE_FSR, - .fixups = &mt25q02_fixups, }, { .id = SNOR_ID(0x20, 0xbb, 0x15), .name = "n25q016a", @@ -480,7 +486,6 @@ static const struct flash_info st_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP | SPI_NOR_BP3_SR_BIT6, .mfr_flags = USE_FSR, - .fixups = &mt25qu512a_fixups, }, { .id = SNOR_ID(0x20, 0xbb, 0x20), .name = "n25q512a", @@ -493,21 +498,18 @@ static const struct flash_info st_nor_parts[] = { .id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00), .name = "mt25qu01g", .mfr_flags = USE_FSR, - .fixups = &mt25q01_fixups, }, { .id = SNOR_ID(0x20, 0xbb, 0x21), .name = "n25q00a", .size = SZ_128M, .no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ, .mfr_flags = USE_FSR, - .fixups = &n25q00_fixups, }, { .id = SNOR_ID(0x20, 0xbb, 0x22), .name = "mt25qu02g", .size = SZ_256M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .mfr_flags = USE_FSR, - .fixups = &mt25q02_fixups, } }; @@ -657,16 +659,35 @@ static const struct spi_nor_fixups micron_st_nor_fixups = { .late_init = micron_st_nor_late_init, }; +static const struct spi_nor_fixup micron_fixups[] = { + { .fixups = µn_st_nor_fixups }, + { .id = SNOR_ID(0x2c, 0x5b, 0x1a), .fixups = &mt35xu512aba_fixups }, + { .id = SNOR_ID(0x2c, 0x5b, 0x1b), .fixups = &mt35_two_die_fixups }, + { .id = SNOR_ID(0x2c, 0x5b, 0x1c), .fixups = &mt35_two_die_fixups }, +}; + +static const struct spi_nor_fixup st_fixups[] = { + { .fixups = µn_st_nor_fixups }, + { .id = SNOR_ID(0x20, 0xba, 0x21), .fixups = &n25q00_fixups }, + { .id = SNOR_ID(0x20, 0xba, 0x22), .fixups = &mt25q02_fixups }, + { .id = SNOR_ID(0x20, 0xbb, 0x20, 0x10, 0x44, 0x00), .fixups = &mt25qu512a_fixups }, + { .id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00), .fixups = &mt25q01_fixups }, + { .id = SNOR_ID(0x20, 0xbb, 0x21), .match = n25q00a_match, .fixups = &n25q00_fixups }, + { .id = SNOR_ID(0x20, 0xbb, 0x22), .fixups = &mt25q02_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_micron = { .name = "micron", .parts = micron_nor_parts, .nparts = ARRAY_SIZE(micron_nor_parts), - .fixups = µn_st_nor_fixups, + .fixups = micron_fixups, + .nfixups = ARRAY_SIZE(micron_fixups), }; const struct spi_nor_manufacturer spi_nor_st = { .name = "st", .parts = st_nor_parts, .nparts = ARRAY_SIZE(st_nor_parts), - .fixups = µn_st_nor_fixups, + .fixups = st_fixups, + .nfixups = ARRAY_SIZE(st_fixups), }; diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 23c11fbc68c3..b0b753fb2345 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -416,18 +416,23 @@ static int spi_nor_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { + const struct spi_nor_fixup *fixups; + unsigned int i; int ret; - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->post_bfpt) { - ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header, - bfpt); - if (ret) - return ret; - } + if (!nor->manufacturer || !nor->manufacturer->fixups) + return 0; - if (nor->info->fixups && nor->info->fixups->post_bfpt) - return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt); + fixups = nor->manufacturer->fixups; + + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (fixups[i].fixups->post_bfpt && + spi_nor_fixup_match(nor, &fixups[i])) { + ret = fixups[i].fixups->post_bfpt(nor, bfpt_header, bfpt); + if (ret) + return ret; + } + } return 0; } @@ -758,12 +763,19 @@ static u8 spi_nor_smpt_addr_nbytes(const struct spi_nor *nor, const u32 settings static void spi_nor_smpt_read_dummy_fixups(const struct spi_nor *nor, u8 *read_dummy) { - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->smpt_read_dummy) - nor->manufacturer->fixups->smpt_read_dummy(nor, read_dummy); + const struct spi_nor_fixup *fixups; + unsigned int i; + + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; - if (nor->info->fixups && nor->info->fixups->smpt_read_dummy) - nor->info->fixups->smpt_read_dummy(nor, read_dummy); + fixups = nor->manufacturer->fixups; + + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (fixups[i].fixups->smpt_read_dummy && + spi_nor_fixup_match(nor, &fixups[i])) + fixups[i].fixups->smpt_read_dummy(nor, read_dummy); + } } /** @@ -788,12 +800,19 @@ static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings) static void spi_nor_smpt_map_id_fixups(const struct spi_nor *nor, u8 *map_id) { - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->smpt_map_id) - nor->manufacturer->fixups->smpt_map_id(nor, map_id); + const struct spi_nor_fixup *fixups; + unsigned int i; + + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; + + fixups = nor->manufacturer->fixups; - if (nor->info->fixups && nor->info->fixups->smpt_map_id) - nor->info->fixups->smpt_map_id(nor, map_id); + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (fixups[i].fixups->smpt_map_id && + spi_nor_fixup_match(nor, &fixups[i])) + fixups[i].fixups->smpt_map_id(nor, map_id); + } } /** @@ -1445,17 +1464,23 @@ out: */ static int spi_nor_post_sfdp_fixups(struct spi_nor *nor) { + const struct spi_nor_fixup *fixups; + unsigned int i; int ret; - if (nor->manufacturer && nor->manufacturer->fixups && - nor->manufacturer->fixups->post_sfdp) { - ret = nor->manufacturer->fixups->post_sfdp(nor); - if (ret) - return ret; - } + if (!nor->manufacturer || !nor->manufacturer->fixups) + return 0; + + fixups = nor->manufacturer->fixups; - if (nor->info->fixups && nor->info->fixups->post_sfdp) - return nor->info->fixups->post_sfdp(nor); + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (fixups[i].fixups->post_sfdp && + spi_nor_fixup_match(nor, &fixups[i])) { + ret = fixups[i].fixups->post_sfdp(nor); + if (ret) + return ret; + } + } return 0; } diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 9c27c00a3a04..4ef8839541ec 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -907,7 +907,6 @@ static const struct flash_info spansion_nor_parts[] = { .sector_size = SZ_256K, .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .mfr_flags = USE_CLSR, - .fixups = &s25fs_s_nor_fixups, }, { .id = SNOR_ID(0x01, 0x20, 0x18, 0x03, 0x00), .name = "s25sl12800", @@ -943,7 +942,6 @@ static const struct flash_info spansion_nor_parts[] = { .size = SZ_16M, .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .mfr_flags = USE_CLSR, - .fixups = &s25fs_s_nor_fixups, }, { .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01), .name = "s25fl129p1", @@ -1003,76 +1001,61 @@ static const struct flash_info spansion_nor_parts[] = { .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90), .name = "s25hl512t", .mfr_flags = USE_CLPEF, - .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90), .name = "s25hl01gt", .mfr_flags = USE_CLPEF, - .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90), .name = "s25hl02gt", .mfr_flags = USE_CLPEF, - .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90), .name = "s25fs256t", .mfr_flags = USE_CLPEF, - .fixups = &s25fs256t_fixups }, { .id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90), .name = "s25hs512t", .mfr_flags = USE_CLPEF, - .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90), .name = "s25hs01gt", .mfr_flags = USE_CLPEF, - .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90), .name = "s25hs02gt", .mfr_flags = USE_CLPEF, - .fixups = &s25hx_t_fixups }, { /* S28HL256T */ .id = SNOR_ID(0x34, 0x5a, 0x19), .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5a, 0x1a), .name = "s28hl512t", .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5a, 0x1b), .name = "s28hl01gt", .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { /* S28HL02GT */ .id = SNOR_ID(0x34, 0x5a, 0x1c), .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5b, 0x19), .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5b, 0x1a), .name = "s28hs512t", .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5b, 0x1b), .name = "s28hs01gt", .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5b, 0x1c), .name = "s28hs02gt", .mfr_flags = USE_CLPEF, - .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0xef, 0x40, 0x13), .name = "s25fl004k", @@ -1172,9 +1155,31 @@ static const struct spi_nor_fixups spansion_nor_fixups = { .late_init = spansion_nor_late_init, }; +static const struct spi_nor_fixup spansion_fixups[] = { + { .fixups = &spansion_nor_fixups }, + { .id = SNOR_ID(0x01, 0x02, 0x20, 0x4d, 0x00, 0x81), .fixups = &s25fs_s_nor_fixups }, + { .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups }, + { .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90), .fixups = &s25fs256t_fixups }, + { .id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5a, 0x19), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5a, 0x1a), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5a, 0x1b), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5a, 0x1c), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5b, 0x19), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5b, 0x1a), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5b, 0x1b), .fixups = &s28hx_t_fixups }, + { .id = SNOR_ID(0x34, 0x5b, 0x1c), .fixups = &s28hx_t_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_spansion = { .name = "spansion", .parts = spansion_nor_parts, .nparts = ARRAY_SIZE(spansion_nor_parts), - .fixups = &spansion_nor_fixups, + .fixups = spansion_fixups, + .nfixups = ARRAY_SIZE(spansion_fixups), }; diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index 77a0bc41d4e5..c460803b278b 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -152,14 +152,12 @@ static const struct flash_info sst_nor_parts[] = { .id = SNOR_ID(0xbf, 0x26, 0x42), .name = "sst26vf032b", .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, - .fixups = &sst26vf_nor_fixups, }, { .id = SNOR_ID(0xbf, 0x26, 0x43), .name = "sst26vf064b", .size = SZ_8M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixups = &sst26vf_nor_fixups, }, { .id = SNOR_ID(0xbf, 0x26, 0x51), .name = "sst26wf016b", @@ -278,9 +276,16 @@ static const struct spi_nor_fixups sst_nor_fixups = { .late_init = sst_nor_late_init, }; +static const struct spi_nor_fixup sst_fixups[] = { + { .fixups = &sst_nor_fixups }, + { .id = SNOR_ID(0xbf, 0x26, 0x42), .fixups = &sst26vf_nor_fixups }, + { .id = SNOR_ID(0xbf, 0x26, 0x43), .fixups = &sst26vf_nor_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_sst = { .name = "sst", .parts = sst_nor_parts, .nparts = ARRAY_SIZE(sst_nor_parts), - .fixups = &sst_nor_fixups, + .fixups = sst_fixups, + .nfixups = ARRAY_SIZE(sst_fixups), }; diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index df4688c637a2..063b85e041cd 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -219,7 +219,6 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_16M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, - .fixups = &w25q128_fixups, }, { /* W25Q256JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x19), @@ -228,7 +227,6 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &w25q256_fixups, }, { /* W25Q512JV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x20), @@ -242,7 +240,6 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x40, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_nor_multi_die_fixups, }, { .id = SNOR_ID(0xef, 0x50, 0x12), .name = "w25q20bw", @@ -345,13 +342,11 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_nor_multi_die_fixups, }, { /* W25Q02JV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, - .fixups = &winbond_nor_multi_die_fixups, }, { .id = SNOR_ID(0xef, 0x71, 0x19), .name = "w25m512jv", @@ -553,9 +548,19 @@ static const struct spi_nor_fixups winbond_nor_fixups = { .late_init = winbond_nor_late_init, }; +static const struct spi_nor_fixup winbond_fixups[] = { + { .fixups = &winbond_nor_fixups }, + { .id = SNOR_ID(0xef, 0x40, 0x18), .fixups = &w25q128_fixups }, + { .id = SNOR_ID(0xef, 0x40, 0x19), .fixups = &w25q256_fixups }, + { .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups }, + { .id = SNOR_ID(0xef, 0x70, 0x21), .fixups = &winbond_nor_multi_die_fixups }, + { .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups }, +}; + const struct spi_nor_manufacturer spi_nor_winbond = { .name = "winbond", .parts = winbond_nor_parts, .nparts = ARRAY_SIZE(winbond_nor_parts), - .fixups = &winbond_nor_fixups, + .fixups = winbond_fixups, + .nfixups = ARRAY_SIZE(winbond_fixups), }; -- cgit v1.2.3 From e6dfda3c83eb8463bdf22289cfb42de9938446d0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:43 +0200 Subject: mtd: spi-nor: Move the fixup flags into the fixup list The idea of the fixup flags is to enable fixups without having to repeat the same fixup function over and over again. It overall reduces the boilerplate with a similar intent: fixing our knowledge of the flash. Move the fixup_flags field as well as the associated flag definitions to the spi_nor_fixup structure and enable them based on the IDs. No functional change. Suggested-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 25 ++++++++++++++++++------- drivers/mtd/spi-nor/core.h | 30 ++++++++++++++---------------- drivers/mtd/spi-nor/gigadevice.c | 4 ++-- drivers/mtd/spi-nor/issi.c | 8 ++++---- drivers/mtd/spi-nor/macronix.c | 4 ++-- drivers/mtd/spi-nor/micron-st.c | 21 ++++++++++++--------- drivers/mtd/spi-nor/sfdp.c | 10 +++++----- drivers/mtd/spi-nor/spansion.c | 6 +++--- 8 files changed, 60 insertions(+), 48 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 322683738ef1..d1fd94c391f4 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2654,7 +2654,7 @@ static void spi_nor_manufacturer_init_params(struct spi_nor *nor) fixups = nor->manufacturer->fixups; for (i = 0; i < nor->manufacturer->nfixups; i++) { - if (fixups[i].fixups->default_init && + if (fixups[i].fixups && fixups[i].fixups->default_init && spi_nor_fixup_match(nor, &fixups[i])) fixups[i].fixups->default_init(nor); } @@ -2787,13 +2787,24 @@ static void spi_nor_init_flags(struct spi_nor *nor) static void spi_nor_init_fixup_flags(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; - const u8 fixup_flags = nor->info->fixup_flags; + const struct spi_nor_fixup *fixups; + unsigned int i; + + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; - if (fixup_flags & SPI_NOR_4B_OPCODES) - params->flags |= SNOR_F_4B_OPCODES; + fixups = nor->manufacturer->fixups; - if (fixup_flags & SPI_NOR_IO_MODE_EN_VOLATILE) - params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + for (i = 0; i < nor->manufacturer->nfixups; i++) { + if (!fixups[i].fixup_flags || + !spi_nor_fixup_match(nor, &fixups[i])) + continue; + + if (fixups[i].fixup_flags & SPI_NOR_4B_OPCODES) + params->flags |= SNOR_F_4B_OPCODES; + if (fixups[i].fixup_flags & SPI_NOR_IO_MODE_EN_VOLATILE) + params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + } } /** @@ -2818,7 +2829,7 @@ static int spi_nor_late_init_params(struct spi_nor *nor) fixups = nor->manufacturer->fixups; for (i = 0; i < nor->manufacturer->nfixups; i++) { - if (fixups[i].fixups->late_init && + if (fixups[i].fixups && fixups[i].fixups->late_init && spi_nor_fixup_match(nor, &fixups[i])) { ret = fixups[i].fixups->late_init(nor); if (ret) diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 28069f62429c..670182b3c2ad 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -457,11 +457,25 @@ struct spi_nor_fixups { * @id: (optional) flash ID this fixup applies to, may only match the * ID prefix, eg. just the first few bytes to match a whole family * @match: (optional) custom match function (can be used together with @id) + * @fixup_flags: flags that indicate support that can be discovered via SFDP + * ideally, but can not be discovered for this particular flash + * because the SFDP table that indicates this support is not + * defined by the flash. In case the table for this support is + * defined but has wrong values, one should instead use a + * post_sfdp() hook to set the SNOR_F equivalent flag. + * + * SPI_NOR_4B_OPCODES: use dedicated 4byte address op codes to support + * memory size above 128Mib. + * SPI_NOR_IO_MODE_EN_VOLATILE: flash enables the best available I/O mode + * via a volatile bit. * @fixups: the fixup hooks to apply when this entry matches */ struct spi_nor_fixup { const struct spi_nor_id *id; bool (*match)(const struct spi_nor *nor); + u8 fixup_flags; +#define SPI_NOR_4B_OPCODES BIT(0) +#define SPI_NOR_IO_MODE_EN_VOLATILE BIT(1) const struct spi_nor_fixups *fixups; }; @@ -524,22 +538,10 @@ struct spi_nor_id { * SPI_NOR_OCTAL_DTR_READ: flash supports octal DTR Read. * SPI_NOR_OCTAL_DTR_PP: flash supports Octal DTR Page Program. * - * @fixup_flags: flags that indicate support that can be discovered via SFDP - * ideally, but can not be discovered for this particular flash - * because the SFDP table that indicates this support is not - * defined by the flash. In case the table for this support is - * defined but has wrong values, one should instead use a - * post_sfdp() hook to set the SNOR_F equivalent flag. - * - * SPI_NOR_4B_OPCODES: use dedicated 4byte address op codes to support - * memory size above 128Mib. - * SPI_NOR_IO_MODE_EN_VOLATILE: flash enables the best available I/O mode - * via a volatile bit. * @mfr_flags: manufacturer private flags. Used in the manufacturer fixup * hooks to differentiate support between flashes of the same * manufacturer. * @otp_org: flash's OTP organization. - * @fixups: part specific fixup hooks. */ struct flash_info { char *name; @@ -570,10 +572,6 @@ struct flash_info { #define SPI_NOR_OCTAL_DTR_READ BIT(6) #define SPI_NOR_OCTAL_DTR_PP BIT(7) - u8 fixup_flags; -#define SPI_NOR_4B_OPCODES BIT(0) -#define SPI_NOR_IO_MODE_EN_VOLATILE BIT(1) - u8 mfr_flags; const struct spi_nor_otp_organization *otp; diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c index f76fd0dedd0a..6a2e6ebda148 100644 --- a/drivers/mtd/spi-nor/gigadevice.c +++ b/drivers/mtd/spi-nor/gigadevice.c @@ -64,7 +64,6 @@ static const struct flash_info gigadevice_nor_parts[] = { .id = SNOR_ID(0xc8, 0x40, 0x19), .name = "gd25q256", .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6, - .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0xc8, 0x60, 0x16), .name = "gd25lq32", @@ -87,7 +86,8 @@ static const struct flash_info gigadevice_nor_parts[] = { }; static const struct spi_nor_fixup gigadevice_fixups[] = { - { .id = SNOR_ID(0xc8, 0x40, 0x19), .fixups = &gd25q256_fixups }, + { .id = SNOR_ID(0xc8, 0x40, 0x19), .fixups = &gd25q256_fixups, + .fixup_flags = SPI_NOR_4B_OPCODES }, }; const struct spi_nor_manufacturer spi_nor_gigadevice = { diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c index 7af850eea9ff..2f057d731df2 100644 --- a/drivers/mtd/spi-nor/issi.c +++ b/drivers/mtd/spi-nor/issi.c @@ -101,7 +101,6 @@ static const struct flash_info issi_nor_parts[] = { }, { .id = SNOR_ID(0x9d, 0x60, 0x19), .name = "is25lp256", - .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0x9d, 0x70, 0x16), .name = "is25wp032", @@ -121,7 +120,6 @@ static const struct flash_info issi_nor_parts[] = { .id = SNOR_ID(0x9d, 0x70, 0x19), .name = "is25wp256", .flags = SPI_NOR_QUAD_PP, - .fixup_flags = SPI_NOR_4B_OPCODES, } }; @@ -146,8 +144,10 @@ static bool issi_pm25lv_match(const struct spi_nor *nor) static const struct spi_nor_fixup issi_fixup_list[] = { { .fixups = &issi_fixups }, { .match = issi_pm25lv_match, .fixups = &pm25lv_nor_fixups }, - { .id = SNOR_ID(0x9d, 0x60, 0x19), .fixups = &is25lp256_fixups }, - { .id = SNOR_ID(0x9d, 0x70, 0x19), .fixups = &is25lp256_fixups }, + { .id = SNOR_ID(0x9d, 0x60, 0x19), .fixups = &is25lp256_fixups, + .fixup_flags = SPI_NOR_4B_OPCODES }, + { .id = SNOR_ID(0x9d, 0x70, 0x19), .fixups = &is25lp256_fixups, + .fixup_flags = SPI_NOR_4B_OPCODES }, }; const struct spi_nor_manufacturer spi_nor_issi = { diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index a96bd0d7c2ec..b3fdb4b2fa1c 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -165,7 +165,6 @@ static const struct flash_info macronix_nor_parts[] = { }, { /* MX25L51245G, MX25L51273G, MX66L51235F */ .id = SNOR_ID(0xc2, 0x20, 0x1a), - .fixup_flags = SPI_NOR_4B_OPCODES, }, { /* MX66L1G45G */ .id = SNOR_ID(0xc2, 0x20, 0x1b), @@ -354,7 +353,8 @@ static const struct spi_nor_fixup macronix_fixups[] = { { .fixups = ¯onix_nor_fixups }, { .id = SNOR_ID(0xc2, 0x20, 0x18), .fixups = &mx25l12805d_4pp3b_fixups }, { .id = SNOR_ID(0xc2, 0x20, 0x19), .fixups = &mx25l25635_fixups }, - { .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixups = ¯onix_qpp4b_fixups }, + { .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixups = ¯onix_qpp4b_fixups, + .fixup_flags = SPI_NOR_4B_OPCODES }, { .id = SNOR_ID(0xc2, 0x20, 0x1b), .fixups = ¯onix_qpp4b_fixups }, { .id = SNOR_ID(0xc2, 0x20, 0x1c), .fixups = ¯onix_qpp4b_fixups }, { .id = SNOR_ID(0xc2, 0x25, 0x3a), .fixups = ¯onix_qpp4b_fixups }, diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index dc1b52bd34df..f97f2dead8c2 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -205,12 +205,10 @@ static const struct flash_info micron_nor_parts[] = { /* MT35XU512ABA */ .id = SNOR_ID(0x2c, 0x5b, 0x1a), .mfr_flags = USE_FSR, - .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE, }, { /* MT35XU01GBBA */ .id = SNOR_ID(0x2c, 0x5b, 0x1b), .mfr_flags = USE_FSR, - .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE, }, { .id = SNOR_ID(0x2c, 0x5b, 0x1c), .name = "mt35xu02g", @@ -218,7 +216,6 @@ static const struct flash_info micron_nor_parts[] = { .size = SZ_256M, .no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ, .mfr_flags = USE_FSR, - .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE, }, }; @@ -403,7 +400,6 @@ static const struct flash_info st_nor_parts[] = { .name = "mt25ql256a", .size = SZ_32M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixup_flags = SPI_NOR_4B_OPCODES, .mfr_flags = USE_FSR, }, { .id = SNOR_ID(0x20, 0xba, 0x19), @@ -416,7 +412,6 @@ static const struct flash_info st_nor_parts[] = { .name = "mt25ql512a", .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixup_flags = SPI_NOR_4B_OPCODES, .mfr_flags = USE_FSR, }, { .id = SNOR_ID(0x20, 0xba, 0x20), @@ -472,7 +467,6 @@ static const struct flash_info st_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP | SPI_NOR_BP3_SR_BIT6, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixup_flags = SPI_NOR_4B_OPCODES, .mfr_flags = USE_FSR, }, { .id = SNOR_ID(0x20, 0xbb, 0x19), @@ -661,13 +655,22 @@ static const struct spi_nor_fixups micron_st_nor_fixups = { static const struct spi_nor_fixup micron_fixups[] = { { .fixups = µn_st_nor_fixups }, - { .id = SNOR_ID(0x2c, 0x5b, 0x1a), .fixups = &mt35xu512aba_fixups }, - { .id = SNOR_ID(0x2c, 0x5b, 0x1b), .fixups = &mt35_two_die_fixups }, - { .id = SNOR_ID(0x2c, 0x5b, 0x1c), .fixups = &mt35_two_die_fixups }, + { .id = SNOR_ID(0x2c, 0x5b, 0x1a), .fixups = &mt35xu512aba_fixups, + .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE }, + { .id = SNOR_ID(0x2c, 0x5b, 0x1b), .fixups = &mt35_two_die_fixups, + .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE }, + { .id = SNOR_ID(0x2c, 0x5b, 0x1c), .fixups = &mt35_two_die_fixups, + .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE }, }; static const struct spi_nor_fixup st_fixups[] = { { .fixups = µn_st_nor_fixups }, + { .id = SNOR_ID(0x20, 0xba, 0x19, 0x10, 0x44, 0x00), + .fixup_flags = SPI_NOR_4B_OPCODES }, + { .id = SNOR_ID(0x20, 0xba, 0x20, 0x10, 0x44, 0x00), + .fixup_flags = SPI_NOR_4B_OPCODES }, + { .id = SNOR_ID(0x20, 0xbb, 0x19, 0x10, 0x44, 0x00), + .fixup_flags = SPI_NOR_4B_OPCODES }, { .id = SNOR_ID(0x20, 0xba, 0x21), .fixups = &n25q00_fixups }, { .id = SNOR_ID(0x20, 0xba, 0x22), .fixups = &mt25q02_fixups }, { .id = SNOR_ID(0x20, 0xbb, 0x20, 0x10, 0x44, 0x00), .fixups = &mt25qu512a_fixups }, diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index b0b753fb2345..823cda6874aa 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -417,8 +417,8 @@ static int spi_nor_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_bfpt *bfpt) { const struct spi_nor_fixup *fixups; - unsigned int i; int ret; + unsigned int i; if (!nor->manufacturer || !nor->manufacturer->fixups) return 0; @@ -426,7 +426,7 @@ static int spi_nor_post_bfpt_fixups(struct spi_nor *nor, fixups = nor->manufacturer->fixups; for (i = 0; i < nor->manufacturer->nfixups; i++) { - if (fixups[i].fixups->post_bfpt && + if (fixups[i].fixups && fixups[i].fixups->post_bfpt && spi_nor_fixup_match(nor, &fixups[i])) { ret = fixups[i].fixups->post_bfpt(nor, bfpt_header, bfpt); if (ret) @@ -772,7 +772,7 @@ static void spi_nor_smpt_read_dummy_fixups(const struct spi_nor *nor, fixups = nor->manufacturer->fixups; for (i = 0; i < nor->manufacturer->nfixups; i++) { - if (fixups[i].fixups->smpt_read_dummy && + if (fixups[i].fixups && fixups[i].fixups->smpt_read_dummy && spi_nor_fixup_match(nor, &fixups[i])) fixups[i].fixups->smpt_read_dummy(nor, read_dummy); } @@ -809,7 +809,7 @@ static void spi_nor_smpt_map_id_fixups(const struct spi_nor *nor, u8 *map_id) fixups = nor->manufacturer->fixups; for (i = 0; i < nor->manufacturer->nfixups; i++) { - if (fixups[i].fixups->smpt_map_id && + if (fixups[i].fixups && fixups[i].fixups->smpt_map_id && spi_nor_fixup_match(nor, &fixups[i])) fixups[i].fixups->smpt_map_id(nor, map_id); } @@ -1474,7 +1474,7 @@ static int spi_nor_post_sfdp_fixups(struct spi_nor *nor) fixups = nor->manufacturer->fixups; for (i = 0; i < nor->manufacturer->nfixups; i++) { - if (fixups[i].fixups->post_sfdp && + if (fixups[i].fixups && fixups[i].fixups->post_sfdp && spi_nor_fixup_match(nor, &fixups[i])) { ret = fixups[i].fixups->post_sfdp(nor); if (ret) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 4ef8839541ec..6938fe26969c 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -978,19 +978,16 @@ static const struct flash_info spansion_nor_parts[] = { .name = "s25fl064l", .size = SZ_8M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0x01, 0x60, 0x18), .name = "s25fl128l", .size = SZ_16M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0x01, 0x60, 0x19), .name = "s25fl256l", .size = SZ_32M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .fixup_flags = SPI_NOR_4B_OPCODES, }, { .id = SNOR_ID(0x04, 0x2c, 0xc2, 0x7f, 0x7f, 0x7f), .name = "cy15x104q", @@ -1159,6 +1156,9 @@ static const struct spi_nor_fixup spansion_fixups[] = { { .fixups = &spansion_nor_fixups }, { .id = SNOR_ID(0x01, 0x02, 0x20, 0x4d, 0x00, 0x81), .fixups = &s25fs_s_nor_fixups }, { .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups }, + { .id = SNOR_ID(0x01, 0x60, 0x17), .fixup_flags = SPI_NOR_4B_OPCODES }, + { .id = SNOR_ID(0x01, 0x60, 0x18), .fixup_flags = SPI_NOR_4B_OPCODES }, + { .id = SNOR_ID(0x01, 0x60, 0x19), .fixup_flags = SPI_NOR_4B_OPCODES }, { .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups }, { .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups }, -- cgit v1.2.3 From 98b89d868a178d47277feede51314d8227d4cb56 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:44 +0200 Subject: mtd: spi-nor: Create an indirection on the part name We currently print through sysfs and debugfs the name of the part if the .name field, which is legacy, has been filled in the ID table (otherwise "(null)" is printed). These IDs had an interest until manufacturers started re-using more and more extensively the so called unique JEDEC IDs. At this stage, a name that was tailored for a chip sold in 2016 may no longer be relevant for a chip sold in 2026. Still showing this name through the various filesystems may confuse people, removing these names cannot be done since they are now part of the user ABI (because of sysfs). In order to allow overwriting the .name field (which is part of a read-only structure), let's create a spi-nor pointer which will by default point to that .name, but which can be cleared in fixup hooks to make sure the name is hidden on newer parts. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 1 + drivers/mtd/spi-nor/debugfs.c | 3 +-- drivers/mtd/spi-nor/sysfs.c | 4 ++-- include/linux/mtd/spi-nor.h | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index d1fd94c391f4..e2b6efafdd8d 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3438,6 +3438,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, return PTR_ERR(info); nor->info = info; + nor->partname = info->name; mutex_init(&nor->lock); diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 51c0582f793a..5c738786145f 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -81,13 +81,12 @@ static int spi_nor_params_show(struct seq_file *s, void *data) struct spi_nor_flash_parameter *params = nor->params; struct spi_nor_erase_map *erase_map = ¶ms->erase_map; struct spi_nor_erase_region *region = erase_map->regions; - const struct flash_info *info = nor->info; char buf[16], *str; loff_t lock_start; u64 lock_length; unsigned int i; - seq_printf(s, "name\t\t%s\n", info->name); + seq_printf(s, "name\t\t%s\n", nor->partname); seq_printf(s, "id\t\t%*ph\n", SPI_NOR_MAX_ID_LEN, nor->id); string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); seq_printf(s, "size\t\t%s\n", buf); diff --git a/drivers/mtd/spi-nor/sysfs.c b/drivers/mtd/spi-nor/sysfs.c index 643513ee891b..a2dabe4c6422 100644 --- a/drivers/mtd/spi-nor/sysfs.c +++ b/drivers/mtd/spi-nor/sysfs.c @@ -25,7 +25,7 @@ static ssize_t partname_show(struct device *dev, struct spi_mem *spimem = spi_get_drvdata(spi); struct spi_nor *nor = spi_mem_get_drvdata(spimem); - return sysfs_emit(buf, "%s\n", nor->info->name); + return sysfs_emit(buf, "%s\n", nor->partname); } static DEVICE_ATTR_RO(partname); @@ -78,7 +78,7 @@ static umode_t spi_nor_sysfs_is_visible(struct kobject *kobj, if (attr == &dev_attr_manufacturer.attr && !nor->manufacturer) return 0; - if (attr == &dev_attr_partname.attr && !nor->info->name) + if (attr == &dev_attr_partname.attr && !nor->partname) return 0; if (attr == &dev_attr_jedec_id.attr && !nor->info->id && !nor->id) return 0; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 539eb514588c..b3e3c6b10186 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -393,6 +393,7 @@ struct spi_nor { u8 *id; const struct flash_info *info; const struct spi_nor_manufacturer *manufacturer; + const char *partname; u8 addr_nbytes; u8 erase_opcode; u8 read_opcode; -- cgit v1.2.3 From aa0a09e0e99838ab9349b1958118c44e6db6737d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:45 +0200 Subject: mtd: spi-nor: Move the SFDP header structure to a C header The sfdp_header structure is currently defined in sfdp.c, whereas the sfdp_parameter_header structure is defined in sfdp.h. In order to ease the reuse of this structure outside of the sfdp.c file, move this structure to the sfdp C header (.h). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 11 ----------- drivers/mtd/spi-nor/sfdp.h | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 823cda6874aa..95f47c0c9317 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -34,17 +34,6 @@ #define SFDP_SIGNATURE 0x50444653U -struct sfdp_header { - u32 signature; /* Ox50444653U <=> "SFDP" */ - u8 minor; - u8 major; - u8 nph; /* 0-base number of parameter headers */ - u8 unused; - - /* Basic Flash Parameter Table. */ - struct sfdp_parameter_header bfpt_header; -}; - /* Fast Read settings. */ struct sfdp_bfpt_read { /* The Fast Read x-y-z hardware capability in params->hwcaps.mask. */ diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h index e8ed814c6530..2cbe22246c5e 100644 --- a/drivers/mtd/spi-nor/sfdp.h +++ b/drivers/mtd/spi-nor/sfdp.h @@ -149,4 +149,15 @@ struct sfdp_parameter_header { u8 id_msb; }; +struct sfdp_header { + u32 signature; /* Ox50444653U <=> "SFDP" */ + u8 minor; + u8 major; + u8 nph; /* 0-base number of parameter headers */ + u8 unused; + + /* Basic Flash Parameter Table. */ + struct sfdp_parameter_header bfpt_header; +}; + #endif /* __LINUX_MTD_SFDP_H */ -- cgit v1.2.3 From 7fdc105ff98d712e1e2f7e9f55e4ab2251e1344f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:46 +0200 Subject: mtd: spi-nor: winbond: Prepare introduction of W25QxxRV-Q/N parts There is an ID collision between the JV and RV families. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). They also do not require the multi-die fixups which must remain assigned to the JV chips. Finally, since they share the IDs but not the names, we must hide the names using a fixup. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 11 ++++++++ drivers/mtd/spi-nor/sfdp.h | 2 ++ drivers/mtd/spi-nor/winbond.c | 60 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 95f47c0c9317..c21a6953db96 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1715,3 +1715,14 @@ free_param_headers: return err; } + +/** + * spi_nor_sfdp_get_header() - retrieves the SFDP header + * @nor: pointer to a 'struct spi_nor' with a valid SFDP field + * + * Return: the cached SFDP header. + */ +struct sfdp_header *spi_nor_sfdp_get_header(const struct spi_nor *nor) +{ + return (struct sfdp_header *)nor->sfdp->dwords; +} diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h index 2cbe22246c5e..1e49ed548a7e 100644 --- a/drivers/mtd/spi-nor/sfdp.h +++ b/drivers/mtd/spi-nor/sfdp.h @@ -160,4 +160,6 @@ struct sfdp_header { struct sfdp_parameter_header bfpt_header; }; +struct sfdp_header *spi_nor_sfdp_get_header(const struct spi_nor *nor); + #endif /* __LINUX_MTD_SFDP_H */ diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 063b85e041cd..9af4f21e82bf 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -24,6 +24,39 @@ 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); + + /* + * W25QxxRV chips re-use the same ID as the W25QxxJV family. + * + * Chips are very similar, W25QxxRV brings mostly performance and power + * consumption improvements. The RV family does not require the multi + * die fixup. + * + * They can be distinguished based on their SFDP minor revision: + * W25QxxJV: JESD216A, minor revision == 05h + * W25Q512/01/02JV: JESD216B, minor revision == 06h + * W25QxxRV: JESD216F, minor revision >= 0Ah + */ + return sfdp_h->minor >= SFDP_JESD216F_MINOR; +} + +/* + * Since SFDP is populated after ->default_init(), the match functions using + * nor->sfdp as discriminant cannot be used for this specific early fixup. + */ +static bool winbond_jv_match(const struct spi_nor *nor) +{ + return !nor->sfdp || !is_w25qxxrv(nor); +} + +static bool winbond_rv_match(const struct spi_nor *nor) +{ + return nor->sfdp && is_w25qxxrv(nor); +} + static int w25q128_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, @@ -146,6 +179,22 @@ static const struct spi_nor_fixups winbond_nor_multi_die_fixups = { .post_sfdp = winbond_nor_multi_die_post_sfdp_fixups, }; +static int winbond_nor_partname_post_sfdp_fixups(struct spi_nor *nor) +{ + /* + * W25QxxRV parts re-use the JEDEC IDs of the JV family. Their name + * being a legacy field, it is kept for the already established JV parts + * but must not be exposed by the newer RV ones. + */ + nor->partname = NULL; + + return 0; +} + +static const struct spi_nor_fixups winbond_nor_partname_fixups = { + .post_sfdp = winbond_nor_partname_post_sfdp_fixups, +}; + static const struct flash_info winbond_nor_parts[] = { { .id = SNOR_ID(0xef, 0x30, 0x10), @@ -552,9 +601,14 @@ static const struct spi_nor_fixup winbond_fixups[] = { { .fixups = &winbond_nor_fixups }, { .id = SNOR_ID(0xef, 0x40, 0x18), .fixups = &w25q128_fixups }, { .id = SNOR_ID(0xef, 0x40, 0x19), .fixups = &w25q256_fixups }, - { .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups }, - { .id = SNOR_ID(0xef, 0x70, 0x21), .fixups = &winbond_nor_multi_die_fixups }, - { .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups }, + { .id = SNOR_ID(0xef, 0x40), .match = winbond_rv_match, + .fixups = &winbond_nor_partname_fixups }, + { .id = SNOR_ID(0xef, 0x40, 0x21), .match = winbond_jv_match, + .fixups = &winbond_nor_multi_die_fixups }, + { .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match, + .fixups = &winbond_nor_multi_die_fixups }, + { .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match, + .fixups = &winbond_nor_multi_die_fixups }, }; const struct spi_nor_manufacturer spi_nor_winbond = { -- cgit v1.2.3 From 7e896ca2fcf15066a6957120dfd9063820506cd2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:47 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q32RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 9af4f21e82bf..1a7522dad377 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -247,7 +247,7 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_1M, .no_sfdp_flags = SECT_4K, }, { - /* W25Q32JV-Q/N */ + /* W25Q32JV-Q/N, W25Q32RV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x16), .name = "w25q32", .size = SZ_4M, -- cgit v1.2.3 From 25949585ca2f31f39a5b116aee23ffa017281929 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:48 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q64RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 1a7522dad377..a013234951f8 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -254,7 +254,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q64JV-Q/N */ + /* W25Q64JV-Q/N, W25Q64RV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x17), .name = "w25q64", .size = SZ_8M, -- cgit v1.2.3 From f84397a769c9aa45e9a6b6a6f1253c1f990c7c72 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:49 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q12RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). This density wrongly advertizes its size and requires a fixup for that. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index a013234951f8..38355d26bfd4 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -43,6 +43,20 @@ static bool is_w25qxxrv(const struct spi_nor *nor) return sfdp_h->minor >= SFDP_JESD216F_MINOR; } +static bool is_zd25q128c(const struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header) +{ + /* + * Zetta ZD25Q128C is a clone of the Winbond device. But the encoded + * size is really wrong. It seems that they confused Mbit with MiB. + * Thus the flash is discovered as a 2MiB device. + */ + return bfpt_header->major == SFDP_JESD216_MAJOR && + bfpt_header->minor == SFDP_JESD216_MINOR && + nor->params->size == SZ_2M && + nor->params->erase_map.regions[0].size == SZ_2M; +} + /* * Since SFDP is populated after ->default_init(), the match functions using * nor->sfdp as discriminant cannot be used for this specific early fixup. @@ -62,15 +76,7 @@ w25q128_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, const struct sfdp_bfpt *bfpt) { - /* - * Zetta ZD25Q128C is a clone of the Winbond device. But the encoded - * size is really wrong. It seems that they confused Mbit with MiB. - * Thus the flash is discovered as a 2MiB device. - */ - if (bfpt_header->major == SFDP_JESD216_MAJOR && - bfpt_header->minor == SFDP_JESD216_MINOR && - nor->params->size == SZ_2M && - nor->params->erase_map.regions[0].size == SZ_2M) { + if (is_zd25q128c(nor, bfpt_header) || winbond_rv_match(nor)) { nor->params->size = SZ_16M; nor->params->erase_map.regions[0].size = SZ_16M; } @@ -261,7 +267,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q128JV-Q/N */ + /* W25Q128JV-Q/N, W25Q12RV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x18), /* Flavors w/ and w/o SFDP. */ .name = "w25q128", -- cgit v1.2.3 From 7573a4edaae362fc0da3fc8996f1265a9dbace4c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:50 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q25RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 38355d26bfd4..4b6387a5fb43 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -275,7 +275,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q256JV-Q/N */ + /* W25Q256JV-Q/N, W25Q25RV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x19), .name = "w25q256", .size = SZ_32M, -- cgit v1.2.3 From 49c32f5b2c2ec8abfc97b8c0ea0c645568f65f8e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:51 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q51RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 4b6387a5fb43..b7b74908fedc 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -283,7 +283,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25Q512JV-Q/N */ + /* W25Q512JV-Q/N, W25Q51RV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x20), .name = "w25q512jvq", .size = SZ_64M, -- cgit v1.2.3 From 4da2d59f3a6cc217399b7c106c36c2f3655106a7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:52 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q01RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index b7b74908fedc..4a6d4292fdb2 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -291,7 +291,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25Q01JV-Q/N */ + /* W25Q01JV-Q/N, W25Q01RV-Q/N */ .id = SNOR_ID(0xef, 0x40, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, -- cgit v1.2.3 From 8d3298fd1d75011c2fa30e813cd280856ea86a2e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:53 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q02RV-Q/N There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 4a6d4292fdb2..cd20622916b7 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -295,6 +295,11 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x40, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, + }, { + /* W25Q02RV-Q/N */ + .id = SNOR_ID(0xef, 0x40, 0x22), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { .id = SNOR_ID(0xef, 0x50, 0x12), .name = "w25q20bw", @@ -611,6 +616,8 @@ static const struct spi_nor_fixup winbond_fixups[] = { .fixups = &winbond_nor_partname_fixups }, { .id = SNOR_ID(0xef, 0x40, 0x21), .match = winbond_jv_match, .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, 0x70, 0x21), .match = winbond_jv_match, .fixups = &winbond_nor_multi_die_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match, -- cgit v1.2.3 From 66d57b58c918a556d57bfab2d85ac1d33e21e1d9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:54 +0200 Subject: mtd: spi-nor: winbond: Prepare introduction of W25QxxRV-M parts There is an ID collision between the JV and RV families. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting The new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Since they share the IDs but not the names, we must hide the names using a fixup. Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index cd20622916b7..1b06d10d0f6b 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -618,6 +618,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, 0x70), .match = winbond_rv_match, + .fixups = &winbond_nor_partname_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match, .fixups = &winbond_nor_multi_die_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match, -- cgit v1.2.3 From 650ee584cc52124a8ab51070d320d6752ea2f580 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:55 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q32RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 1b06d10d0f6b..6b58be6f3e7b 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -366,7 +366,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q32JV-M */ + /* W25Q32JV-M, W25Q32RV-M */ .id = SNOR_ID(0xef, 0x70, 0x16), .name = "w25q32jv", .size = SZ_4M, -- cgit v1.2.3 From 94460d091584f9032539a5c949e6e71fa812ad15 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:56 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q64RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 6b58be6f3e7b..cff6d3f07ed9 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -373,7 +373,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q64JV-M */ + /* W25Q64JV-M, W25Q64RV-M */ .id = SNOR_ID(0xef, 0x70, 0x17), .name = "w25q64jvm", .size = SZ_8M, -- cgit v1.2.3 From 11484eb5458fd54b59f35c511819af9e4de27cd1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:57 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q12RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). This density wrongly advertizes its size and requires a fixup for that. Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index cff6d3f07ed9..2f2c79710241 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -380,7 +380,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q128JV-M */ + /* W25Q128JV-M, W25Q12RV-M */ .id = SNOR_ID(0xef, 0x70, 0x18), .name = "w25q128jv", .size = SZ_16M, @@ -620,6 +620,7 @@ static const struct spi_nor_fixup winbond_fixups[] = { .fixups = &winbond_nor_multi_die_fixups }, { .id = SNOR_ID(0xef, 0x70), .match = winbond_rv_match, .fixups = &winbond_nor_partname_fixups }, + { .id = SNOR_ID(0xef, 0x70, 0x18), .fixups = &w25q128_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match, .fixups = &winbond_nor_multi_die_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match, -- cgit v1.2.3 From 8eb8c80e59a3cde9fd7e4d730532668cb8ef7b0d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:58 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q25RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 2f2c79710241..1f8f8c91b1a1 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -387,7 +387,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, }, { - /* W25Q256JV-M */ + /* W25Q256JV-M, W25Q25RV-M */ .id = SNOR_ID(0xef, 0x70, 0x19), .name = "w25q256jvm", .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | -- cgit v1.2.3 From 6397f5f8f8754218dd3a059d39eba6b57d04dc3d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:59 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q51RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 1f8f8c91b1a1..91d0e67c93b6 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -393,7 +393,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25Q512JV-M */ + /* W25Q512JV-M, W25Q51RV-M */ .id = SNOR_ID(0xef, 0x70, 0x20), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, -- cgit v1.2.3 From 677a0d6752eda1480eae3e187c5dfe7367f9d752 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:13:00 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q01RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 91d0e67c93b6..96791ee019e0 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -398,7 +398,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25Q01JV-M */ + /* W25Q01JV-M, W25Q01RV-M */ .id = SNOR_ID(0xef, 0x70, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, -- cgit v1.2.3 From 4094a2fd562453cac04e909688af6620fbd3203f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:13:01 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q02RV-M There is an ID collision with the chip of same density from the JV family. Both chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower). As a significant difference, RV chips identify themselves as supporting the new SFDP (rev F) field which forces an alternate write SR2 opcode (0x31). Reviewed-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 96791ee019e0..f6321c1dc7dc 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -403,7 +403,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, }, { - /* W25Q02JV-M */ + /* W25Q02JV-M, W25Q02RV-M */ .id = SNOR_ID(0xef, 0x70, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, -- cgit v1.2.3 From 700bf34058ca7cd792236b1ba5caad3770d66208 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:13:02 +0200 Subject: mtd: spi-nor: debugfs: Expose SR opcodes and QE mask Expose Status Register opcodes based on the flash capabilities discovered while parsing the SFDP QER field. Value '0' indicates an absence of opcode (non supported), we do not print anything in that case. The QE bit is also displayed. Example on a W25Q02JV-M: $ grep opcodes -A10 /sys/kernel/debug/spi-nor/spi0.0/params opcodes read 0xec dummy cycles 6 erase 0xdc program 0x34 SR1 read 0x05 SR2 read 0x35 SR1+SR2 write 0x01 QE SR2 bit 1 8D extension none Tested-by: Takahiro Kuwano Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/debugfs.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 5c738786145f..3f8d586a7622 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -104,6 +104,21 @@ static int spi_nor_params_show(struct seq_file *s, void *data) seq_printf(s, " dummy cycles\t%u\n", nor->read_dummy); seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode); seq_printf(s, " program\t0x%02x\n", nor->program_opcode); + seq_printf(s, " SR1 read\t0x%02x\n", params->opcodes.read_sr1); + if (params->opcodes.read_sr2) + seq_printf(s, " SR2 read\t0x%02x\n", params->opcodes.read_sr2); + if (params->opcodes.write_sr1) + seq_printf(s, " SR1 write\t0x%02x\n", params->opcodes.write_sr1); + if (params->opcodes.write_sr2) + seq_printf(s, " SR2 write\t0x%02x\n", params->opcodes.write_sr2); + if (params->opcodes.write_sr1_and_sr2) + seq_printf(s, " SR1+SR2 write\t0x%02x\n", + params->opcodes.write_sr1_and_sr2); + + if (params->qe_mask[0] || params->qe_mask[1]) + seq_printf(s, " QE\t\tSR%d bit %d\n", + params->qe_mask[0] ? 1 : 2, + ffs(params->qe_mask[0] | params->qe_mask[1]) - 1); switch (params->cmd_ext_type) { case SPI_NOR_EXT_NONE: -- cgit v1.2.3 From 90f242c92d3ab241f0d22e2368b57750b8755667 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 11 Sep 2026 19:22:45 +0200 Subject: mtd: spi-nor: Fix quad-enable for flashes with QER bit in SR1 Some flashes (eg. from Macronix) do set BFPT_DWORD15_QER_SR1_BIT6, which means they do not have an SR2 to read from/write to. The new generic QE helper was supposed to accommodate this situation but in the last version that got merged, parts of that specific handling has been moved to a more contained location, swp.c (which needed most of the extra code), yet the Macronix case has been forgotten about in that generic QE handling helper. Booting with such flashes will always fail probing. Fix the situation by making sure SR2 reads just return 0 if unsupported. This is safe since there is no chip with a write SR2 path but no read SR2 path (which is now enforced in the SFDP parsing step). This way, callers still do not have to care about the internal device capabilities. Calling sr1_and_sr2 read/write helpers is safe in both directions (not risk to get a spurious error). The behavior for SR1-only chips is respected, the complexity in the core kept to its minimum. Reported-by: Jon Hunter Closes: https://lore.kernel.org/linux-mtd/178876719232.3543902.14451625037676421254.b4-ty@b4/T/#m5bc4ba6776436f2870ced0eb5789d229037ad840 Fixes: 63489002d397 ("mtd: spi-nor: Refactor Read Status/Write Status support") Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 15 ++++++++++++--- drivers/mtd/spi-nor/sfdp.c | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index e2b6efafdd8d..4377b73e57fb 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -849,7 +849,8 @@ int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) /** * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2 - * General purpose helper. + * General purpose helper, always safe to call. Will expectedly ignore + * SR2 on certain chips. * * @nor: the spi_nor structure * @sr: pointer to a valid 2-byte array @@ -864,7 +865,12 @@ int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr) if (ret) return ret; - return spi_nor_read_sr2(nor, &sr[1]); + if (nor->params->opcodes.read_sr2) + ret = spi_nor_read_sr2(nor, &sr[1]); + else + sr[1] = 0; + + return ret; } /** @@ -960,7 +966,10 @@ int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr) if (ret) return ret; - if (sr[0] != tmp[0] || sr[1] != tmp[1]) + if (sr[0] != tmp[0]) + return -EIO; + + if (nor->params->opcodes.read_sr2 && sr[1] != tmp[1]) return -EIO; return 0; diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index c21a6953db96..641f17ad51a0 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -656,7 +656,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor, /* opcodes sanity check */ WARN_ON(!params->opcodes.read_sr1 || - (!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2)); + (!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2) || + (!params->opcodes.read_sr2 && params->opcodes.write_sr2)); dword = bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_4B_ADDR_MODE_MASK; if (SFDP_MASK_CHECK(dword, BFPT_DWORD16_4B_ADDR_MODE_BRWR)) -- cgit v1.2.3 From f917d9edf54c3dd578efc231b8fbcd35744f99cd Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Mon, 14 Sep 2026 13:50:36 +0900 Subject: mtd: spi-nor: spansion: Remove s25fs256s0 The Spansion ID table lists s25fs256s0 and s25fs256s1 as variants distinguished by id[4] (0x00 for 256 KB sectors and 0x01 for 64 KB sectors), following an incorrect statement in the datasheet. The sector size of s25fs256s is configurable between 256 KB and 64 KB. The factory default is 64 KB, and id[4] is always 0x01 regardless of the configured sector size. Remove the invalid s25fs256s0 entry from the ID table. Fixes: 075fd6dff24a ("mtd: spi-nor: spansion: Differentiate between s25fl256s and s25fs256s") Signed-off-by: Takahiro Kuwano Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/spansion.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 6938fe26969c..6a62ca6f6451 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -873,13 +873,6 @@ static const struct flash_info spansion_nor_parts[] = { .sector_size = SZ_256K, .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .mfr_flags = USE_CLSR, - }, { - .id = SNOR_ID(0x01, 0x02, 0x19, 0x4d, 0x00, 0x81), - .name = "s25fs256s0", - .size = SZ_32M, - .sector_size = SZ_256K, - .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, - .mfr_flags = USE_CLSR, }, { .id = SNOR_ID(0x01, 0x02, 0x19, 0x4d, 0x01, 0x80), .name = "s25fl256s1", -- cgit v1.2.3 From a96edc3f0b4f036a5b6ebc9b1eca4273ef5ce2d0 Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Mon, 14 Sep 2026 13:50:37 +0900 Subject: mtd: spi-nor: spansion: Assign .fixups to s25fs256s1 s25fs256s1 has incorrect SFDP entries, like s25fs512s and s25fs128s1. Assign the common s25fs_s_nor_fixups to s25fs256s1 to fix its page size and sector map. Signed-off-by: Takahiro Kuwano Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/spansion.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 6a62ca6f6451..bc50881d5648 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -1148,6 +1148,7 @@ static const struct spi_nor_fixups spansion_nor_fixups = { static const struct spi_nor_fixup spansion_fixups[] = { { .fixups = &spansion_nor_fixups }, { .id = SNOR_ID(0x01, 0x02, 0x20, 0x4d, 0x00, 0x81), .fixups = &s25fs_s_nor_fixups }, + { .id = SNOR_ID(0x01, 0x02, 0x19, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups }, { .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups }, { .id = SNOR_ID(0x01, 0x60, 0x17), .fixup_flags = SPI_NOR_4B_OPCODES }, { .id = SNOR_ID(0x01, 0x60, 0x18), .fixup_flags = SPI_NOR_4B_OPCODES }, -- cgit v1.2.3 From 935457a1ddc90921c3a3444f8bb7aaa97d2af55c Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Thu, 17 Sep 2026 14:02:15 +0900 Subject: mtd: spi-nor: sfdp: Increase SFDP data size limit to 16 KiB The size of SFDP data read and cached is limited to avoid allocating too much memory. The current limit is PAGE_SIZE, but the Spansion S25FS256S has parameter tables at offsets beyond 4 KiB. Increase the limit to 16 KiB to support such devices by introducing the SFDP_MAX_SIZE macro. Suggested-by: Miquel Raynal Suggested-by: Michael Walle Signed-off-by: Takahiro Kuwano Acked-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 641f17ad51a0..5d5bb6e587bf 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -115,6 +115,12 @@ struct sfdp_bfpt_erase { #define SFDP_4BAIT_DWORD_MAX 2 +/* + * Limit the total size of SFDP to a reasonable value to avoid allocating too + * much memory just of because the flash returned some insane values. + */ +#define SFDP_MAX_SIZE SZ_16K + struct sfdp_4bait { /* The hardware capability. */ u32 hwcaps; @@ -1594,14 +1600,10 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) SFDP_PARAM_HEADER_PARAM_LEN(param_header)); } - /* - * Limit the total size to a reasonable value to avoid allocating too - * much memory just of because the flash returned some insane values. - */ - if (sfdp_size > PAGE_SIZE) { + if (sfdp_size > SFDP_MAX_SIZE) { dev_dbg(dev, "SFDP data (%zu) too big, truncating\n", sfdp_size); - sfdp_size = PAGE_SIZE; + sfdp_size = SFDP_MAX_SIZE; } sfdp = devm_kzalloc(dev, sizeof(*sfdp), GFP_KERNEL); -- cgit v1.2.3 From b1c987abe26f6b8614888c93dfb088193eebcdcc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:32 +0200 Subject: mtd: spi-nor: winbond: W25Q32xW-Q/N: Enhance identification of the chips The EF 60 16 ID has been reused for: - DW-P/G (no SFDP support, but CMP and quad capable) - FW-G/Q (SFDP, CMP and quad capable) - JW-Q/N (SFDP, CMP and quad capable) Add a comment to flag these chips. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index f6321c1dc7dc..55c55e41ec92 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -322,6 +322,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q32DW-P/G, W25Q32FW-G/Q, W25Q32JW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x16), .name = "w25q32dw", .size = SZ_4M, -- cgit v1.2.3 From 96601905963b030a82b9ba4b0df98d8681c6a002 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:33 +0200 Subject: mtd: spi-nor: winbond: W25Q64xW-Q/N: Enhance identification of the chips The EF 60 17 ID has been reused for: - DW-P/G (no SFDP support, but CMP and quad capable) - FW-G/Q (SFDP, CMP and quad capable) - JW-Q/N (SFDP, CMP and quad capable) Add a comment to flag these chips. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 55c55e41ec92..5ce29987a13c 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -330,6 +330,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { + /* W25Q64DW-P/G, W25Q64FW-G/Q, W25Q64JW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x17), .name = "w25q64dw", .size = SZ_8M, -- cgit v1.2.3 From a0d9dedd3423efd069bad9f181cf69c7dda76464 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:34 +0200 Subject: mtd: spi-nor: winbond: W25Q32xW-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing capabilities. This feature is identical across the various datasheets exposing the same ID, however I could only test it on the JW-Q variant. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 5ce29987a13c..fe541c6fd939 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -326,7 +326,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x16), .name = "w25q32dw", .size = SZ_4M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { -- cgit v1.2.3 From a13c9bf62caa62a4cb96d7b85394346cabb73496 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:35 +0200 Subject: mtd: spi-nor: winbond: W25Q64xW-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing capabilities. This feature is identical across the various datasheets exposing the same ID, however I could only test it on the JW-Q variant. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index fe541c6fd939..7097948e7dba 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -334,7 +334,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x17), .name = "w25q64dw", .size = SZ_8M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q128FW-G/Q, W25Q128JW-Q/N */ -- cgit v1.2.3 From acaacdb1e75d7ebca7ffb1fa97f149b08ea0fd3e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:36 +0200 Subject: mtd: spi-nor: winbond: W25Q128JW-Q/N: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing capabilities. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 7097948e7dba..4201bda91dcd 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -341,7 +341,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x18), .name = "w25q128fw", .size = SZ_16M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q256JW-Q/N */ -- cgit v1.2.3 From f81793c87530d45e96c5a012fd35d87457328cd1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:37 +0200 Subject: mtd: spi-nor: winbond: W25Q32JW-M: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing capabilities. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 4201bda91dcd..1177e00513d7 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -419,7 +419,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x16), .name = "w25q32jwm", .size = SZ_4M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { -- cgit v1.2.3 From 6af98fcf79a5d86822d3863802ad063a7ad6ca7d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:38 +0200 Subject: mtd: spi-nor: winbond: W25Q64JW-M: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing capabilities. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 1177e00513d7..d0da8f29fe5f 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -427,7 +427,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x17), .name = "w25q64jwm", .size = SZ_8M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q128JW-M */ -- cgit v1.2.3 From 9780e26196039d7a67c915409e22c87db6b721d3 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:39 +0200 Subject: mtd: spi-nor: winbond: W25Q128JW-M: Fill locking information The SFDP table does not advertize the locking capabilities of the device, flag the missing capabilities. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index d0da8f29fe5f..4ffc7a39ac6d 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -434,7 +434,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x18), .name = "w25q128jwm", .size = SZ_16M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q256JW-M */ -- cgit v1.2.3 From f5e252b178066ecf4efe5abf2b0438242199a1ea Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:40 +0200 Subject: mtd: spi-nor: winbond: W25Q256JW-Q/N/M: Fill locking information I do not have any W25Q256JW-Q/N/M in hand, but since all the other chips from this family have the same locking pattern, we can reasonably expect that these chips follow the exact same rules. Add the missing CMP flags. Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 4ffc7a39ac6d..62f69e868d7d 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -348,7 +348,8 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x19), .name = "w25q256jw", .size = SZ_32M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q512NW-Q/N */ @@ -441,7 +442,8 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x19), .name = "w25q256jwm", .size = SZ_32M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | + SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q512NW-M */ -- cgit v1.2.3 From efb7901c6b4d039d4a9a995c0710517b7217cbf0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:41 +0200 Subject: mtd: spi-nor: winbond: W25Q32JW-Q/N: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn a few % write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 393 KiB/s page write speed is 391 KiB/s 2 page write speed is 391 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 402 KiB/s page write speed is 400 KiB/s 2 page write speed is 400 KiB/s Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 62f69e868d7d..022fcfd05da2 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -326,7 +326,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x16), .name = "w25q32dw", .size = SZ_4M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { -- cgit v1.2.3 From 016d9512933789a5e23608cf0839288f894b73a1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:42 +0200 Subject: mtd: spi-nor: winbond: W25Q64JW-Q/N: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn a few % write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 707 KiB/s page write speed is 699 KiB/s 2 page write speed is 703 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 735 KiB/s page write speed is 726 KiB/s 2 page write speed is 730 KiB/s Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 022fcfd05da2..7e79a415db4b 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -334,7 +334,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x17), .name = "w25q64dw", .size = SZ_8M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q128FW-G/Q, W25Q128JW-Q/N */ -- cgit v1.2.3 From d83b5c1df429b5df707046fc6562c60432445b32 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:43 +0200 Subject: mtd: spi-nor: winbond: W25Q128JW-Q/N: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn a few % write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 378 KiB/s page write speed is 375 KiB/s 2 page write speed is 375 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 386 KiB/s page write speed is 382 KiB/s 2 page write speed is 384 KiB/s Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 7e79a415db4b..a4ac11785d25 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -341,7 +341,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x18), .name = "w25q128fw", .size = SZ_16M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q256JW-Q/N */ -- cgit v1.2.3 From afdf4a8c316bba78d8e72a2e113c63427504ca85 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:44 +0200 Subject: mtd: spi-nor: winbond: W25Q32JW-M: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn a few % of write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 416 KiB/s page write speed is 413 KiB/s 2 page write speed is 415 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 424 KiB/s page write speed is 421 KiB/s 2 page write speed is 423 KiB/s Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index a4ac11785d25..98a1e063e7fe 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -420,7 +420,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x16), .name = "w25q32jwm", .size = SZ_4M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { -- cgit v1.2.3 From 34d7f1f5f1aab0c7b08599f0b77611d79b1377a9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:45 +0200 Subject: mtd: spi-nor: winbond: W25Q64JW-M: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn a few % of write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 707 KiB/s page write speed is 699 KiB/s 2 page write speed is 703 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 739 KiB/s page write speed is 730 KiB/s 2 page write speed is 733 KiB/s Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 98a1e063e7fe..945363fd52a2 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -428,7 +428,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x17), .name = "w25q64jwm", .size = SZ_8M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q128JW-M */ -- cgit v1.2.3 From 3520931436ee2ac2720f097c856aea01541ded60 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:46 +0200 Subject: mtd: spi-nor: winbond: W25Q128JW-M: Add quad page program capability The benefit is not massive @25MHz, yet it is a supported feature of the chip which is already handled by a flag, so let's enable it and earn a few % write throughput. Before: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 379 KiB/s page write speed is 376 KiB/s 2 page write speed is 378 KiB/s After: $ flash_speed /dev/mtd0 -dc10 eraseblock write speed is 387 KiB/s page write speed is 385 KiB/s 2 page write speed is 386 KiB/s Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 945363fd52a2..fbad9e408d7a 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -435,7 +435,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x18), .name = "w25q128jwm", .size = SZ_16M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, + .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { /* W25Q256JW-M */ -- cgit v1.2.3 From a95136c1c4756885e4d333c7968b716f9008ee99 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:47 +0200 Subject: mtd: spi-nor: winbond: Prepare introduction of W25QxxPW-Q/N parts There is an ID collision between chips of same density from the JW family (which it self conflicted with yet another family) with new PW parts. Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability for the higher devices (> 32Mb). Chips with ECC capability protect 16 bytes chunks are against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index fbad9e408d7a..a2dea77dfc2c 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -71,6 +71,41 @@ static bool winbond_rv_match(const struct spi_nor *nor) return nor->sfdp && is_w25qxxrv(nor); } +static bool is_w25qxxpw(const struct spi_nor *nor) +{ + struct sfdp_header *sfdp_h = spi_nor_sfdp_get_header(nor); + + /* + * W25QxxPW chips re-use the same ID as the W25QxxJW/NW family. + * + * Chips are very similar, W25QxxPW brings mostly performance and power + * consumption improvements. One key difference in behaviour is the + * automatic 16-byte based error correction. + * + * They can be distinguished based on their SFDP minor revision: + * W25QxxJW: JESD216B, minor revision == 06h + * W25Q51/01/02NW: JESD216B, minor revision == 06h + * W25QxxPW: JESD216F, minor revision >= 0Ah + */ + return sfdp_h->minor >= SFDP_JESD216F_MINOR; +} + +static bool winbond_pw_match(const struct spi_nor *nor) +{ + return nor->sfdp && is_w25qxxpw(nor); +} + +static bool winbond_pw_with_ecc_match(const struct spi_nor *nor) +{ + const struct spi_nor_id *id = nor->info->id; + + if (!winbond_pw_match(nor)) + return false; + + /* W25QxxPW densities <= 32 Mbit (id[2] <= 0x16) do not have built-in ECC support */ + return id->len == 3 && id->bytes[2] >= 0x17; +} + static int w25q128_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, @@ -201,6 +236,22 @@ static const struct spi_nor_fixups winbond_nor_partname_fixups = { .post_sfdp = winbond_nor_partname_post_sfdp_fixups, }; +static int winbond_nor_ecc_configuration_post_sfdp_fixups(struct spi_nor *nor) +{ + /* + * PW chips feature automatic error correction. Non 16-byte aligned + * writes work, but disable error correction on the region until next erase. + */ + nor->params->writesize = 16; + nor->params->flags |= SNOR_F_ECC; + + return 0; +} + +static const struct spi_nor_fixups winbond_nor_ecc_configuration_fixups = { + .post_sfdp = winbond_nor_ecc_configuration_post_sfdp_fixups, +}; + static const struct flash_info winbond_nor_parts[] = { { .id = SNOR_ID(0xef, 0x30, 0x10), @@ -622,6 +673,10 @@ 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, 0x60), .match = winbond_pw_with_ecc_match, + .fixups = &winbond_nor_ecc_configuration_fixups }, + { .id = SNOR_ID(0xef, 0x60), .match = winbond_pw_match, + .fixups = &winbond_nor_partname_fixups }, { .id = SNOR_ID(0xef, 0x70), .match = winbond_rv_match, .fixups = &winbond_nor_partname_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x18), .fixups = &w25q128_fixups }, -- cgit v1.2.3 From 93a72776acb954814940af8b915e609e1b18893d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:48 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q33PW-Q/N There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. Unlike the other (bigger) densities from the PW family, this part has no ECC support. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index a2dea77dfc2c..6934f3e10ec3 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -373,7 +373,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q32DW-P/G, W25Q32FW-G/Q, W25Q32JW-Q/N */ + /* W25Q32DW-P/G, W25Q32FW-G/Q, W25Q32JW-Q/N, W25Q33PW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x16), .name = "w25q32dw", .size = SZ_4M, -- cgit v1.2.3 From c0f1355cece2e2bd19d62a35ba1b66bd09e89bde Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:49 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q64PW-Q/N There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. 16 bytes chunks are protected against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 6934f3e10ec3..d65e2fbadd03 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -381,7 +381,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { - /* W25Q64DW-P/G, W25Q64FW-G/Q, W25Q64JW-Q/N */ + /* W25Q64DW-P/G, W25Q64FW-G/Q, W25Q64JW-Q/N, W25Q64PW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x17), .name = "w25q64dw", .size = SZ_8M, -- cgit v1.2.3 From b8a959f55f9f1806865aa38304289d2abaceacf4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:50 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q12PW-Q/N There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. 16 bytes chunks are protected against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index d65e2fbadd03..75cca5ab1d8c 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -388,7 +388,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q128FW-G/Q, W25Q128JW-Q/N */ + /* W25Q128FW-G/Q, W25Q128JW-Q/N, W25Q12PW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x18), .name = "w25q128fw", .size = SZ_16M, -- cgit v1.2.3 From 136588c583adc2c73934bb0e89877fd1a8b91305 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:51 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q25PW-Q/N There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. 16 bytes chunks are protected against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 75cca5ab1d8c..4c9cbe0240d8 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -395,7 +395,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q256JW-Q/N */ + /* W25Q256JW-Q/N, W25Q25PW-Q/N */ .id = SNOR_ID(0xef, 0x60, 0x19), .name = "w25q256jw", .size = SZ_32M, -- cgit v1.2.3 From b60627be8faf82b2c64d86c83c27c730423d0524 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:52 +0200 Subject: mtd: spi-nor: winbond: Prepare introduction of W25QxxPW-M parts There is an ID collision between chips of same density from the JW family (which it self conflicted with yet another family) with new PW parts. Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability for the higher devices (> 32Mb). Chips with ECC capability protect 16 bytes chunks are against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 4c9cbe0240d8..c1792b12db63 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -684,6 +684,10 @@ static const struct spi_nor_fixup winbond_fixups[] = { .fixups = &winbond_nor_multi_die_fixups }, { .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match, .fixups = &winbond_nor_multi_die_fixups }, + { .id = SNOR_ID(0xef, 0x80), .match = winbond_pw_with_ecc_match, + .fixups = &winbond_nor_ecc_configuration_fixups }, + { .id = SNOR_ID(0xef, 0x80), .match = winbond_pw_match, + .fixups = &winbond_nor_partname_fixups }, }; const struct spi_nor_manufacturer spi_nor_winbond = { -- cgit v1.2.3 From 03613e40f15d704013f90b2405c4a3bc6b751207 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:53 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q33PW-M There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. Unlike the other (bigger) densities from the PW family, this part has no ECC support. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index c1792b12db63..c56bb01c197b 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -467,7 +467,7 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q32JW-M */ + /* W25Q32JW-M, W25Q33PW-M */ .id = SNOR_ID(0xef, 0x80, 0x16), .name = "w25q32jwm", .size = SZ_4M, -- cgit v1.2.3 From d96b00b230d0512844d0e9c9bc391cdc30ab653b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:54 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q64PW-M There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. 16 bytes chunks are protected against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index c56bb01c197b..6bb0f2bda14c 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -475,7 +475,7 @@ static const struct flash_info winbond_nor_parts[] = { .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), }, { - /* W25Q64JW-M */ + /* W25Q64JW-M, W25Q64PW-M */ .id = SNOR_ID(0xef, 0x80, 0x17), .name = "w25q64jwm", .size = SZ_8M, -- cgit v1.2.3 From 8df89f0fe8e8ecc345c5a9e3ce49e41139eb4a0c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:55 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q12PW-M There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. 16 bytes chunks are protected against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 6bb0f2bda14c..86e043cb20e4 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -482,7 +482,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q128JW-M */ + /* W25Q128JW-M, W25Q12PW-M */ .id = SNOR_ID(0xef, 0x80, 0x18), .name = "w25q128jwm", .size = SZ_16M, -- cgit v1.2.3 From ceedf3193cac49ddb488518616587484093d66e4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:56 +0200 Subject: mtd: spi-nor: winbond: Add support for W25Q25PW-M There is an ID collision with chips of same density from the JW family (which it self conflicted with yet another family). Chips are very similar in practice, it is mostly a matter of electrical differences (mostly power consumption being lower) as well as the addition of inline ECC capability. 16 bytes chunks are protected against single errors. In case a non-aligned write happens, ECC is locally disabled until the next erase. Another significant difference is that PW chips identify themselves as supporting the new SFDP (rev F) QER field which forces an alternate write SR2 opcode (0x31). Signed-off-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 86e043cb20e4..51f6bcd3f8e5 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -489,7 +489,7 @@ static const struct flash_info winbond_nor_parts[] = { .flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { - /* W25Q256JW-M */ + /* W25Q256JW-M, W25Q25PW-M */ .id = SNOR_ID(0xef, 0x80, 0x19), .name = "w25q256jwm", .size = SZ_32M, -- cgit v1.2.3 From 68d7115d77a873c28d8c7d2f239c3d9ca83bf39f Mon Sep 17 00:00:00 2001 From: Nuno Sá Date: Mon, 14 Sep 2026 14:42:01 +0100 Subject: mtd: spi-nor: sfdp: get the 1-1-8 and 1-8-8 page programs from 4BAIT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit af2792abd455 ("mtd: spi-nor: sfdp: get the 1-1-8 and 1-8-8 protocol from SFDP") taught the core to discover octal read support from BFPT, but the 4-Byte Address Instruction Table parser was never extended accordingly. Parse the 1-1-8 and 1-8-8 page program bits of 4BAIT DWORD1 and set the corresponding 4-byte opcodes, so that octal writes stay available once the flash switches to the 4-byte address instruction set. This is in preparation of adding support for the ISSI IS25WX01G, a 1 Gbit octal flash which is beyond the 3-byte address range and advertises 4-byte 1-1-8 and 1-8-8 page program instructions. Signed-off-by: Nuno Sá Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 5d5bb6e587bf..beb0f5c66212 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1088,6 +1088,8 @@ static int spi_nor_parse_4bait(struct spi_nor *nor, { SNOR_HWCAPS_PP, BIT(6) }, { SNOR_HWCAPS_PP_1_1_4, BIT(7) }, { SNOR_HWCAPS_PP_1_4_4, BIT(8) }, + { SNOR_HWCAPS_PP_1_1_8, BIT(23) }, + { SNOR_HWCAPS_PP_1_8_8, BIT(24) }, }; static const struct sfdp_4bait erases[SNOR_ERASE_TYPE_MAX] = { { 0u /* not used */, BIT(9) }, @@ -1214,6 +1216,14 @@ static int spi_nor_parse_4bait(struct spi_nor *nor, spi_nor_set_pp_settings(¶ms_pp[SNOR_CMD_PP_1_4_4], SPINOR_OP_PP_1_4_4_4B, SNOR_PROTO_1_4_4); + if (pp_hwcaps & SNOR_HWCAPS_PP_1_1_8) + spi_nor_set_pp_settings(¶ms_pp[SNOR_CMD_PP_1_1_8], + SPINOR_OP_PP_1_1_8_4B, + SNOR_PROTO_1_1_8); + if (pp_hwcaps & SNOR_HWCAPS_PP_1_8_8) + spi_nor_set_pp_settings(¶ms_pp[SNOR_CMD_PP_1_8_8], + SPINOR_OP_PP_1_8_8_4B, + SNOR_PROTO_1_8_8); for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { if (erase_mask & BIT(i)) -- cgit v1.2.3 From 7dc9a4e7b9fcee871d021765c90943c4bab91474 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 15 Oct 2026 10:42:14 +0200 Subject: mtd: spi-nor: sfdp: fix missing kernel-doc Commit 69d9d7dabbeb ("mtd: spi-nor: move spi_nor_post_bfpt_fixups() into sfdp") added the kernel-doc but missed one description for bfpt. Fix it. While at it, also add documentation for the return value. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202610040802.yD5XKM5i-lkp@intel.com/ Fixes: 69d9d7dabbeb ("mtd: spi-nor: move spi_nor_post_bfpt_fixups() into sfdp") Reviewed-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index beb0f5c66212..818c7d4d69d4 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -403,9 +403,12 @@ static void spi_nor_regions_sort_erase_types(struct spi_nor_erase_map *map) * @nor: pointer to a 'struct spi_nor' * @bfpt_header: pointer to the 'struct sfdp_parameter_header' describing * the Basic Flash Parameter Table length and version + * @bfpt: pointer to the 'struct sfdp_bfpt' to be fixed up * * Used to tweak various flash parameters when information provided by the SFDP * tables are wrong. + * + * Return: 0 on success, -errno otherwise. */ static int spi_nor_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, -- cgit v1.2.3 From d7040c620d8b22f839af7750860b96e39cc58b7c Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 15 Oct 2026 13:31:52 +0200 Subject: mtd: spi-nor: sfdp: reduce stack usage in spi_nor_parse_sfdp() Two large spi_nor_flash_parameter structures on a function stack are really too much, and this can exceed an otherwise reasonable frame limit: drivers/mtd/spi-nor/sfdp.c: In function 'spi_nor_parse_sfdp': drivers/mtd/spi-nor/sfdp.c:1717:1: error: the frame size of 1600 bytes is larger than 1536 bytes [-Werror=frame-larger-than=] Allocate both rollback copies on the heap and use structure copies for type safety. Suggested-by: Arnd Bergmann Suggested-by: David Laight Fixes: d20029474a76 ("mtd: spi-nor: push the rollback mechanism into the sfdp module") Reviewed-by: Miquel Raynal Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/sfdp.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 818c7d4d69d4..4a3f82e97cef 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -1540,7 +1540,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) { const struct sfdp_parameter_header *param_header, *bfpt_header; struct sfdp_parameter_header *param_headers = NULL; - struct spi_nor_flash_parameter params, params2; + struct spi_nor_flash_parameter *save_params; struct sfdp_header header; struct device *dev = nor->dev; struct sfdp *sfdp; @@ -1552,17 +1552,22 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) * Get a backup of all the parameter to roll back to in case of an * error. */ - memcpy(¶ms, nor->params, sizeof(params)); + save_params = kmalloc_objs(*save_params, 2); + if (!save_params) + return -ENOMEM; + save_params[0] = *nor->params; /* Get the SFDP header. */ err = spi_nor_read_sfdp_dma_unsafe(nor, 0, sizeof(header), &header); if (err < 0) - return err; + goto free_save_params; /* Check the SFDP header version. */ if (le32_to_cpu(header.signature) != SFDP_SIGNATURE || - header.major != SFDP_JESD216_MAJOR) - return -EINVAL; + header.major != SFDP_JESD216_MAJOR) { + err = -EINVAL; + goto free_save_params; + } /* * Verify that the first and only mandatory parameter header is a @@ -1570,8 +1575,10 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) */ bfpt_header = &header.bfpt_header; if (SFDP_PARAM_HEADER_ID(bfpt_header) != SFDP_BFPT_ID || - bfpt_header->major != SFDP_JESD216_MAJOR) - return -EINVAL; + bfpt_header->major != SFDP_JESD216_MAJOR) { + err = -EINVAL; + goto free_save_params; + } sfdp_size = SFDP_PARAM_HEADER_PTP(bfpt_header) + SFDP_PARAM_HEADER_PARAM_LEN(bfpt_header); @@ -1591,8 +1598,10 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) psize = header.nph * sizeof(*param_headers); param_headers = kmalloc(psize, GFP_KERNEL); - if (!param_headers) - return -ENOMEM; + if (!param_headers) { + err = -ENOMEM; + goto free_save_params; + } err = spi_nor_read_sfdp(nor, sizeof(header), psize, param_headers); @@ -1668,7 +1677,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) /* Parse optional parameter tables. */ for (i = 0; i < header.nph; i++) { - memcpy(¶ms2, nor->params, sizeof(params2)); + save_params[1] = *nor->params; param_header = ¶m_headers[i]; switch (SFDP_PARAM_HEADER_ID(param_header)) { @@ -1706,7 +1715,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) * spi_nor_flash_parameter data. */ err = 0; - memcpy(nor->params, ¶ms2, sizeof(*nor->params)); + *nor->params = save_params[1]; } } @@ -1715,6 +1724,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) goto clear_sfdp_ptr; kfree(param_headers); + kfree(save_params); return 0; @@ -1727,7 +1737,9 @@ free_sfdp: free_param_headers: kfree(param_headers); if (err) - memcpy(nor->params, ¶ms, sizeof(*nor->params)); + *nor->params = save_params[0]; +free_save_params: + kfree(save_params); return err; } -- cgit v1.2.3