From 85b6110155cab6b67c9072c19f21dcb620c2819d Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Fri, 10 Apr 2026 17:10:39 +0800 Subject: [PATCH 001/155] 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, }, { From 1dde2faa82fd69a4da5ab7a4635b51773b566d4d Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Fri, 5 Jun 2026 16:48:36 +0800 Subject: [PATCH 002/155] 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), From 01739ccea21b69818dc071096a249e6f84a6b13d Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Fri, 5 Jun 2026 16:48:37 +0800 Subject: [PATCH 003/155] 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 */ From 0132eaac40518f6ebb42131e5005efc375473e8e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:39 +0200 Subject: [PATCH 004/155] 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), From a4cd8da7f2bda7562a7e156ee9c9e9b3ba98208e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:40 +0200 Subject: [PATCH 005/155] 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; - } } /** From e37ee8734881a7f92dbbeecf8cc129dd8a6a76b8 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:41 +0200 Subject: [PATCH 006/155] 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]; From 0ede46b4f5b38cb43225927abbf088dd568db54e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:42 +0200 Subject: [PATCH 007/155] 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]; From 69d9d7dabbebd91cd8aba4e23caabe9b34d2722c Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:43 +0200 Subject: [PATCH 008/155] 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' From d395d4b8e76b1dfdb2f46b29c62e7f1f4850c9b2 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:44 +0200 Subject: [PATCH 009/155] 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; From d20029474a76a06a2a92427453c2326cc3e98dfa Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:45 +0200 Subject: [PATCH 010/155] 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; } From 90a8ed3c63b905dcd375585da6f4851d88cc343d Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 13 Jul 2026 11:11:46 +0200 Subject: [PATCH 011/155] 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) From b9f34a95a81021fa1f3c9cb8cfd458e66824cad6 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Mon, 20 Jul 2026 23:11:03 +0900 Subject: [PATCH 012/155] 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 @@ static int spi_nor_parse_4bait(struct spi_nor *nor, #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) From 1413f38f37194673502e61cd3749e091c031370b Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Mon, 20 Jul 2026 23:11:04 +0900 Subject: [PATCH 013/155] 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 @@ static int spi_nor_parse_profile1(struct spi_nor *nor, } #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) From c63c890a12c0e5959190a486f1b8409f90d35f6c Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Fri, 31 Jul 2026 16:57:19 +0900 Subject: [PATCH 014/155] 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"); From 3c2464c70284eb5443a18ea7ee2f815a06d91a5b Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 11 Aug 2026 08:44:44 +0200 Subject: [PATCH 015/155] 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, }, }; From f594a33f9721f08facd98d0a31adc14ff6da9d70 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 11 Aug 2026 17:56:23 +0200 Subject: [PATCH 016/155] 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; } From 90936c3c790ec22b02c6d8611a9df0aecb8139bb Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 18:22:03 +0530 Subject: [PATCH 017/155] mtd: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Miquel Raynal --- drivers/mtd/devices/docg3.c | 4 ++-- drivers/mtd/nand/onenand/onenand_base.c | 2 +- drivers/mtd/nand/raw/fsmc_nand.c | 2 +- drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 2 +- drivers/mtd/nand/raw/mtk_nand.c | 2 +- drivers/mtd/nand/raw/nand_base.c | 2 +- drivers/mtd/nand/raw/nand_legacy.c | 4 ++-- drivers/mtd/spi-nor/otp.c | 2 +- include/linux/mtd/bbm.h | 2 +- include/linux/mtd/rawnand.h | 2 +- include/linux/mtd/sh_flctl.h | 4 ++-- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c index 603fd0efc2ea..84566997d665 100644 --- a/drivers/mtd/devices/docg3.c +++ b/drivers/mtd/devices/docg3.c @@ -1884,7 +1884,7 @@ static void doc_release_device(struct mtd_info *mtd) /** * docg3_resume - Awakens docg3 floor - * @pdev: platfrom device + * @pdev: platform device * * Returns 0 (always successful) */ @@ -1963,7 +1963,7 @@ static int docg3_suspend(struct platform_device *pdev, pm_message_t state) * @pdev: platform device * * Probes for a G3 chip at the specified IO space in the platform data - * ressources. The floor 0 must be available. + * resources. The floor 0 must be available. * * Returns 0 on success, -ENOMEM, -ENXIO on error */ diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c index d08aeac86f9f..bca5a3bebd41 100644 --- a/drivers/mtd/nand/onenand/onenand_base.c +++ b/drivers/mtd/nand/onenand/onenand_base.c @@ -3279,7 +3279,7 @@ static void onenand_check_features(struct mtd_info *mtd) * * Now it has as following * KFM4G16Q4M has NOP 4 with version ID 0x0131 - * KFM4G16Q5M has NOP 1 with versoin ID 0x013e + * KFM4G16Q5M has NOP 1 with version ID 0x013e */ if ((this->version_id & 0xf) == 0xe) this->options |= ONENAND_HAS_NOP_1; diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c index b13b2b0c3f30..08ef4425559c 100644 --- a/drivers/mtd/nand/raw/fsmc_nand.c +++ b/drivers/mtd/nand/raw/fsmc_nand.c @@ -461,7 +461,7 @@ static int fsmc_correct_ecc1(struct nand_chip *chip, chip->ecc.size, sm_order); } -/* Count the number of 0's in buff upto a max of max_bits */ +/* Count the number of 0's in buff up to a max of max_bits */ static int count_written_bits(u8 *buff, int size, int max_bits) { int k, written_bits = 0; diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index 527165ccc839..c61f3983a713 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -2271,7 +2271,7 @@ static int nand_boot_init(struct gpmi_nand_data *this) { nand_boot_set_geometry(this); - /* This is ROM arch-specific initilization before the BBT scanning. */ + /* This is ROM arch-specific initialization before the BBT scanning. */ if (GPMI_IS_MX23(this)) return mx23_boot_init(this); return 0; diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index 21c7e1102746..c439b7814ebb 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -42,7 +42,7 @@ #define CON_BRD BIT(8) /* burst read */ #define CON_BWR BIT(9) /* burst write */ #define CON_SEC_SHIFT (12) -/* Timming control register */ +/* Timing control register */ #define NFI_ACCCON (0x0C) #define NFI_INTR_EN (0x10) #define INTR_AHB_DONE_EN BIT(6) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index a5b278ab9384..a225b10f8145 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -6205,7 +6205,7 @@ static int nand_scan_tail(struct nand_chip *chip) /* * FIXME: some NAND manufacturer drivers expect the first die to be * selected when manufacturer->init() is called. They should be fixed - * to explictly select the relevant die when interacting with the NAND + * to explicitly select the relevant die when interacting with the NAND * chip. */ nand_select_target(chip, 0); diff --git a/drivers/mtd/nand/raw/nand_legacy.c b/drivers/mtd/nand/raw/nand_legacy.c index 97700f80d5b8..94d5c7723c5a 100644 --- a/drivers/mtd/nand/raw/nand_legacy.c +++ b/drivers/mtd/nand/raw/nand_legacy.c @@ -319,7 +319,7 @@ static void nand_command(struct nand_chip *chip, unsigned int command, NAND_CTRL_CLE | NAND_CTRL_CHANGE); chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - /* EZ-NAND can take upto 250ms as per ONFi v4.0 */ + /* EZ-NAND can take up to 250ms as per ONFi v4.0 */ nand_wait_status_ready(chip, 250); return; @@ -459,7 +459,7 @@ static void nand_command_lp(struct nand_chip *chip, unsigned int command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - /* EZ-NAND can take upto 250ms as per ONFi v4.0 */ + /* EZ-NAND can take up to 250ms as per ONFi v4.0 */ nand_wait_status_ready(chip, 250); return; diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 7d0b145d78d8..61c55227e15d 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -25,7 +25,7 @@ * Read a security register by using the SPINOR_OP_RSECR commands. * * In Winbond/GigaDevice datasheets the term "security register" stands for - * an one-time-programmable memory area, consisting of multiple bytes (usually + * a one-time-programmable memory area, consisting of multiple bytes (usually * 256). Thus one "security register" maps to one OTP region. * * This method is used on GigaDevice and Winbond flashes. diff --git a/include/linux/mtd/bbm.h b/include/linux/mtd/bbm.h index d890805f5494..67929fdf2dff 100644 --- a/include/linux/mtd/bbm.h +++ b/include/linux/mtd/bbm.h @@ -75,7 +75,7 @@ struct nand_bbt_descr { * with NAND_BBT_CREATE. */ #define NAND_BBT_CREATE_EMPTY 0x00000400 -/* Write bbt if neccecary */ +/* Write bbt if necessary */ #define NAND_BBT_WRITE 0x00002000 /* Read and write back block contents when writing bbt */ #define NAND_BBT_SAVECONTENT 0x00004000 diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index 5c70e7bd3ed5..43fefff02025 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -1592,7 +1592,7 @@ void nand_wait_ready(struct nand_chip *chip); /* * Free resources held by the NAND device, must be called on error after a - * sucessful nand_scan(). + * successful nand_scan(). */ void nand_cleanup(struct nand_chip *chip); diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h index 78fc2d4218c8..15adf2474fa0 100644 --- a/include/linux/mtd/sh_flctl.h +++ b/include/linux/mtd/sh_flctl.h @@ -60,8 +60,8 @@ * Some hardware uses bits called PULSEx instead of FCKSEL_E and QTSEL_E * to control the clock divider used between the High-Speed Peripheral Clock * and the FLCTL internal clock. If so, use CLK_8_BIT_xxx for connecting 8 bit - * and CLK_16_BIT_xxx for connecting 16 bit bus bandwith NAND chips. For the 16 - * bit version the divider is seperate for the pulse width of high and low + * and CLK_16_BIT_xxx for connecting 16 bit bus bandwidth NAND chips. For the 16 + * bit version the divider is separate for the pulse width of high and low * signals. */ #define PULSE3 (0x1 << 27) From 8696502b0aa96bcbb1f1e78ae117726378823ae8 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Tue, 11 Aug 2026 00:01:59 -0600 Subject: [PATCH 018/155] mtd: rawnand: sunxi: propagate user-data allocation errors sunxi_nfc_maximize_user_data() returns -ENOMEM when its per-step length array cannot be allocated, but the caller ignores that error. The NULL array then makes sunxi_nfc_user_data_sz() report the fixed four-byte fallback, which is not the layout that the variable-length setup calculated. Return the setup error so an allocation failure cannot silently select and validate a different OOB layout. Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length") Signed-off-by: James Hilliard Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 45ccbce91551..108161fe10cb 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -2072,6 +2072,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, int total_user_data_sz = 0; int nsectors; int ecc_mode; + int ret; int i; if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { @@ -2169,9 +2170,12 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, * The rationale for variable data length is to prioritize maximum ECC * strength, and then use the remaining space for user data. */ - if (nfc->caps->reg_user_data_len) - sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes, - nsectors); + if (nfc->caps->reg_user_data_len) { + ret = sunxi_nfc_maximize_user_data(nand, mtd->oobsize, + ecc->bytes, nsectors); + if (ret) + return ret; + } if (total_user_data_sz == 0) for (i = 0; i < nsectors; i++) From bde8fd880d84627072c224729c818306c4b30d58 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Tue, 11 Aug 2026 00:02:00 -0600 Subject: [PATCH 019/155] mtd: rawnand: sunxi: reserve a full user-data word for BBM H6/H616 protected user-data lengths are encoded in four-byte units, but ECC maximization reserves only the two bad block marker bytes. A strength leaving fewer than four bytes therefore selects a zero-byte first user-data section. The OOB layout then subtracts the two marker bytes from that unsigned zero length, and the controller has no protected user-data word in which to store the marker. Reserve one complete user-data word while maximizing ECC, reject configurations which produce no ECC sectors or a shorter first section, and make the OOB iterator bounds-safe. Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length") Signed-off-by: James Hilliard Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 108161fe10cb..01a0d0fa7b62 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1991,7 +1991,7 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, struct nand_chip *nand = mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc = &nand->ecc; struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); - unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section); + unsigned int user_data_sz; /* * The controller does not provide access to OOB bytes @@ -2000,6 +2000,8 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, if (section >= ecc->steps) return -ERANGE; + user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section); + /* * The first 2 bytes are used for BB markers, hence we * only have user_data_sz - 2 bytes available in the first user data @@ -2007,7 +2009,7 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, */ if (section == 0) { oobregion->offset = 2; - oobregion->length = user_data_sz - 2; + oobregion->length = user_data_sz > 2 ? user_data_sz - 2 : 0; return 0; } @@ -2041,6 +2043,9 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize int remaining_bytes = oobsize - (ecc_bytes * nsectors); int i, step; + if (nsectors <= 0) + return -EINVAL; + sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors, GFP_KERNEL); if (!sunxi_nand->user_data_bytes) @@ -2056,6 +2061,8 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize if (sunxi_nand->user_data_bytes[step] == 0) break; } + if (sunxi_nand->user_data_bytes[0] < USER_DATA_SZ) + return -EINVAL; return 0; } @@ -2103,10 +2110,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, bytes -= total_user_data_sz; } else { /* - * remove at least the BBM size before computing the - * max ECC + * User-data lengths are encoded in four-byte units. Reserve + * the first word because it contains the two BBM bytes. */ - bytes -= 2; + bytes -= USER_DATA_SZ; } /* From a8596bb7148136b9e83980900d508d0ef86507ef Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Tue, 11 Aug 2026 00:02:01 -0600 Subject: [PATCH 020/155] mtd: rawnand: sunxi: use a stack buffer for BBM randomization Variable protected user-data lengths replaced the original four-byte stack buffer with a heap allocation before compensating the bad block marker. The allocation is unchecked, and a zero length returns ZERO_SIZE_PTR which sunxi_nfc_randomize_bbm() dereferences. The controller supports at most 32 protected user-data bytes per ECC step, so use a zero-initialized buffer of that bounded size. This removes the allocation failure path and keeps zero-sized invalid layouts from dereferencing ZERO_SIZE_PTR. Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length") Signed-off-by: James Hilliard Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 01a0d0fa7b62..83666dd6cb2a 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -222,6 +222,7 @@ * USER_DATA_LEN registers. */ #define USER_DATA_SZ 4 +#define SUNXI_NFC_MAX_USER_DATA_SZ 32 /** * struct sunxi_nand_chip_sel - stores information related to NAND Chip Select @@ -1003,11 +1004,10 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand, struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); - u8 *user_data = NULL; + u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {}; /* Randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) { - user_data = kmalloc(user_data_sz, GFP_KERNEL); memcpy(user_data, oob, user_data_sz); sunxi_nfc_randomize_bbm(nand, page, user_data); oob = user_data; @@ -1040,7 +1040,6 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand, } } - kfree(user_data); } static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand, From c9e20e213bb051d02711155de0dc4759de2faddf Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 17:37:28 +0700 Subject: [PATCH 021/155] mtd: rawnand: brcmnand: Propagate errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available, in which case the driver falls back to using the ctlrdy IRQ. Other errors should be propagated so that the caller can handle them appropriately. Treat -ENXIO as the only case where the driver falls back to the ctlrdy IRQ, and propagate all other errors returned by platform_get_irq_optional(). Signed-off-by: bui duc phuc Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/brcmnand/brcmnand.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c index 5b9dadd5405e..4a1f5e736a1c 100644 --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c @@ -3272,6 +3272,8 @@ static int brcmnand_edu_setup(struct platform_device *pdev) ctrl->edu_irq = platform_get_irq_optional(pdev, 1); if (ctrl->edu_irq < 0) { + if (ctrl->edu_irq != -ENXIO) + return ctrl->edu_irq; dev_warn(dev, "FLASH EDU enabled, using ctlrdy irq\n"); } else { From ea609469a3e37fc731636c7be6bc3e8983b2070b Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Mon, 17 Aug 2026 05:29:17 +0000 Subject: [PATCH 022/155] mtd: spinand: winbond: add support for W25N04LW MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for Winbond W25N04LW 4Gbit SPI-NAND: > 1.8V 4G-bit > Raw serial SLC NAND flash memory > Dual/Quad SPI with 104MHz > buffer read, continuous read & sequential read Highlights copied from datasheet: – Page size: 4,352 Bytes (4096 + 256 Bytes) – Block size: 64 pages (256K + 16K Bytes) - Built-in 8-Bit ECC for memory array – ECC status bits indicate ECC results – Bad Block Management and LUT access – Software and Hardware Write-Protect – Power Supply Lock-Down and OTP protection – Unique ID and Parameter page – Ten 4KB OTP pages per die – Read Level Setting for Read Retry The 4096+256 byte page layout is as follow: - 4096 bytes ECC protected "main memory array", subdivided in 8 512 bytes sectors - 128 bytes spare array, subdivided in 8 16 bytes sectors as follow: - 2 bytes bad block marker (no ECC protection) - 2 bytes "user data II" (no ECC protection) - 12 bytes "user data I" (ECC protected) - 128 bytes parity array, subdivided in 8 16 bytes sectors: - 13 bytes ECC for main data and user data - 3 unused bytes This commit does not support most advanced features such as continuous read, LUT management and OTP pages, and only provides basic usage. OOB layout was kept compatible with previous models (exposing both user data I and II), dependent on mtd->oobsize. Co-developed-by: Daisuke Mizobuchi Signed-off-by: Daisuke Mizobuchi Signed-off-by: Dominique Martinet Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 35 +++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 9b78c1e6cbc9..a1807fa9ae03 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -314,10 +314,18 @@ static int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section, static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { - if (section > 3) + int num_sections = 4; + int offset = 64; + + if (mtd->oobsize == 256) { + num_sections = 8; + offset = 128; + } + + if (section >= num_sections) return -ERANGE; - region->offset = 64 + (16 * section); + region->offset = offset + (16 * section); region->length = 13; return 0; @@ -326,9 +334,21 @@ static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { - if (section > 3) + int num_sections = 4; + + if (mtd->oobsize == 256) + num_sections = 8; + + if (section >= num_sections) return -ERANGE; + /* + * For at least W25N04KW and W25N04LW, this region is actually + * split in two: + * ECC-protected "User Data I" at + 4 offset, length 12 + * unprotected "User Data II" at + 2 offset, length 2 + * This returns the full region for backwards compatibility. + */ region->offset = (16 * section) + 2; region->length = 14; @@ -768,6 +788,15 @@ static const struct spinand_info winbond_spinand_table[] = { &update_cache_variants), 0, SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), + SPINAND_INFO("W25N04LW", /* 1.8V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), SPINAND_INFO("W35N04JW", /* 1.8V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23), NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1), From 09ec4fe9036ed0a0293518dc11f2480423d1139a Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Sat, 22 Aug 2026 04:59:56 +0000 Subject: [PATCH 023/155] mtd: rawnand: stm32_fmc2: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(). This lets us drop the __maybe_unused annotations from its suspend and resume callbacks, also reduces kernel size in case CONFIG_PM or CONFIG_PM_SLEEP is disabled. Signed-off-by: Triet Hoang Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/stm32_fmc2_nand.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c index c08d6b176372..9f42f8ab3212 100644 --- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c +++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c @@ -2076,7 +2076,7 @@ static void stm32_fmc2_nfc_remove(struct platform_device *pdev) stm32_fmc2_nfc_wp_enable(nand); } -static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev) +static int stm32_fmc2_nfc_suspend(struct device *dev) { struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev); struct stm32_fmc2_nand *nand = &nfc->nand; @@ -2090,7 +2090,7 @@ static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev) return 0; } -static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev) +static int stm32_fmc2_nfc_resume(struct device *dev) { struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev); struct stm32_fmc2_nand *nand = &nfc->nand; @@ -2118,7 +2118,7 @@ static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(stm32_fmc2_nfc_pm_ops, stm32_fmc2_nfc_suspend, +static DEFINE_SIMPLE_DEV_PM_OPS(stm32_fmc2_nfc_pm_ops, stm32_fmc2_nfc_suspend, stm32_fmc2_nfc_resume); static const struct stm32_fmc2_nfc_data stm32_fmc2_nfc_mp1_data = { @@ -2153,7 +2153,7 @@ static struct platform_driver stm32_fmc2_nfc_driver = { .driver = { .name = "stm32_fmc2_nfc", .of_match_table = stm32_fmc2_nfc_match, - .pm = &stm32_fmc2_nfc_pm_ops, + .pm = pm_sleep_ptr(&stm32_fmc2_nfc_pm_ops), }, }; module_platform_driver(stm32_fmc2_nfc_driver); From c469391db15839b2d1b1294eb4a5e5c36ab5ac7a Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 22 Aug 2026 16:03:34 -0700 Subject: [PATCH 024/155] mtd: nand: omap2: Fix kernel-doc parameter warning kernel-doc interprets inline comments in the omap_compare_ecc() parameter list as part of the prototype, causing it to miss ecc_data2 and report its documentation as excess. Remove the inline comments; the parameter descriptions are already provided by the kernel-doc tags. Assisted-by: Codex:GPT-5.6-luna Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/omap2.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index 552f36da79fc..4909e62dc11e 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -666,9 +666,7 @@ static void gen_true_ecc(u8 *ecc_buf) * If there is no error, %0 is returned. If there is an error but it * was corrected, %1 is returned. Otherwise, %-1 is returned. */ -static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ - u8 *ecc_data2, /* read from register */ - u8 *page_data) +static int omap_compare_ecc(u8 *ecc_data1, u8 *ecc_data2, u8 *page_data) { uint i; u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; From 0d66f9895f286d2fab396a71e6efeb95c4a7e5a6 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 24 Aug 2026 13:21:20 -0700 Subject: [PATCH 025/155] mtd: rawnand: ndfc: use devm_platform_ioremap_resource() Replace the open-coded of_iomap()/iounmap() with the managed devm_platform_ioremap_resource() helper. This fixes a mapping leak on driver remove (the old code never called iounmap()) and simplifies the error path by dropping the manual cleanup in ndfc_probe(). There is no overlapping memory-region concern introduced by this change: each supported board DT describes a single ndfc node with a unique 0x2000 register region, and the EBC parent uses dcr-reg rather than a MEM reg resource, so the newly added request_mem_region() cannot conflict with an existing reservation. Distinct chip selects map to distinct 64-bit physical addresses, so CS instances do not overlap. Fixes: a808ad3b0d28 ("[MTD] [NAND] ndfc driver") Cc: stable@vger.kernel.org Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/ndfc.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c index a48274297d3b..d6db04d48c45 100644 --- a/drivers/mtd/nand/raw/ndfc.c +++ b/drivers/mtd/nand/raw/ndfc.c @@ -185,10 +185,15 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc, static int ndfc_probe(struct platform_device *ofdev) { struct ndfc_controller *ndfc; + void __iomem *ndfcbase; const __be32 *reg; u32 ccr; u32 cs; - int err, len = 0; + int len = 0; + + ndfcbase = devm_platform_ioremap_resource(ofdev, 0); + if (IS_ERR(ndfcbase)) + return PTR_ERR(ndfcbase); /* Read the reg property to get the chip select */ reg = of_get_property(ofdev->dev.of_node, "reg", &len); @@ -210,11 +215,7 @@ static int ndfc_probe(struct platform_device *ofdev) ndfc->ofdev = ofdev; dev_set_drvdata(&ofdev->dev, ndfc); - ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0); - if (!ndfc->ndfcbase) { - dev_err(&ofdev->dev, "failed to get memory\n"); - return -EIO; - } + ndfc->ndfcbase = ndfcbase; ccr = NDFC_CCR_BS(ndfc->chip_select); @@ -232,13 +233,7 @@ static int ndfc_probe(struct platform_device *ofdev) iowrite32be(be32_to_cpup(reg), ndfc->ndfcbase + offset); } - err = ndfc_chip_init(ndfc, ofdev->dev.of_node); - if (err) { - iounmap(ndfc->ndfcbase); - return err; - } - - return 0; + return ndfc_chip_init(ndfc, ofdev->dev.of_node); } static void ndfc_remove(struct platform_device *ofdev) From f4f2a54cfef7cd23a54b8427a00c2dae83ffaf2e Mon Sep 17 00:00:00 2001 From: Bill Lee Date: Thu, 27 Aug 2026 15:39:12 -0500 Subject: [PATCH 026/155] mtd: spinand: add support for ISSI SPI NAND flash Add a new manufacturer driver for ISSI SPI NAND devices (manufacturer ID 0x9d). The following devices are supported: - IS37/IS38SMW01G8B: 1Gb, 1.8V - IS37/IS38SMW02G8B: 2Gb, 1.8V - IS37/IS38SML04G8B: 4Gb, 3.3V, 2-die stacked - IS37/IS38SMW04G8B: 4Gb, 1.8V, 2-die stacked Signed-off-by: Bill Lee Signed-off-by: Jeff Kim Signed-off-by: Han Xu Link: https://patch.msgid.link/20260825-spinand_issi-v1-1-d81bdd7c212d@nxp.com Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/Makefile | 2 +- drivers/mtd/nand/spi/core.c | 1 + drivers/mtd/nand/spi/issi.c | 187 ++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 1 + 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/issi.c diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index b5ccb44860df..f6b2a094fafc 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 spinand-objs := core.o otp.o spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o heyangtek.o -spinand-objs += macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o +spinand-objs += issi.o macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 8bf9301f25e7..86a8d759682b 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1360,6 +1360,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = { &foresee_spinand_manufacturer, &gigadevice_spinand_manufacturer, &heyangtek_spinand_manufacturer, + &issi_spinand_manufacturer, ¯onix_spinand_manufacturer, µn_spinand_manufacturer, ¶gon_spinand_manufacturer, diff --git a/drivers/mtd/nand/spi/issi.c b/drivers/mtd/nand/spi/issi.c new file mode 100644 index 000000000000..122b3a6e2f62 --- /dev/null +++ b/drivers/mtd/nand/spi/issi.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 ISSI + * + * Authors: + * Bill Lee + * Jeff Kim + * Co-Author: + * Han Xu + */ + +#include +#include +#include +#include + +#define SPINAND_MFR_ISSI 0x9d + +#define ISSI_STATUS_ECC_MASK GENMASK(6, 4) +#define ISSI_STATUS_ECC_NO_BITFLIPS 0 +#define ISSI_STATUS_ECC_1TO3_BITFLIPS 1 +#define ISSI_STATUS_ECC_UNCOR_ERROR 2 +#define ISSI_STATUS_ECC_4TO6_BITFLIPS 3 +#define ISSI_STATUS_ECC_7TO8_BITFLIPS 5 + +/* + * As per datasheet, die selection is done by the 7th bit of Drive + * Strength Register (Address 0xD0). + */ +#define ISSI_DIE_SELECT_REG 0xD0 +#define ISSI_SELECT_DIE_MASK BIT(7) +#define ISSI_SELECT_DIE(x) ((x) << 7) + +static SPINAND_OP_VARIANTS( + quadio_read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0)); + +static SPINAND_OP_VARIANTS(x4_write_cache_variants, + SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0), + SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(x4_update_cache_variants, + SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), + SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); + +static int issi_8_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int issi_8_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = (mtd->oobsize / 2) - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops issi_8_ooblayout = { + .ecc = issi_8_ooblayout_ecc, + .free = issi_8_ooblayout_free, +}; + +static int issi_select_target(struct spinand_device *spinand, + unsigned int target) +{ + int ret; + u8 regval; + + if (target > 1) + return -EINVAL; + + ret = spinand_read_reg_op(spinand, ISSI_DIE_SELECT_REG, ®val); + if (ret) + return ret; + + regval &= ~ISSI_SELECT_DIE_MASK; + regval |= ISSI_SELECT_DIE(target); + + return spinand_write_reg_op(spinand, ISSI_DIE_SELECT_REG, regval); +} + +static int issi_8_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + switch (FIELD_GET(ISSI_STATUS_ECC_MASK, status)) { + case ISSI_STATUS_ECC_NO_BITFLIPS: + return 0; + + case ISSI_STATUS_ECC_1TO3_BITFLIPS: + return 3; + + case ISSI_STATUS_ECC_4TO6_BITFLIPS: + return 6; + + case ISSI_STATUS_ECC_7TO8_BITFLIPS: + return 8; + + case ISSI_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info issi_spinand_table[] = { + /* IS37/38SMW01G8B 1Gb 1.8V */ + SPINAND_INFO("IS37/38SMW01G8B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), + NAND_MEMORG(1, 2048, 128, 64, 1024, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), + + /* IS37/38SMW02G8B 2Gb 1.8V */ + SPINAND_INFO("IS37/38SMW02G8B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), + + /* IS37/38SML04G8B 4Gb 3.3V */ + SPINAND_INFO("IS37/38SML04G8", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x34), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), + + /* IS37/38SMW04G8B 4Gb 1.8V */ + SPINAND_INFO("IS37/38SMW04G8B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&issi_8_ooblayout, issi_8_ecc_get_status), + SPINAND_SELECT_TARGET(issi_select_target)), +}; + +static int issi_spinand_init(struct spinand_device *spinand) +{ + return 0; +} + +static const struct spinand_manufacturer_ops issi_spinand_manuf_ops = { + .init = issi_spinand_init, +}; + +const struct spinand_manufacturer issi_spinand_manufacturer = { + .id = SPINAND_MFR_ISSI, + .name = "ISSI", + .chips = issi_spinand_table, + .nchips = ARRAY_SIZE(issi_spinand_table), + .ops = &issi_spinand_manuf_ops, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 5f4c00ae72a7..b42407b69db3 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -438,6 +438,7 @@ extern const struct spinand_manufacturer fmsh_spinand_manufacturer; extern const struct spinand_manufacturer foresee_spinand_manufacturer; extern const struct spinand_manufacturer gigadevice_spinand_manufacturer; extern const struct spinand_manufacturer heyangtek_spinand_manufacturer; +extern const struct spinand_manufacturer issi_spinand_manufacturer; extern const struct spinand_manufacturer macronix_spinand_manufacturer; extern const struct spinand_manufacturer micron_spinand_manufacturer; extern const struct spinand_manufacturer paragon_spinand_manufacturer; From e18c514e9c1ff330bec87fd62341251d041e7443 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 3 Sep 2026 14:24:23 +0300 Subject: [PATCH 027/155] mtd: rawnand: lpc32xx_slc: Remove platform data support Since NXP LPC32xx SoC support in the Linux kernel strictly depends on device tree support, the unused platform data support can be removed from the NAND SLC driver. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/lpc32xx_slc.c | 21 ++------------------- include/linux/mtd/lpc32xx_slc.h | 17 ----------------- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 include/linux/mtd/lpc32xx_slc.h diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c index 10c8080207f4..b4d90afa9267 100644 --- a/drivers/mtd/nand/raw/lpc32xx_slc.c +++ b/drivers/mtd/nand/raw/lpc32xx_slc.c @@ -25,7 +25,6 @@ #include #include #include -#include #define LPC32XX_MODNAME "lpc32xx-nand" @@ -213,7 +212,6 @@ struct lpc32xx_nand_cfg_slc { struct lpc32xx_nand_host { struct nand_chip nand_chip; - struct lpc32xx_slc_platform_data *pdata; struct clk *clk; struct gpio_desc *wp_gpio; void __iomem *io_base; @@ -727,24 +725,11 @@ static int lpc32xx_nand_write_page_raw_syndrome(struct nand_chip *chip, static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host) { struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); - dma_cap_mask_t mask; host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx"); if (IS_ERR(host->dma_chan)) { - /* fallback to request using platform data */ - if (!host->pdata || !host->pdata->dma_filter) { - dev_err(mtd->dev.parent, "no DMA platform data\n"); - return -ENOENT; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, "nand-slc"); - - if (!host->dma_chan) { - dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); - return -EBUSY; - } + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return PTR_ERR(host->dma_chan); } return 0; @@ -873,8 +858,6 @@ static int lpc32xx_nand_probe(struct platform_device *pdev) gpiod_set_consumer_name(host->wp_gpio, "NAND WP"); - host->pdata = dev_get_platdata(&pdev->dev); - chip = &host->nand_chip; mtd = nand_to_mtd(chip); nand_set_controller_data(chip, host); diff --git a/include/linux/mtd/lpc32xx_slc.h b/include/linux/mtd/lpc32xx_slc.h deleted file mode 100644 index a044b806566b..000000000000 --- a/include/linux/mtd/lpc32xx_slc.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Platform data for LPC32xx SoC SLC NAND controller - * - * Copyright © 2012 Roland Stigge - */ - -#ifndef __LINUX_MTD_LPC32XX_SLC_H -#define __LINUX_MTD_LPC32XX_SLC_H - -#include - -struct lpc32xx_slc_platform_data { - dma_filter_fn dma_filter; -}; - -#endif /* __LINUX_MTD_LPC32XX_SLC_H */ From def559a215699aa2f2d8ef98f30156b9d562a154 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 3 Sep 2026 14:24:24 +0300 Subject: [PATCH 028/155] mtd: rawnand: lpc32xx_mlc: Remove platform data support Since NXP LPC32xx SoC support in the Linux kernel strictly depends on device tree support, the unused platform data support can be removed from the NAND MLC driver. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/lpc32xx_mlc.c | 21 ++------------------- include/linux/mtd/lpc32xx_mlc.h | 17 ----------------- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 include/linux/mtd/lpc32xx_mlc.h diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c index 8f6a89d9ba83..8436015edcb4 100644 --- a/drivers/mtd/nand/raw/lpc32xx_mlc.c +++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -174,7 +173,6 @@ static struct nand_bbt_descr lpc32xx_nand_bbt_mirror = { struct lpc32xx_nand_host { struct platform_device *pdev; struct nand_chip nand_chip; - struct lpc32xx_mlc_platform_data *pdata; struct clk *clk; struct gpio_desc *wp_gpio; void __iomem *io_base; @@ -580,24 +578,11 @@ static void lpc32xx_ecc_enable(struct nand_chip *chip, int mode) static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host) { struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); - dma_cap_mask_t mask; host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx"); if (IS_ERR(host->dma_chan)) { - /* fallback to request using platform data */ - if (!host->pdata || !host->pdata->dma_filter) { - dev_err(mtd->dev.parent, "no DMA platform data\n"); - return -ENOENT; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, "nand-mlc"); - - if (!host->dma_chan) { - dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); - return -EBUSY; - } + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return PTR_ERR(host->dma_chan); } /* @@ -736,8 +721,6 @@ static int lpc32xx_nand_probe(struct platform_device *pdev) gpiod_set_consumer_name(host->wp_gpio, "NAND WP"); - host->pdata = dev_get_platdata(&pdev->dev); - /* link the private data structures */ nand_set_controller_data(nand_chip, host); nand_set_flash_node(nand_chip, pdev->dev.of_node); diff --git a/include/linux/mtd/lpc32xx_mlc.h b/include/linux/mtd/lpc32xx_mlc.h deleted file mode 100644 index 35e971be0950..000000000000 --- a/include/linux/mtd/lpc32xx_mlc.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Platform data for LPC32xx SoC MLC NAND controller - * - * Copyright © 2012 Roland Stigge - */ - -#ifndef __LINUX_MTD_LPC32XX_MLC_H -#define __LINUX_MTD_LPC32XX_MLC_H - -#include - -struct lpc32xx_mlc_platform_data { - dma_filter_fn dma_filter; -}; - -#endif /* __LINUX_MTD_LPC32XX_MLC_H */ From e42bfc080d96eb36520f5b4f50b50767837bc1e0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:54 +0200 Subject: [PATCH 029/155] 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; From 0fb499b527cdcdb87188fbe54684e258bb98b9f1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:55 +0200 Subject: [PATCH 030/155] 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)); From 4df380710b8c8a076923050a948b41715ef09b5f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:56 +0200 Subject: [PATCH 031/155] 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) From 9bae0fe02ea8abf11ab8d86ca2ca6b907a874077 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:57 +0200 Subject: [PATCH 032/155] 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), From 0f3a2567a10609b720bffea15e80de53c1f686fa Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:58 +0200 Subject: [PATCH 033/155] 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, From 2267f0161e60d3052e94a63f9c71a45d77be1d27 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:18:59 +0200 Subject: [PATCH 034/155] 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), From 9f071bb8fadc1f09c4d5f075d3c1f91b6f6bf0c9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:00 +0200 Subject: [PATCH 035/155] 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; } From 66c36f35633dd5b6fb8412697229996d14973d9b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:01 +0200 Subject: [PATCH 036/155] 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; } From 8218e55d31725f5768f54e91e6b865c53e7295f8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:02 +0200 Subject: [PATCH 037/155] 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), From e9f96f43214094c4b9227acffca55b9abbf2f30b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:03 +0200 Subject: [PATCH 038/155] 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, }, { From fc9f75c1457b9ea8f80db77c8292b2ed84116589 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:04 +0200 Subject: [PATCH 039/155] 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), From ab7242e3dc6ec73f657f03d03e0f97277664d910 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:05 +0200 Subject: [PATCH 040/155] 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), From 9b65f84c62f0377dc2311919fc63b37ba0e9c03a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:06 +0200 Subject: [PATCH 041/155] 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), From c9aee7f457c3e37ca8753c10e13653ff297e9f4a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:07 +0200 Subject: [PATCH 042/155] 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), From 151aa1a5953adaec73c793c25a6b881e98e12a73 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:08 +0200 Subject: [PATCH 043/155] 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; From 3b1fba74306b3a0d0d4052c00940b6e3c74dd2ca Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:09 +0200 Subject: [PATCH 044/155] 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), From 4a6aab1f7335dd8a54b768fdbcbcce0ad454d2fa Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:10 +0200 Subject: [PATCH 045/155] 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 */ From 08ce407217371994f738ab6c391f8af0f8a2eb2b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:11 +0200 Subject: [PATCH 046/155] 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), From 5eaaa72edec1bd5517808a8cb16d4c334ec69aff Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:12 +0200 Subject: [PATCH 047/155] 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), From 2e9abc448c2b6bc22d9d77eb20fcaa2cc05da6f9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:13 +0200 Subject: [PATCH 048/155] 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), From 860fd661dff43cd4d85eec4e7ad1c0bbfa9d1c98 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:14 +0200 Subject: [PATCH 049/155] 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), From e2e9dcab5505e8e4fc55404d9f329c5933001588 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:15 +0200 Subject: [PATCH 050/155] 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), From 0d8f65aa9a44574e0cabf89aa6aa37a7e3a19cfe Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:16 +0200 Subject: [PATCH 051/155] 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), From d5212797429d43c5ad3859f5b14683e52ab686a7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:17 +0200 Subject: [PATCH 052/155] 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), From 6648e64c6efe6a0b11a080ef1a87e26147085774 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:18 +0200 Subject: [PATCH 053/155] 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), From 41d96c2da5970f7e25b7818710d7732f5035cdb4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 19:19:19 +0200 Subject: [PATCH 054/155] 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), From a885b694afb4acd914e4a9f94365d828432ca066 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:38 +0200 Subject: [PATCH 055/155] 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) From 01771ff25fec9d24663a0087723621e97e6d8484 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:39 +0200 Subject: [PATCH 056/155] 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; } From 63489002d397ebf136548c79af68a8e05b30d45c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:40 +0200 Subject: [PATCH 057/155] 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 | 52 +- drivers/mtd/spi-nor/core.c | 664 ++++++++--------------- 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, 392 insertions(+), 597 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) - dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n"); + return 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 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,34 +717,91 @@ 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); + } + + memcpy(sr, nor->bouncebuf, len); + + if (ret) + dev_dbg(nor->dev, "Error %d reading Status Registers\n", ret); + + return ret; +} + +/** + * 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_sr_ll(struct spi_nor *nor, u8 opcode, const u8 *sr, + unsigned int len) +{ + int ret, i; + + if (len > 2) + return -EINVAL; + + ret = spi_nor_write_enable(nor); + if (ret) + return ret; + + for (i = 0; i < len; i++) + nor->bouncebuf[i] = sr[i]; + + if (nor->spimem) { + struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode, + nor->bouncebuf, len); + + 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, opcode, + nor->bouncebuf, len); } if (ret) { - dev_dbg(nor->dev, "error %d writing SR\n", ret); + dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret); return ret; } @@ -820,309 +809,193 @@ int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) } /** - * 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. + * 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. */ -static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1) +int spi_nor_read_sr1(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; - - if (nor->bouncebuf[0] != sr1) { - dev_dbg(nor->dev, "SR1: read back test failed\n"); - return -EIO; - } - - return 0; + return spi_nor_read_sr_ll(nor, nor->params->opcodes.read_sr1, sr1, 1); } /** - * 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_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 * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr2: pointer to a valid SR2 buffer + * + * Return 0 or errno. */ -static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) +{ + struct spi_nor_flash_parameter *params = nor->params; + + if (!params->opcodes.read_sr2) + return -EINVAL; + + return spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1); +} + +/** + * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2 + * General purpose helper. + * + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. + */ +int spi_nor_read_sr1_and_sr2(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(nor, &sr[1]); +} + +/** + * spi_nor_write_sr1() - Write SR1 only + * Useful for: + * - Manufacturer drivers (since they know the chip SR layout) + * + * @nor: the spi_nor structure + * @sr1: pointer to a valid SR1 buffer + * + * Return 0 or errno. + */ +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1) +{ + if (WARN_ONCE(!nor->params->opcodes.write_sr1, + "Restricted helper use, write SR1 not supported")) + return -EIO; + + return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr1, sr1, 1); +} + +/** + * spi_nor_write_sr2() - Write SR2 only + * Useful for: + * - 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_write_sr2(struct spi_nor *nor, const u8 *sr2) +{ + if (WARN_ONCE(!nor->params->opcodes.write_sr2, + "Restricted helper use, write SR2 not supported")) + return -EIO; + + return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr2, sr2, 1); +} + +/** + * spi_nor_write_sr1_and_sr2() - Write SR1 then SR2 + * 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. + */ +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; - 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; - } + if (params->opcodes.write_sr1_and_sr2) + return spi_nor_write_sr_ll(nor, + params->opcodes.write_sr1_and_sr2, + sr, 2); - sr_cr[0] = sr1; - - ret = spi_nor_write_sr(nor, sr_cr, 2); + ret = spi_nor_write_sr1(nor, &sr[0]); if (ret) return ret; - ret = spi_nor_read_sr(nor, sr_cr); - if (ret) - return ret; - - if (sr1 != sr_cr[0]) { - dev_dbg(nor->dev, "SR: Read back test failed\n"); - return -EIO; - } - - if (params->flags & SNOR_F_NO_READ_CR) - return 0; - - cr_written = sr_cr[1]; - - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) - return ret; - - if (cr_written != sr_cr[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } + if (params->opcodes.write_sr2) + return spi_nor_write_sr2(nor, &sr[1]); return 0; } /** - * 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_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. * - * Return: 0 on success, -errno otherwise. + * @nor: the spi_nor structure + * @sr: pointer to a valid 2-byte array + * + * Return 0 or errno. */ -int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr) { + u8 tmp[2]; 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); + ret = spi_nor_write_sr1_and_sr2(nor, sr); if (ret) return ret; - sr_cr[1] = cr; - - ret = spi_nor_write_sr(nor, sr_cr, 2); + ret = spi_nor_read_sr1_and_sr2(nor, tmp); 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"); + if (sr[0] != tmp[0] || sr[1] != tmp[1]) return -EIO; - } - - if (nor->params->flags & SNOR_F_NO_READ_CR) - return 0; - - ret = spi_nor_read_cr(nor, &sr_cr[1]); - if (ret) - return ret; - - if (cr != sr_cr[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } return 0; } /** - * 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_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_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +static int spi_nor_generic_quad_enable(struct spi_nor *nor) { - u8 written_regs[2]; + u8 *qe_mask = nor->params->qe_mask; + u8 sr[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); - 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) + if (!qe_mask[0] && !qe_mask[1]) return 0; - ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]); + ret = spi_nor_read_sr1_and_sr2(nor, sr); if (ret) return ret; - if (written_regs[1] != nor->bouncebuf[1]) { - dev_dbg(nor->dev, "CR: read back test failed\n"); - return -EIO; - } + if (sr[0] & qe_mask[0] || sr[1] & qe_mask[1]) + return 0; - return 0; -} + sr[0] |= qe_mask[0]; + sr[1] |= qe_mask[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. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) -{ - 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); -} - -/** - * 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. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) -{ - 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]); -} - -/** - * 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. - * - * Return: 0 on success, -errno otherwise. - */ -static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) -{ - int ret; - - ret = spi_nor_write_enable(nor); - if (ret) - return ret; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2); - - 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_WRSR2, - sr2, 1); - } - - if (ret) { - dev_dbg(nor->dev, "error %d writing SR2\n", ret); - return ret; - } - - return spi_nor_wait_till_ready(nor); -} - -/** - * 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. - * - * Return: 0 on success, -errno otherwise. - */ -static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2); - - 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_RDSR2, sr2, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading SR2\n", ret); - - return ret; + return spi_nor_write_sr1_and_sr2_and_check(nor, sr); } /** @@ -1914,106 +1787,6 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) 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) From 9f1feb4162387295b0d8de6c879f00357852609b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:41 +0200 Subject: [PATCH 058/155] 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) */ From af9bc9f9c5dcbc1a351ac00d9a4b86400bc442b8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:42 +0200 Subject: [PATCH 059/155] 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->info->fixups && nor->info->fixups->default_init) - nor->info->fixups->default_init(nor); + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; + + fixups = nor->manufacturer->fixups; + + 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->info->fixups && nor->info->fixups->smpt_read_dummy) - nor->info->fixups->smpt_read_dummy(nor, read_dummy); + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; + + 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->info->fixups && nor->info->fixups->smpt_map_id) - nor->info->fixups->smpt_map_id(nor, map_id); + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; + + fixups = nor->manufacturer->fixups; + + 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 @@ static int spi_nor_parse_sccr_mc(struct spi_nor *nor, */ 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; - if (nor->info->fixups && nor->info->fixups->post_sfdp) - return nor->info->fixups->post_sfdp(nor); + fixups = nor->manufacturer->fixups; + + 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), }; From e6dfda3c83eb8463bdf22289cfb42de9938446d0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:43 +0200 Subject: [PATCH 060/155] 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 (fixup_flags & SPI_NOR_4B_OPCODES) - params->flags |= SNOR_F_4B_OPCODES; + if (!nor->manufacturer || !nor->manufacturer->fixups) + return; - if (fixup_flags & SPI_NOR_IO_MODE_EN_VOLATILE) - params->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + fixups = nor->manufacturer->fixups; + + 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 }, From 98b89d868a178d47277feede51314d8227d4cb56 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:44 +0200 Subject: [PATCH 061/155] 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; From aa0a09e0e99838ab9349b1958118c44e6db6737d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:45 +0200 Subject: [PATCH 062/155] 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 */ From 7fdc105ff98d712e1e2f7e9f55e4ab2251e1344f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:46 +0200 Subject: [PATCH 063/155] 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 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) 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 = { From 7e896ca2fcf15066a6957120dfd9063820506cd2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:47 +0200 Subject: [PATCH 064/155] 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, From 25949585ca2f31f39a5b116aee23ffa017281929 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:48 +0200 Subject: [PATCH 065/155] 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, From f84397a769c9aa45e9a6b6a6f1253c1f990c7c72 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:49 +0200 Subject: [PATCH 066/155] 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", From 7573a4edaae362fc0da3fc8996f1265a9dbace4c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:50 +0200 Subject: [PATCH 067/155] 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, From 49c32f5b2c2ec8abfc97b8c0ea0c645568f65f8e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:51 +0200 Subject: [PATCH 068/155] 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, From 4da2d59f3a6cc217399b7c106c36c2f3655106a7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:52 +0200 Subject: [PATCH 069/155] 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, From 8d3298fd1d75011c2fa30e813cd280856ea86a2e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:53 +0200 Subject: [PATCH 070/155] 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, From 66d57b58c918a556d57bfab2d85ac1d33e21e1d9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:54 +0200 Subject: [PATCH 071/155] 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, From 650ee584cc52124a8ab51070d320d6752ea2f580 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:55 +0200 Subject: [PATCH 072/155] 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, From 94460d091584f9032539a5c949e6e71fa812ad15 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:56 +0200 Subject: [PATCH 073/155] 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, From 11484eb5458fd54b59f35c511819af9e4de27cd1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:57 +0200 Subject: [PATCH 074/155] 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, From 8eb8c80e59a3cde9fd7e4d730532668cb8ef7b0d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:58 +0200 Subject: [PATCH 075/155] 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 | From 6397f5f8f8754218dd3a059d39eba6b57d04dc3d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:12:59 +0200 Subject: [PATCH 076/155] 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, From 677a0d6752eda1480eae3e187c5dfe7367f9d752 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:13:00 +0200 Subject: [PATCH 077/155] 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, From 4094a2fd562453cac04e909688af6620fbd3203f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:13:01 +0200 Subject: [PATCH 078/155] 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, From 700bf34058ca7cd792236b1ba5caad3770d66208 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 20:13:02 +0200 Subject: [PATCH 079/155] 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: From a3c84084a73e170374b7dd493947efdef4ee57bc Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Fri, 4 Sep 2026 19:05:37 -0600 Subject: [PATCH 080/155] mtd: rawnand: sunxi: reject ECC maximization on small pages When nand-ecc-maximize is set, the driver selects a 1024-byte ECC step and divides the available OOB bytes by the resulting number of steps. A NAND with a smaller page therefore produces zero steps and a division by zero. The older controllers also provide a 512-byte ECC block mode, but the hardware documentation describes that as an ECC block size within NAND pages whose supported sizes start at 1024 bytes. It does not document a 512-byte NAND page mode. Reject pages smaller than 1024 bytes before deriving the step count. This prevents the division by zero at the point where the incompatible maximized geometry is selected without changing explicitly configured 512-byte ECC block handling. Fixes: 4796d8655915 ("mtd: nand: sunxi: Support ECC maximization") Cc: stable@vger.kernel.org Signed-off-by: James Hilliard Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 83666dd6cb2a..c84f304fb749 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -2084,6 +2084,9 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { int bytes = mtd->oobsize; + if (mtd->writesize < 1024) + return -EINVAL; + ecc->size = 1024; nsectors = mtd->writesize / ecc->size; From c56d52865624ca236c44838ef9366a8597b6af6c Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Fri, 4 Sep 2026 16:17:58 -0600 Subject: [PATCH 081/155] mtd: rawnand: sunxi: fix free OOB section offsets The sunxi hardware ECC OOB layout places protected user data before the ECC bytes for every step. Since variable user-data lengths were introduced, free sections after the first use sunxi_get_ecc_offset(), which skips the protected user data and points at the ECC bytes instead. As a result, the MTD free-OOB interface addresses ECC bytes rather than the protected user data for those sections. Use sunxi_get_oob_offset() to point each free section at the beginning of its protected user data. Keep the special two-byte offset for section zero to reserve the bad block marker. Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length") Cc: stable@vger.kernel.org Signed-off-by: James Hilliard Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index c84f304fb749..f41feebe4257 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -2013,7 +2013,7 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, return 0; } - oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section); + oobregion->offset = sunxi_get_oob_offset(sunxi_nand, ecc, section); oobregion->length = user_data_sz; return 0; From 2b7e16d565af2b4715290d9de513187a7374b2ed Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 7 Sep 2026 23:03:27 -0700 Subject: [PATCH 082/155] mtd: rawnand: fsl_ifc: use devm_platform_get_and_ioremap_resource Replace the manual of_address_to_resource(), ioremap(), and iounmap() in the probe/remove paths with devm_platform_get_and_ioremap_resource() so the resource is managed and released automatically on probe failure and driver removal. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/fsl_ifc_nand.c | 30 +++++++++-------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c index a88ac2cfaccd..315263c94497 100644 --- a/drivers/mtd/nand/raw/fsl_ifc_nand.c +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -969,9 +968,6 @@ static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv) kfree(mtd->name); - if (priv->vbase) - iounmap(priv->vbase); - ifc_nand_ctrl->chips[priv->bank] = NULL; return 0; @@ -996,12 +992,12 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) { struct fsl_ifc_runtime __iomem *ifc; struct fsl_ifc_mtd *priv; - struct resource res; + struct resource *res; + void __iomem *vbase; static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", "ofpart", NULL }; int ret; int bank; - struct device_node *node = dev->dev.of_node; struct mtd_info *mtd; if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->rregs) @@ -1009,15 +1005,13 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) ifc = fsl_ifc_ctrl_dev->rregs; /* get, allocate and map the memory resource */ - ret = of_address_to_resource(node, 0, &res); - if (ret) { - dev_err(&dev->dev, "%s: failed to get resource\n", __func__); - return ret; - } + vbase = devm_platform_get_and_ioremap_resource(dev, 0, &res); + if (IS_ERR(vbase)) + return PTR_ERR(vbase); /* find which chip select it is connected to */ for (bank = 0; bank < fsl_ifc_ctrl_dev->banks; bank++) { - if (match_bank(fsl_ifc_ctrl_dev->gregs, bank, res.start)) + if (match_bank(fsl_ifc_ctrl_dev->gregs, bank, res->start)) break; } @@ -1054,13 +1048,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) priv->bank = bank; priv->ctrl = fsl_ifc_ctrl_dev; priv->dev = &dev->dev; - - priv->vbase = ioremap(res.start, resource_size(&res)); - if (!priv->vbase) { - dev_err(priv->dev, "%s: failed to map chip region\n", __func__); - ret = -ENOMEM; - goto err; - } + priv->vbase = vbase; dev_set_drvdata(priv->dev, priv); @@ -1076,7 +1064,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) &ifc->ifc_nand.nand_evter_intr_en); mtd = nand_to_mtd(&priv->chip); - mtd->name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start); + mtd->name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res->start); if (!mtd->name) { ret = -ENOMEM; goto err; @@ -1098,7 +1086,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) goto cleanup_nand; dev_info(priv->dev, "IFC NAND device at 0x%llx, bank %d\n", - (unsigned long long)res.start, priv->bank); + (unsigned long long)res->start, priv->bank); return 0; From 7e874b1750a40f3dc9a629aeb72eba09c77f77e9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:40:55 +0200 Subject: [PATCH 083/155] mtd: spinand: Fix spinand_manufacturer_ops kernel-doc The kernel-doc heading for struct spinand_manufacturer_ops misspells and truncates its name as manufacurer_ops. As a result, kernel-doc cannot match the comment to the structure declaration. Use the structure's actual name. Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support SPI NANDs") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Miquel Raynal --- include/linux/mtd/spinand.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index b42407b69db3..d7c99fcfb85e 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -399,7 +399,7 @@ struct spinand_devid { }; /** - * struct manufacurer_ops - SPI NAND manufacturer specific operations + * struct spinand_manufacturer_ops - SPI NAND manufacturer specific operations * @init: initialize a SPI NAND device * @cleanup: cleanup a SPI NAND device * From 673f0a002d60701af888d2280543b7556c0c2b20 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 6 Sep 2026 17:48:21 -0700 Subject: [PATCH 084/155] mtd: powernv_flash: fix kernel-doc for powernv_flash_release The function is powernv_flash_release, not op_release. Update the kernel-doc comment to match. Fixes W=1 warning: Warning: drivers/mtd/devices/powernv_flash.c:271 expecting prototype for op_release(). Prototype was for powernv_flash_release() instead Assisted-by: Opencode:BigPickle Signed-off-by: Rosen Penev Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Miquel Raynal --- drivers/mtd/devices/powernv_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/devices/powernv_flash.c b/drivers/mtd/devices/powernv_flash.c index a12427d9e20f..fe1d6684d0bd 100644 --- a/drivers/mtd/devices/powernv_flash.c +++ b/drivers/mtd/devices/powernv_flash.c @@ -263,7 +263,7 @@ static int powernv_flash_probe(struct platform_device *pdev) } /** - * op_release - Release the driver + * powernv_flash_release - Release the driver * @pdev: the platform device * * Returns 0 From 8fcbb04ee5152b303980b2b48d69c6f7f4094ca6 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 6 Sep 2026 17:49:10 -0700 Subject: [PATCH 085/155] mtd: docg3: use devm_platform_ioremap_resource() Replace the open-coded platform_get_resource(IORESOURCE_MEM, 0) plus devm_ioremap() sequence with devm_platform_ioremap_resource(), which folds the resource lookup, region reservation and mapping into one step and returns an ERR_PTR checked with IS_ERR()/PTR_ERR(). The canonical docg3 register window is DOC_IOSPACE_SIZE (0x2000), which is the size any platform provides for this device, so mapping the full resource is equivalent to the previous fixed-size devm_ioremap(). Built for ARM (multi_v7_defconfig + CONFIG_MTD_DOCG3) with LLVM=1; drivers/mtd/devices/docg3.o compiles cleanly. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/devices/docg3.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c index 84566997d665..3d89f810e2db 100644 --- a/drivers/mtd/devices/docg3.c +++ b/drivers/mtd/devices/docg3.c @@ -1971,25 +1971,15 @@ static int __init docg3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtd_info *mtd; - struct resource *ress; void __iomem *base; int ret, floor; struct docg3_cascade *cascade; - ret = -ENXIO; - ress = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!ress) { - dev_err(dev, "No I/O memory resource defined\n"); - return ret; - } + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); ret = -ENOMEM; - base = devm_ioremap(dev, ress->start, DOC_IOSPACE_SIZE); - if (!base) { - dev_err(dev, "devm_ioremap dev failed\n"); - return ret; - } - cascade = devm_kcalloc(dev, DOC_MAX_NBFLOORS, sizeof(*cascade), GFP_KERNEL); if (!cascade) From 90f242c92d3ab241f0d22e2368b57750b8755667 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 11 Sep 2026 19:22:45 +0200 Subject: [PATCH 086/155] 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)) From f917d9edf54c3dd578efc231b8fbcd35744f99cd Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Mon, 14 Sep 2026 13:50:36 +0900 Subject: [PATCH 087/155] 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", From a96edc3f0b4f036a5b6ebc9b1eca4273ef5ce2d0 Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Mon, 14 Sep 2026 13:50:37 +0900 Subject: [PATCH 088/155] 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 }, From 935457a1ddc90921c3a3444f8bb7aaa97d2af55c Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Thu, 17 Sep 2026 14:02:15 +0900 Subject: [PATCH 089/155] 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); From b1c987abe26f6b8614888c93dfb088193eebcdcc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:32 +0200 Subject: [PATCH 090/155] 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, From 96601905963b030a82b9ba4b0df98d8681c6a002 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:33 +0200 Subject: [PATCH 091/155] 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, From a0d9dedd3423efd069bad9f181cf69c7dda76464 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:34 +0200 Subject: [PATCH 092/155] 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), }, { From a13c9bf62caa62a4cb96d7b85394346cabb73496 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:35 +0200 Subject: [PATCH 093/155] 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 */ From acaacdb1e75d7ebca7ffb1fa97f149b08ea0fd3e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:36 +0200 Subject: [PATCH 094/155] 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 */ From f81793c87530d45e96c5a012fd35d87457328cd1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:37 +0200 Subject: [PATCH 095/155] 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), }, { From 6af98fcf79a5d86822d3863802ad063a7ad6ca7d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:38 +0200 Subject: [PATCH 096/155] 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 */ From 9780e26196039d7a67c915409e22c87db6b721d3 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:39 +0200 Subject: [PATCH 097/155] 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 */ From f5e252b178066ecf4efe5abf2b0438242199a1ea Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:40 +0200 Subject: [PATCH 098/155] 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 */ From efb7901c6b4d039d4a9a995c0710517b7217cbf0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:41 +0200 Subject: [PATCH 099/155] 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), }, { From 016d9512933789a5e23608cf0839288f894b73a1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:42 +0200 Subject: [PATCH 100/155] 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 */ From d83b5c1df429b5df707046fc6562c60432445b32 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:43 +0200 Subject: [PATCH 101/155] 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 */ From afdf4a8c316bba78d8e72a2e113c63427504ca85 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:44 +0200 Subject: [PATCH 102/155] 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), }, { From 34d7f1f5f1aab0c7b08599f0b77611d79b1377a9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:45 +0200 Subject: [PATCH 103/155] 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 */ From 3520931436ee2ac2720f097c856aea01541ded60 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:46 +0200 Subject: [PATCH 104/155] 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 */ From a95136c1c4756885e4d333c7968b716f9008ee99 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:47 +0200 Subject: [PATCH 105/155] 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 }, From 93a72776acb954814940af8b915e609e1b18893d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:48 +0200 Subject: [PATCH 106/155] 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, From c0f1355cece2e2bd19d62a35ba1b66bd09e89bde Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:49 +0200 Subject: [PATCH 107/155] 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, From b8a959f55f9f1806865aa38304289d2abaceacf4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:50 +0200 Subject: [PATCH 108/155] 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, From 136588c583adc2c73934bb0e89877fd1a8b91305 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:51 +0200 Subject: [PATCH 109/155] 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, From b60627be8faf82b2c64d86c83c27c730423d0524 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:52 +0200 Subject: [PATCH 110/155] 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 = { From 03613e40f15d704013f90b2405c4a3bc6b751207 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:53 +0200 Subject: [PATCH 111/155] 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, From d96b00b230d0512844d0e9c9bc391cdc30ab653b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:54 +0200 Subject: [PATCH 112/155] 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, From 8df89f0fe8e8ecc345c5a9e3ce49e41139eb4a0c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:55 +0200 Subject: [PATCH 113/155] 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, From ceedf3193cac49ddb488518616587484093d66e4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 21 Sep 2026 08:43:56 +0200 Subject: [PATCH 114/155] 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, From 68d7115d77a873c28d8c7d2f239c3d9ca83bf39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 14 Sep 2026 14:42:01 +0100 Subject: [PATCH 115/155] 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)) From 5e73728a875a91fb2c7dc527f7391da0205f85b1 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 9 Sep 2026 15:35:21 -0700 Subject: [PATCH 116/155] mtd: rawnand: ndfc: use devm_kasprintf for mtd name Convert the mtd->name allocation from kasprintf() plus a manual kfree() in the error path and in ndfc_remove() to the managed devm_kasprintf() helper. The name is now released automatically with the device, removing the need for explicit cleanup. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/ndfc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c index d6db04d48c45..d7b119028a71 100644 --- a/drivers/mtd/nand/raw/ndfc.c +++ b/drivers/mtd/nand/raw/ndfc.c @@ -162,8 +162,8 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc, return -ENODEV; nand_set_flash_node(chip, flash_np); - mtd->name = kasprintf(GFP_KERNEL, "%s.%pOFn", dev_name(&ndfc->ofdev->dev), - flash_np); + mtd->name = devm_kasprintf(&ndfc->ofdev->dev, GFP_KERNEL, "%s.%pOFn", + dev_name(&ndfc->ofdev->dev), flash_np); if (!mtd->name) { ret = -ENOMEM; goto err; @@ -177,8 +177,6 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc, err: of_node_put(flash_np); - if (ret) - kfree(mtd->name); return ret; } @@ -246,7 +244,6 @@ static void ndfc_remove(struct platform_device *ofdev) ret = mtd_device_unregister(mtd); WARN_ON(ret); nand_cleanup(chip); - kfree(mtd->name); } static const struct of_device_id ndfc_match[] = { From 44c7b273e6b06fad31f2ca166307a41705ef15dd Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 9 Sep 2026 15:38:13 -0700 Subject: [PATCH 117/155] mtd: rawnand: fsl_ifc: use devm_kasprintf for mtd name Replace the manual kasprintf() in probe with devm_kasprintf() so the name is freed automatically on driver removal, and drop the now unnecessary kfree() in fsl_ifc_chip_remove() and its leftover mtd local. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/fsl_ifc_nand.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c index 315263c94497..be6bbc1c184d 100644 --- a/drivers/mtd/nand/raw/fsl_ifc_nand.c +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -964,10 +964,6 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv) { - struct mtd_info *mtd = nand_to_mtd(&priv->chip); - - kfree(mtd->name); - ifc_nand_ctrl->chips[priv->bank] = NULL; return 0; @@ -1064,7 +1060,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) &ifc->ifc_nand.nand_evter_intr_en); mtd = nand_to_mtd(&priv->chip); - mtd->name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res->start); + mtd->name = devm_kasprintf(priv->dev, GFP_KERNEL, "%llx.flash", (u64)res->start); if (!mtd->name) { ret = -ENOMEM; goto err; From 01947ecc37bdead7d2e1f199185b936820b90eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Wed, 23 Sep 2026 14:52:29 +0100 Subject: [PATCH 118/155] mtd: spinand: winbond: Add support for W25N08LW MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the W25N08LW, a 1.8V 8Gbit SPI-NAND made of two 4Gbit LUNs, with 4096-byte pages and 256 bytes of spare area. The chip reuses the KV ECC status helper, but needs its own OOB layout because the geometry of its spare area differs from the existing Winbond parts. Only half of it is visible while the internal ECC is enabled, so the ECC region is reported as inaccessible and just the free bytes are exposed. Assisted-by: Claude:Opus-5 Signed-off-by: Nuno Sá Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index a1807fa9ae03..810975808c97 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -211,6 +211,13 @@ static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0), SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0)); +static SPINAND_OP_VARIANTS(cont_read_cache_variants, + WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(6, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(4, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(4, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(4, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(4, NULL, 0, 0)); + static SPINAND_OP_VARIANTS(write_cache_variants, SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0), SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); @@ -365,6 +372,39 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { .free = w25n02kv_ooblayout_free, }; +static int w25n08lw_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + /* + * With ecc enabled the parity bits are not accessible. So we can + * only see page + 128. Without ecc the full page + 256 is accessible. + * To make it simple just return the area as not accessible. + */ + return -ERANGE; +} + +static int w25n08lw_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 7) + return -ERANGE; + + /* + * Only expose the ECC protected "User Data I" bytes. The "User Data II" + * bytes at offset + 2 are not covered by the on-die ECC engine, and the + * first two bytes of the section hold the BBM. + */ + region->offset = (16 * section) + 4; + region->length = 12; + + return 0; +} + +static const struct mtd_ooblayout_ops w25n08lw_ooblayout = { + .ecc = w25n08lw_ooblayout_ecc, + .free = w25n08lw_ooblayout_free, +}; + static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { @@ -810,6 +850,18 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status), SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg), SPINAND_CONT_READ(w35n0xjw_set_cont_read)), + /* 8G-bit densities */ + SPINAND_INFO("W25N08LW", /* 2x4G-bit 1.8V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb3, 0x24), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_variants, + &write_cache_variants, + &update_cache_variants, + &cont_read_cache_variants), + 0, + SPINAND_ECCINFO(&w25n08lw_ooblayout, w25n02kv_ecc_get_status), + SPINAND_CONT_READ(w25n0xjw_set_cont_read)), }; static int winbond_spinand_init(struct spinand_device *spinand) From 8a485a40f45a19c5fcafb5cbee7387c2762af7c4 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 16 Sep 2026 00:45:37 +0800 Subject: [PATCH 119/155] mtd: rawnand: nuvoton: fix clock cleanup on probe failure ma35_nand_probe() gets and enables the NAND clock with devm_clk_get_enabled(), but the ma35_nand_chips_init() failure path also calls clk_disable() before returning. devm_clk_get_enabled() registers a managed cleanup that calls clk_disable_unprepare() on probe failure and driver removal. Calling clk_disable() explicitly first drops the clock enable count, so the managed cleanup attempts to disable the clock again and triggers the clock framework warning for an already disabled clock. Remove the redundant clk_disable() and let the managed clock cleanup handle the clock state. This issue was found by manual code inspection. Fixes: 5abb5d414d55 ("mtd: rawnand: nuvoton: add new driver for the Nuvoton MA35 SoC") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c index 1a285cd8fad6..f659d3a6b9ef 100644 --- a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c +++ b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c @@ -993,7 +993,6 @@ static int ma35_nand_probe(struct platform_device *pdev) ret = ma35_nand_chips_init(&pdev->dev, nand); if (ret) { dev_err(&pdev->dev, "failed to init NAND chips\n"); - clk_disable(nand->clk); return ret; } From 94edcda45d888c3bb1310de11e10e4537c166f00 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 10:29:24 +0000 Subject: [PATCH 120/155] mtd: rawnand: atmel: Fix MCK clock leak in atmel_nand_controller_init() atmel_nand_controller_init() takes a reference to the MCK clock with of_clk_get() and keeps it in nc->mck for the lifetime of the controller. If the "atmel,smc" phandle is missing or the SMC regmap cannot be obtained afterwards, the error path jumps to out_release_dma which only releases the DMA channel, so the clock reference is leaked. The callers return the error directly and never run atmel_nand_controller_cleanup(), where the clock is normally put. Release the reference on those error paths. The path on which of_clk_get() itself fails keeps jumping to out_release_dma, as nc->mck only holds an error pointer there. Fixes: f9ce2eddf176 ("mtd: nand: atmel: Add ->setup_data_interface() hooks") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/atmel/nand-controller.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index 8a19408abb63..0bdbcbb50d94 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -2085,7 +2085,7 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc, if (!np) { dev_err(dev, "Missing or invalid atmel,smc property\n"); ret = -EINVAL; - goto out_release_dma; + goto out_put_mck; } nc->smc = syscon_node_to_regmap(np); @@ -2093,11 +2093,13 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc, if (IS_ERR(nc->smc)) { ret = PTR_ERR(nc->smc); dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret); - goto out_release_dma; + goto out_put_mck; } return 0; +out_put_mck: + clk_put(nc->mck); out_release_dma: if (nc->dmac) dma_release_channel(nc->dmac); From e97c701c09d3d8f23397f68478c7e2c7eaf87528 Mon Sep 17 00:00:00 2001 From: Peter-Simon Dieterich Date: Thu, 17 Sep 2026 15:00:42 +0200 Subject: [PATCH 121/155] mtd: rawnand: esmt: Disable broken timings feature on GD9FU2G8F3A GigaDevice and ESMT share JEDEC manufacturer ID 0xc8, so GigaDevice raw NAND chips are handled by the ESMT manufacturer code. The GD9FU2G8F3A advertises SET/GET_FEATURES support in its parameter page, but its implementation of the timing mode feature is broken: GET_FEATURES(TIMING_MODE) reads back a value that does not match the mode previously written, which causes the following error during boot: nand: timing mode 5 not acknowledged by the NAND chip and falls back to timing mode 0. GigaDevice confirmed that the chip is compatible with all timing modes advertised in its parameter page, that SET_FEATURE(TIMING_MODE) is not required to enter them, and that it may be ignored by the chip. The timing mode is therefore a host-side concern only, and the readback value carries no information. Unflag the feature for this model so the core configures the controller and skips both the SET and GET, mirroring what nand_macronix.c does for the MX30LF AC series. Tested on a custom board with an imx6ull using gpmi-nand, this restores timing mode 5 operation and sequential reads improve from 7 MiB/s to 20 MiB/s. Signed-off-by: Peter-Simon Dieterich Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/nand_esmt.c | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/mtd/nand/raw/nand_esmt.c b/drivers/mtd/nand/raw/nand_esmt.c index 4412c407aef3..117b110fc54a 100644 --- a/drivers/mtd/nand/raw/nand_esmt.c +++ b/drivers/mtd/nand/raw/nand_esmt.c @@ -3,6 +3,9 @@ * Copyright (C) 2018 Toradex AG * * Author: Marcel Ziswiler + * + * ESMT and GigaDevice share the same JEDEC manufacturer ID (0xc8), so this + * file handles chips from both vendors. */ #include @@ -38,6 +41,35 @@ static void esmt_nand_decode_id(struct nand_chip *chip) nanddev_set_ecc_requirements(base, &requirements); } +/* + * GigaDevice GD9FU2G8F3A does not implement the timing mode feature: + * GET_FEATURES does not report the mode that was written, and + * SET_FEATURES is ignored, even though the parameter page advertises + * support. The vendor confirmed the chip works at every advertised + * timing mode without being told, so unflag the feature and configure + * the host side only. + */ +static void esmt_nand_fix_broken_get_timings(struct nand_chip *chip) +{ + int i; + static const char * const broken_get_timings[] = { + "GD9FU2G8F3A", + }; + + if (!chip->parameters.supports_set_get_features) + return; + + i = match_string(broken_get_timings, ARRAY_SIZE(broken_get_timings), + chip->parameters.model); + if (i < 0) + return; + + bitmap_clear(chip->parameters.get_feature_list, + ONFI_FEATURE_ADDR_TIMING_MODE, 1); + bitmap_clear(chip->parameters.set_feature_list, + ONFI_FEATURE_ADDR_TIMING_MODE, 1); +} + static int esmt_nand_init(struct nand_chip *chip) { if (nand_is_slc(chip)) @@ -50,6 +82,8 @@ static int esmt_nand_init(struct nand_chip *chip) chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE | NAND_BBM_LASTPAGE; + esmt_nand_fix_broken_get_timings(chip); + return 0; } From c62b598ac7f650c963ca00a19f161e99e899498e Mon Sep 17 00:00:00 2001 From: Peter-Simon Dieterich Date: Thu, 17 Sep 2026 15:00:43 +0200 Subject: [PATCH 122/155] mtd: rawnand: Reflect that ESMT and GigaDevice share ID 0xc8 JEDEC manufacturer ID 0xc8 is used by both ESMT and GigaDevice, so nand_esmt.c handles chips from both vendors. Chips from either vendor are currently reported as "ESMT", which is misleading when debugging a GigaDevice part. Rename the manufacturer entry to "ESMT/GigaDevice" and document the collision next to the NAND_MFR_ESMT definition. The only behavioural change is the manufacturer name printed at detection time. Signed-off-by: Peter-Simon Dieterich Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/internals.h | 1 + drivers/mtd/nand/raw/nand_ids.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h index b7162ced9efa..b8b29341b88c 100644 --- a/drivers/mtd/nand/raw/internals.h +++ b/drivers/mtd/nand/raw/internals.h @@ -19,6 +19,7 @@ #define NAND_MFR_AMD 0x01 #define NAND_MFR_ATO 0x9b #define NAND_MFR_EON 0x92 +/* ESMT and GigaDevice share the same manufacturer ID. */ #define NAND_MFR_ESMT 0xc8 #define NAND_MFR_FUJITSU 0x04 #define NAND_MFR_HYNIX 0xad diff --git a/drivers/mtd/nand/raw/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c index 08707ce9d7c0..9b2dd246398f 100644 --- a/drivers/mtd/nand/raw/nand_ids.c +++ b/drivers/mtd/nand/raw/nand_ids.c @@ -185,7 +185,7 @@ static const struct nand_manufacturer_desc nand_manufacturer_descs[] = { {NAND_MFR_AMD, "AMD/Spansion", &amd_nand_manuf_ops}, {NAND_MFR_ATO, "ATO"}, {NAND_MFR_EON, "Eon"}, - {NAND_MFR_ESMT, "ESMT", &esmt_nand_manuf_ops}, + {NAND_MFR_ESMT, "ESMT/GigaDevice", &esmt_nand_manuf_ops}, {NAND_MFR_FUJITSU, "Fujitsu"}, {NAND_MFR_HYNIX, "Hynix", &hynix_nand_manuf_ops}, {NAND_MFR_INTEL, "Intel"}, From 55c5b6d5f59f59a8c98a7effc3195226cc20175c Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sun, 20 Sep 2026 22:16:18 +0200 Subject: [PATCH 123/155] mtd: nand: qpic_common: drop stray empty line from struct 'bam_transaction' Drop a superfluous empty line from the end of the 'bam_positions' structure group within the 'bam_transaction' structure. No functional changes. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- include/linux/mtd/nand-qpic-common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/mtd/nand-qpic-common.h b/include/linux/mtd/nand-qpic-common.h index 437448995187..0f40964eefe7 100644 --- a/include/linux/mtd/nand-qpic-common.h +++ b/include/linux/mtd/nand-qpic-common.h @@ -262,7 +262,6 @@ struct bam_transaction { u32 tx_sgl_start; u32 rx_sgl_pos; u32 rx_sgl_start; - ); }; From 3be16e5d4990bf96309ac5ba35cb369c6015bce7 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:06:47 -0400 Subject: [PATCH 124/155] mtd: maps: pci: balance PCI enable on cleanup mtd_pci_probe() leaves the PCI device enabled on later probe failure and mtd_pci_remove() does not disable it either. Use pcim_enable_device() to release the enable reference after the existing map and region cleanup. The imbalance is already present in the initial Git import. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Miquel Raynal --- drivers/mtd/maps/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/maps/pci.c b/drivers/mtd/maps/pci.c index 34041cc8f173..f13a1f520d04 100644 --- a/drivers/mtd/maps/pci.c +++ b/drivers/mtd/maps/pci.c @@ -250,7 +250,7 @@ static int mtd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) struct mtd_info *mtd = NULL; int err; - err = pci_enable_device(dev); + err = pcim_enable_device(dev); if (err) goto out; From cfc5ebc9540e29f8f96db88bbfa261139e5afedb Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Wed, 16 Sep 2026 16:27:31 +0000 Subject: [PATCH 125/155] mtd: cfi_cmdset_0002: cap the write-buffer chunk at 256 bytes on an x8 device The Write to Buffer command takes the number of words to program minus one, and do_write_buffer() sends that count as a single bus word: map_write(map, CMD(words - 1), cmd_adr); CMD() replicates the value into every device lane, so each device reads the count off its own data lines - eight of them on an x8 part. A count that does not fit in eight bits is truncated there, which limits one Write to Buffer to 256 words on an x8 device, however large a buffer the chip advertises in its CFI query. The M29EW on a Cavium Octeon CN6335 board (an x16 part strapped to x8) advertises MaxBufWriteSize = 512 bytes, so cfi_amdstd_write_buffers() split the data into 512-byte chunks and do_write_buffer() sent a count of 511. The chip saw 511 & 0xff = 255, expected 256 bytes, and aborted the program when the 257th arrived. Chunks below 256 bytes - the leading partial chunk of a write - completed normally, so only the full-size ones failed: MTD do_write_buffer_wait(): software timeout, address:0x03278bff. jffs2: Write of 4164 bytes at 0x02c789b4 failed. returned -5, retlen 68 jffs2: No space for garbage collection. Aborting GC thread Clamp MaxBufWriteSize for x8 devices in the write-buffer fixup, and recompute mtd->writebufsize, which cfi_cmdset_0002_setup() derives from MaxBufWriteSize before the fixups run. The clamp deliberately keys off the device width rather than the part, even though the driver already recognises this one in is_m29ew(): the ceiling comes from the width of a device's data bus, not from the chip, so any x8 device advertising a larger buffer is told a count it cannot receive. The M29EW is only where it was found. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Signed-off-by: Miquel Raynal --- drivers/mtd/chips/cfi_cmdset_0002.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index 517db2f2707f..100cab95a17b 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -283,6 +283,21 @@ static void fixup_use_write_buffers(struct mtd_info *mtd) pr_debug("Using buffer write method\n"); mtd->_write = cfi_amdstd_write_buffers; } + + /* + * The word count of the Write to Buffer command is a single bus + * word per device, so an x8 device can be told to program at most + * 256 bytes however large a buffer it advertises - including when + * several of them are interleaved on a wider bus, where CMD() + * replicates the count into each device's lane and it is truncated + * there. + */ + if (cfi->device_type == CFI_DEVICETYPE_X8 && + cfi->cfiq->MaxBufWriteSize > 8) { + cfi->cfiq->MaxBufWriteSize = 8; + mtd->writebufsize = cfi_interleave(cfi) << + cfi->cfiq->MaxBufWriteSize; + } } #endif /* !FORCE_WORD_WRITE */ From 1a122f5fecbabd47b81b1be3f0934f57b78aad42 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 10:20:51 +0000 Subject: [PATCH 126/155] mtd: amd76xrom: Fix PCI device reference leak in amd76xrom_init_one() init_amd76xrom() obtains the PCI device with pci_get_device() and hands the reference over to amd76xrom_init_one(), which stores it in window->pdev. When reserving the ROM window fails, that function returns -EBUSY directly instead of going through amd76xrom_cleanup(), so the reference is neither released nor cleared. Drop the reference and clear window->pdev on that path. Fixes: dd8e9ed6ed54 ("[MTD] Switch to pci_get_device and do ref counting") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Miquel Raynal --- drivers/mtd/maps/amd76xrom.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/maps/amd76xrom.c b/drivers/mtd/maps/amd76xrom.c index 457e6ab7f3fb..0c3e0eb08b8d 100644 --- a/drivers/mtd/maps/amd76xrom.c +++ b/drivers/mtd/maps/amd76xrom.c @@ -152,6 +152,8 @@ static int amd76xrom_init_one(struct pci_dev *pdev, printk(KERN_ERR MOD_NAME " %s(): Unable to register resource %pR - kernel bug?\n", __func__, &window->rsrc); + pci_dev_put(window->pdev); + window->pdev = NULL; return -EBUSY; } From 521ad948e04934b2d70ae899bc016bbb38802e90 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 10:22:44 +0000 Subject: [PATCH 127/155] mtd: maps: l440gx: Fix double put of the ISA bridge in init_l440gx() init_l440gx() takes a single reference on the PIIX4 ISA bridge with pci_get_device() and drops it again right after the lookups, before the flash window is set up. The pci_assign_resource() failure path then calls pci_dev_put() on the same device a second time, releasing a reference that is no longer owned and underflowing the reference count. Remove the extra pci_dev_put(). Fixes: dd8e9ed6ed54 ("[MTD] Switch to pci_get_device and do ref counting") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Miquel Raynal --- drivers/mtd/maps/l440gx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mtd/maps/l440gx.c b/drivers/mtd/maps/l440gx.c index 832b880d1aaf..85cd273ce065 100644 --- a/drivers/mtd/maps/l440gx.c +++ b/drivers/mtd/maps/l440gx.c @@ -110,7 +110,6 @@ static int __init init_l440gx(void) /* Allocate the resource region */ if (pci_assign_resource(pm_dev, PIIXE_IOBASE_RESOURCE) != 0) { - pci_dev_put(dev); pci_dev_put(pm_dev); printk(KERN_WARNING "Could not allocate pm iobase resource\n"); iounmap(l440gx_map.virt); From e6ca524ee5b2705be30ea19ce2e55122d9f9ed8f Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 10:24:07 +0000 Subject: [PATCH 128/155] mtd: maps: l440gx: Fix double put of the PM device in init_l440gx() The PIIX4 PM device reference taken with pci_get_device() is dropped again before the iobase resource is allocated. If pci_assign_resource() fails, the error path calls pci_dev_put() on pm_dev a second time, releasing a reference that is no longer owned and underflowing the reference count. Remove the extra pci_dev_put(). Fixes: dd8e9ed6ed54 ("[MTD] Switch to pci_get_device and do ref counting") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Miquel Raynal --- drivers/mtd/maps/l440gx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mtd/maps/l440gx.c b/drivers/mtd/maps/l440gx.c index 85cd273ce065..1df5ea5fafca 100644 --- a/drivers/mtd/maps/l440gx.c +++ b/drivers/mtd/maps/l440gx.c @@ -110,7 +110,6 @@ static int __init init_l440gx(void) /* Allocate the resource region */ if (pci_assign_resource(pm_dev, PIIXE_IOBASE_RESOURCE) != 0) { - pci_dev_put(pm_dev); printk(KERN_WARNING "Could not allocate pm iobase resource\n"); iounmap(l440gx_map.virt); return -ENXIO; From 62ee3da38f41bef83c1cf250f6816a0ee187674b Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 10:25:50 +0000 Subject: [PATCH 129/155] mtd: mtdsuper: Fix MTD device reference leak in mtd_get_sb() mtd_get_sb() is called with a reference to the MTD device already taken by the caller. When sget_dev() fails, the function returns the error without dropping that reference, so the device is never released and its refcount never reaches zero. Every mount attempt on an MTD device whose superblock cannot be set up leaks a reference. Restore the put_mtd_device() that the fs_context conversion dropped on this path. Fixes: 0f071004109d ("mtd: Provide fs_context-aware mount_mtd() replacement") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Miquel Raynal --- drivers/mtd/mtdsuper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/mtdsuper.c b/drivers/mtd/mtdsuper.c index c709ff7aa6b5..02eede3fe582 100644 --- a/drivers/mtd/mtdsuper.c +++ b/drivers/mtd/mtdsuper.c @@ -31,8 +31,10 @@ static int mtd_get_sb(struct fs_context *fc, int ret; sb = sget_dev(fc, MKDEV(MTD_BLOCK_MAJOR, mtd->index)); - if (IS_ERR(sb)) + if (IS_ERR(sb)) { + put_mtd_device(mtd); return PTR_ERR(sb); + } if (sb->s_root) { /* new mountpoint for an already mounted superblock */ From 904f18430e8cce829cb9130eab7a93020e33fc89 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Thu, 17 Sep 2026 16:53:29 +0200 Subject: [PATCH 130/155] mtd: Call get_wunit() on the master partition The code checks whether master has get_wunit() but still calls it on the child. Fix this. Fixes: 46b5889cc2c5 ("mtd: implement proper partition handling") Cc: stable@vger.kernel.org Signed-off-by: Richard Weinberger Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 16629382a787..82b1e6392b16 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -563,7 +563,7 @@ int mtd_pairing_info_to_wunit(struct mtd_info *mtd, return -EINVAL; if (master->pairing && master->pairing->get_wunit) - return mtd->pairing->get_wunit(master, info); + return master->pairing->get_wunit(master, info); return info->pair; } From a018524a2ec96da5bb1965053967a20e0069ed9e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 17 Sep 2026 16:47:06 -0400 Subject: [PATCH 131/155] mtd: use dmaengine_get_dma_device() instead of chan->device->dev Replace direct dma_chan::device::dev access with the proper dmaengine_get_dma_device() for consumer API. chan->device->dev is not always the device used for DMA mapping. Some DMA engines support per-channel IOMMU mappings, so different channels may use different DMA devices. dmaengine_get_dma_device() returns the correct device for each channel. This also prepares for making the DMA engine provider data structures private. DMA consumers should not access DMA engine internals directly. Assisted-by: LLM Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- drivers/mtd/hyperbus/hbmc-am654.c | 7 ++++--- drivers/mtd/nand/raw/cadence-nand-controller.c | 2 +- drivers/mtd/nand/raw/intel-nand-controller.c | 6 ++++-- drivers/mtd/nand/raw/loongson-nand-controller.c | 4 ++-- drivers/mtd/nand/raw/lpc32xx_mlc.c | 10 ++++------ drivers/mtd/nand/raw/lpc32xx_slc.c | 10 ++++------ drivers/mtd/nand/raw/marvell_nand.c | 7 ++++--- drivers/mtd/nand/raw/omap2.c | 10 +++++----- drivers/mtd/nand/raw/sh_flctl.c | 8 +++++--- 9 files changed, 33 insertions(+), 31 deletions(-) diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c index 9d31464046b2..8a14e908d0b5 100644 --- a/drivers/mtd/hyperbus/hbmc-am654.c +++ b/drivers/mtd/hyperbus/hbmc-am654.c @@ -78,14 +78,15 @@ static int am654_hbmc_dma_read(struct am654_hbmc_device_priv *priv, void *to, struct dma_chan *rx_chan = priv->rx_chan; struct dma_async_tx_descriptor *tx; dma_addr_t dma_dst, dma_src; + struct device *rx_dev = dmaengine_get_dma_device(rx_chan); dma_cookie_t cookie; int ret; if (!priv->rx_chan || !virt_addr_valid(to) || object_is_on_stack(to)) return -EINVAL; - dma_dst = dma_map_single(rx_chan->device->dev, to, len, DMA_FROM_DEVICE); - if (dma_mapping_error(rx_chan->device->dev, dma_dst)) { + dma_dst = dma_map_single(rx_dev, to, len, DMA_FROM_DEVICE); + if (dma_mapping_error(rx_dev, dma_dst)) { dev_dbg(priv->ctlr->dev, "DMA mapping failed\n"); return -EIO; } @@ -117,7 +118,7 @@ static int am654_hbmc_dma_read(struct am654_hbmc_device_priv *priv, void *to, } unmap_dma: - dma_unmap_single(rx_chan->device->dev, dma_dst, len, DMA_FROM_DEVICE); + dma_unmap_single(rx_dev, dma_dst, len, DMA_FROM_DEVICE); return ret; } diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index d53b35a8b3cb..88f22fa4704e 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -3229,7 +3229,7 @@ static void cadence_nand_remove(struct cdns_nand_ctrl *cdns_ctrl) { cadence_nand_chips_cleanup(cdns_ctrl); if (cdns_ctrl->dmac) - dma_unmap_resource(cdns_ctrl->dmac->device->dev, + dma_unmap_resource(dmaengine_get_dma_device(cdns_ctrl->dmac), cdns_ctrl->io.iova_dma, cdns_ctrl->io.size, DMA_BIDIRECTIONAL, 0); cadence_nand_irq_cleanup(cdns_ctrl->irq, cdns_ctrl); diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c index 01cefdaf115d..0a4f5ff11133 100644 --- a/drivers/mtd/nand/raw/intel-nand-controller.c +++ b/drivers/mtd/nand/raw/intel-nand-controller.c @@ -290,6 +290,7 @@ static int ebu_dma_start(struct ebu_nand_controller *ebu_host, u32 dir, struct dma_async_tx_descriptor *tx; struct completion *dma_completion; dma_async_tx_callback callback; + struct device *dma_dev; struct dma_chan *chan; dma_cookie_t cookie; unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; @@ -307,8 +308,9 @@ static int ebu_dma_start(struct ebu_nand_controller *ebu_host, u32 dir, callback = ebu_dma_tx_callback; } - buf_dma = dma_map_single(chan->device->dev, (void *)buf, len, dir); - if (dma_mapping_error(chan->device->dev, buf_dma)) { + dma_dev = dmaengine_get_dma_device(chan); + buf_dma = dma_map_single(dma_dev, (void *)buf, len, dir); + if (dma_mapping_error(dma_dev, buf_dma)) { dev_err(ebu_host->dev, "Failed to map DMA buffer\n"); ret = -EIO; goto err_unmap; diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c index 8490412d5be1..935f66602de9 100644 --- a/drivers/mtd/nand/raw/loongson-nand-controller.c +++ b/drivers/mtd/nand/raw/loongson-nand-controller.c @@ -391,7 +391,7 @@ static void loongson_nand_dma_callback(void *data) { struct loongson_nand_host *host = (struct loongson_nand_host *)data; struct dma_chan *chan = host->dma_chan; - struct device *dev = chan->device->dev; + struct device *dev = dmaengine_get_dma_device(chan); enum dma_status status; status = dmaengine_tx_status(chan, host->dma_cookie, NULL); @@ -407,7 +407,7 @@ static int loongson_nand_dma_transfer(struct loongson_nand_host *host, struct lo { struct nand_chip *chip = &host->chip; struct dma_chan *chan = host->dma_chan; - struct device *dev = chan->device->dev; + struct device *dev = dmaengine_get_dma_device(chan); struct dma_async_tx_descriptor *desc; enum dma_data_direction data_dir = op->is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; enum dma_transfer_direction xfer_dir = op->is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c index 8f6a89d9ba83..4400989df8a0 100644 --- a/drivers/mtd/nand/raw/lpc32xx_mlc.c +++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c @@ -394,6 +394,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, { struct nand_chip *chip = mtd_to_nand(mtd); struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + struct device *dma_dev = dmaengine_get_dma_device(host->dma_chan); struct dma_async_tx_descriptor *desc; int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; unsigned long time_left; @@ -401,8 +402,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, sg_init_one(&host->sgl, mem, len); - res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1, - DMA_BIDIRECTIONAL); + res = dma_map_sg(dma_dev, &host->sgl, 1, DMA_BIDIRECTIONAL); if (res != 1) { dev_err(mtd->dev.parent, "Failed to map sg list\n"); return -ENXIO; @@ -430,12 +430,10 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, goto out1; } - dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, - DMA_BIDIRECTIONAL); + dma_unmap_sg(dma_dev, &host->sgl, 1, DMA_BIDIRECTIONAL); return 0; out1: - dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, - DMA_BIDIRECTIONAL); + dma_unmap_sg(dma_dev, &host->sgl, 1, DMA_BIDIRECTIONAL); return res; } diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c index 10c8080207f4..bdc3957369c2 100644 --- a/drivers/mtd/nand/raw/lpc32xx_slc.c +++ b/drivers/mtd/nand/raw/lpc32xx_slc.c @@ -427,6 +427,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, { struct nand_chip *chip = mtd_to_nand(mtd); struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + struct device *dma_dev = dmaengine_get_dma_device(host->dma_chan); struct dma_async_tx_descriptor *desc; int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; int res; @@ -448,8 +449,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, sg_init_one(&host->sgl, mem, len); - res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1, - DMA_BIDIRECTIONAL); + res = dma_map_sg(dma_dev, &host->sgl, 1, DMA_BIDIRECTIONAL); if (res != 1) { dev_err(mtd->dev.parent, "Failed to map sg list\n"); return -ENXIO; @@ -477,13 +477,11 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, res = 0; } - dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, - DMA_BIDIRECTIONAL); + dma_unmap_sg(dma_dev, &host->sgl, 1, DMA_BIDIRECTIONAL); return res; out1: - dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, - DMA_BIDIRECTIONAL); + dma_unmap_sg(dma_dev, &host->sgl, 1, DMA_BIDIRECTIONAL); return -ENXIO; } diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c index 38b7eb5b992c..e5fa5540e80a 100644 --- a/drivers/mtd/nand/raw/marvell_nand.c +++ b/drivers/mtd/nand/raw/marvell_nand.c @@ -868,6 +868,7 @@ static int marvell_nfc_xfer_data_dma(struct marvell_nfc *nfc, unsigned int len) { unsigned int dma_len = min_t(int, ALIGN(len, 32), MAX_CHUNK_SIZE); + struct device *dma_dev = dmaengine_get_dma_device(nfc->dma_chan); struct dma_async_tx_descriptor *tx; struct scatterlist sg; dma_cookie_t cookie; @@ -876,7 +877,7 @@ static int marvell_nfc_xfer_data_dma(struct marvell_nfc *nfc, marvell_nfc_enable_dma(nfc); /* Prepare the DMA transfer */ sg_init_one(&sg, nfc->dma_buf, dma_len); - ret = dma_map_sg(nfc->dma_chan->device->dev, &sg, 1, direction); + ret = dma_map_sg(dma_dev, &sg, 1, direction); if (!ret) { dev_err(nfc->dev, "Could not map DMA S/G list\n"); return -ENXIO; @@ -888,7 +889,7 @@ static int marvell_nfc_xfer_data_dma(struct marvell_nfc *nfc, DMA_PREP_INTERRUPT); if (!tx) { dev_err(nfc->dev, "Could not prepare DMA S/G list\n"); - dma_unmap_sg(nfc->dma_chan->device->dev, &sg, 1, direction); + dma_unmap_sg(dma_dev, &sg, 1, direction); return -ENXIO; } @@ -900,7 +901,7 @@ static int marvell_nfc_xfer_data_dma(struct marvell_nfc *nfc, dma_async_issue_pending(nfc->dma_chan); ret = marvell_nfc_wait_cmdd(nfc->selected_chip); - dma_unmap_sg(nfc->dma_chan->device->dev, &sg, 1, direction); + dma_unmap_sg(dma_dev, &sg, 1, direction); marvell_nfc_disable_dma(nfc); if (ret) { dev_err(nfc->dev, "Timeout waiting for DMA (status: %d)\n", diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index 552f36da79fc..9f7079e36663 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -380,8 +380,8 @@ static inline int omap_nand_dma_transfer(struct nand_chip *chip, { struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); struct dma_async_tx_descriptor *tx; - enum dma_data_direction dir = is_write ? DMA_TO_DEVICE : - DMA_FROM_DEVICE; + enum dma_data_direction dir = is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + struct device *dma_dev = dmaengine_get_dma_device(info->dma); struct scatterlist sg; unsigned long tim, limit; unsigned n; @@ -392,7 +392,7 @@ static inline int omap_nand_dma_transfer(struct nand_chip *chip, goto out_copy; sg_init_one(&sg, addr, len); - n = dma_map_sg(info->dma->device->dev, &sg, 1, dir); + n = dma_map_sg(dma_dev, &sg, 1, dir); if (n == 0) { dev_err(&info->pdev->dev, "Couldn't DMA map a %d byte buffer\n", len); @@ -434,11 +434,11 @@ static inline int omap_nand_dma_transfer(struct nand_chip *chip, /* disable and stop the PFPW engine */ omap_prefetch_reset(info->gpmc_cs, info); - dma_unmap_sg(info->dma->device->dev, &sg, 1, dir); + dma_unmap_sg(dma_dev, &sg, 1, dir); return 0; out_copy_unmap: - dma_unmap_sg(info->dma->device->dev, &sg, 1, dir); + dma_unmap_sg(dma_dev, &sg, 1, dir); out_copy: is_write == 0 ? omap_nand_data_in(chip, (void *)addr, len, false) : omap_nand_data_out(chip, addr, len, false); diff --git a/drivers/mtd/nand/raw/sh_flctl.c b/drivers/mtd/nand/raw/sh_flctl.c index 97f733e481ff..0497801d7059 100644 --- a/drivers/mtd/nand/raw/sh_flctl.c +++ b/drivers/mtd/nand/raw/sh_flctl.c @@ -378,6 +378,7 @@ static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, int len, enum dma_data_direction dir) { struct dma_async_tx_descriptor *desc = NULL; + struct device *dma_dev; struct dma_chan *chan; enum dma_transfer_direction tr_dir; dma_addr_t dma_addr; @@ -394,9 +395,10 @@ static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, tr_dir = DMA_MEM_TO_DEV; } - dma_addr = dma_map_single(chan->device->dev, buf, len, dir); + dma_dev = dmaengine_get_dma_device(chan); + dma_addr = dma_map_single(dma_dev, buf, len, dir); - if (!dma_mapping_error(chan->device->dev, dma_addr)) + if (!dma_mapping_error(dma_dev, dma_addr)) desc = dmaengine_prep_slave_single(chan, dma_addr, len, tr_dir, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); @@ -440,7 +442,7 @@ static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, reg &= ~DREQ0EN; writel(reg, FLINTDMACR(flctl)); - dma_unmap_single(chan->device->dev, dma_addr, len, dir); + dma_unmap_single(dma_dev, dma_addr, len, dir); /* ret == 0 is success */ return ret; From 30c70e01f79f04b802821d6de24a6480538e3909 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sat, 19 Sep 2026 21:08:06 +0800 Subject: [PATCH 132/155] mtd: cfi_cmdset_0001: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit to the assign_bit API. Signed-off-by: Peng Fan Reviewed-by: Breno Leitao Signed-off-by: Miquel Raynal --- drivers/mtd/chips/cfi_cmdset_0001.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 6049ba2d6bcb..dff29347ef40 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -2510,10 +2510,7 @@ static void cfi_intelext_save_locks(struct mtd_info *mtd) status = cfi_varsize_frob(mtd, do_getlockstatus_oneblock, adr, len, NULL); - if (status) - set_bit(block, region->lockmap); - else - clear_bit(block, region->lockmap); + assign_bit(block, region->lockmap, status); } } } From d498b2ddda99eed28cde177b1620dbcea246a1c4 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Mon, 21 Sep 2026 09:49:20 +0800 Subject: [PATCH 133/155] mtd: block: prevent reclaim I/O during request processing The blktrans request callbacks run under dev->lock and may allocate memory with GFP_KERNEL. For example, mtdblock_writesect() uses vmalloc() to allocate its eraseblock cache. Direct reclaim can then recurse into block I/O and deadlock on resources held by the request being processed. syzbot reports a circular locking dependency involving the device mutex and fs_reclaim. Commit d5ba1c8ffd0b ("mtd: don't use PF_MEMALLOC") removed PF_MEMALLOC from the MTD request thread. That flag had prevented direct reclaim, so its removal made allocations during request processing eligible for reclaim without excluding I/O. Wrap request processing in mtd_queue_rq() in a NOIO scope. This covers both the request and background callbacks, including allocations made by lower MTD drivers, and prevents reclaim from initiating filesystem or block I/O. Fixes: d5ba1c8ffd0b ("mtd: don't use PF_MEMALLOC") Assisted-by: Codex:gpt-6-astra Reported-by: syzbot+e08e0a15269eefa87790@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e08e0a15269eefa87790 Signed-off-by: Qingfang Deng Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_blkdevs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index 4d2e7b7774e9..42f250344f0c 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "mtdcore.h" @@ -167,6 +168,7 @@ static blk_status_t mtd_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { struct mtd_blktrans_dev *dev; + unsigned int noio_flags; dev = hctx->queue->queuedata; if (!dev) { @@ -174,10 +176,13 @@ static blk_status_t mtd_queue_rq(struct blk_mq_hw_ctx *hctx, return BLK_STS_IOERR; } + /* Reclaim must not recurse into I/O while processing requests. */ + noio_flags = memalloc_noio_save(); spin_lock_irq(&dev->queue_lock); list_add_tail(&bd->rq->queuelist, &dev->rq_list); mtd_blktrans_work(dev); spin_unlock_irq(&dev->queue_lock); + memalloc_noio_restore(noio_flags); return BLK_STS_OK; } From c4f64a86a28d218f3b16cd7b18691993aa60db60 Mon Sep 17 00:00:00 2001 From: Stephen Bancroft Date: Wed, 23 Sep 2026 19:26:24 +1000 Subject: [PATCH 134/155] mtd: maps: add INT0800 firmware-flash map driver Add a read-only mapping driver that binds the ACPI INT0800 "Intel 82802 firmware hub" device and exposes the system firmware flash as an MTD ROM device. On x86 machines the boot flash is decoded into the physical address space below 4 GB, so a plain ioremap() of the declared resource window is sufficient for reads - no SPI or LPC controller access is needed. The window may be larger than the real flash; undecoded holes read as 0xff. This gives userspace a clean, safe way to read firmware flash contents without flashrom or relaxed /dev/mem access - for firmware analysis, and for extracting option ROMs stored inside EFI firmware volumes (e.g. the NVIDIA VBIOS on EFI-booted Apple machines, which can then be fed to nouveau via nouveau.config=NvBios=). There is deliberately no write or erase support. Tested on a MacBookPro4,1 (ICH8M): /dev/mtd0 reads are byte-identical to a flashrom dump of the 2 MiB SST25VF016B, except for live NVRAM variable-store regions. Signed-off-by: Stephen Bancroft Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 17 ++++++ drivers/mtd/maps/Makefile | 1 + drivers/mtd/maps/int0800.c | 115 +++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 drivers/mtd/maps/int0800.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 1bb3dba2631d..649fd145e15d 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -161,6 +161,23 @@ config MTD_AMD76XROM BE VERY CAREFUL. +config MTD_INT0800 + tristate "Read-only BIOS/firmware flash via ACPI INT0800" + depends on X86 && ACPI + select MTD_ROM + help + Support for reading the system firmware (BIOS/EFI) flash chip + through the memory window described by the ACPI INT0800 + "82802 firmware hub" device, present on most x86 machines. + + The flash is exposed read-only via the ROM chip driver, e.g. + for firmware analysis or extracting option ROMs (such as + video BIOS images embedded in EFI firmware volumes). There is + no write or erase support. + + To compile this driver as a module, choose M here: the + module will be called int0800. + config MTD_ICHXROM tristate "BIOS flash chip on Intel Controller Hub 2/3/4/5" depends on X86 && MTD_JEDECPROBE diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 01745eca1f73..2f1a7375e13c 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_MTD_L440GX) += l440gx.o obj-$(CONFIG_MTD_AMD76XROM) += amd76xrom.o obj-$(CONFIG_MTD_ESB2ROM) += esb2rom.o obj-$(CONFIG_MTD_ICHXROM) += ichxrom.o +obj-$(CONFIG_MTD_INT0800) += int0800.o obj-$(CONFIG_MTD_CK804XROM) += ck804xrom.o obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o diff --git a/drivers/mtd/maps/int0800.c b/drivers/mtd/maps/int0800.c new file mode 100644 index 000000000000..c5f108aa3479 --- /dev/null +++ b/drivers/mtd/maps/int0800.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Read-only MTD access to the system firmware flash behind the ACPI + * INT0800 "Intel 82802 firmware hub" device. + * + * On x86 systems the boot flash is decoded into the physical address + * space below 4 GB, so a plain ioremap() is sufficient to read it - + * no SPI or LPC controller access is required. The declared _CRS + * window may be larger than the real flash (the whole top-16MiB + * decode range is commonly claimed); undecoded holes read as 0xff. + * + * The device is exposed read-only via the ROM chip driver; there is + * deliberately no write or erase support. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* top-of-4GB firmware decode, used when _CRS reports no window */ +#define INT0800_DEFAULT_PHYS 0xffe00000UL +#define INT0800_DEFAULT_SIZE SZ_2M + +struct int0800 { + struct map_info map; + struct mtd_info *mtd; +}; + +static int int0800_probe(struct platform_device *pdev) +{ + struct resource *res; + struct int0800 *fw; + resource_size_t end; + int ret; + + fw = devm_kzalloc(&pdev->dev, sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; + + fw->map.name = dev_name(&pdev->dev); + fw->map.bankwidth = 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + fw->map.phys = res->start; + fw->map.size = resource_size(res); + } else { + fw->map.phys = INT0800_DEFAULT_PHYS; + fw->map.size = INT0800_DEFAULT_SIZE; + } + + /* + * Plain ioremap on purpose: the window is already claimed by the + * ACPI/pnp resource reservation, so devm_ioremap_resource() would + * fail with -EBUSY. + */ + fw->map.virt = devm_ioremap(&pdev->dev, fw->map.phys, fw->map.size); + if (!fw->map.virt) + return -ENOMEM; + + simple_map_init(&fw->map); + fw->mtd = do_map_probe("map_rom", &fw->map); + if (!fw->mtd) + return -ENODEV; + fw->mtd->dev.parent = &pdev->dev; + platform_set_drvdata(pdev, fw); + + end = fw->map.phys + fw->map.size - 1; + dev_info(&pdev->dev, "mapped firmware window %pa-%pa\n", + &fw->map.phys, &end); + + ret = mtd_device_register(fw->mtd, NULL, 0); + if (ret) + map_destroy(fw->mtd); + return ret; +} + +static void int0800_remove(struct platform_device *pdev) +{ + struct int0800 *fw = platform_get_drvdata(pdev); + + mtd_device_unregister(fw->mtd); + map_destroy(fw->mtd); +} + +static const struct acpi_device_id int0800_ids[] = { + { "INT0800", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, int0800_ids); + +static struct platform_driver int0800_driver = { + .probe = int0800_probe, + .remove = int0800_remove, + .driver = { + .name = "int0800", + .acpi_match_table = int0800_ids, + /* + * remove() frees the mtd_info and unmaps the window even + * while /dev/mtdX fds are still open; the only safe + * teardown path is module unload, which open fds block + * via mtd->owner. + */ + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(int0800_driver); + +MODULE_AUTHOR("Stephen Bancroft "); +MODULE_DESCRIPTION("Read-only MTD map over the INT0800 firmware flash window"); +MODULE_LICENSE("GPL"); From 2e566a6830b82d95837c4d669c63a0c1abf766a9 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Thu, 24 Sep 2026 14:14:11 -0700 Subject: [PATCH 135/155] mtd: concat: use container_of_const() in CONCAT() CONCAT() currently recovers the enclosing struct mtd_concat by casting its embedded struct mtd_info pointer. This assumes mtd remains the first member of the structure. Use container_of_const() with the named member to make the relationship explicit and preserve any const qualifier. Suggested-by: Luca Ceresoli Signed-off-by: Harshit Mogalapalli Signed-off-by: Miquel Raynal --- drivers/mtd/mtdconcat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index c97167d51fe2..1585d31a507d 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -31,7 +31,7 @@ * Given a pointer to the MTD object in the mtd_concat structure, * we can retrieve the pointer to that structure with this macro. */ -#define CONCAT(x) ((struct mtd_concat *)(x)) +#define CONCAT(x) container_of_const(x, struct mtd_concat, mtd) /* * MTD methods which look up the relevant subdevice, translate the From 532caaa2f445b22b74b88b4023a810426772db54 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Fri, 25 Sep 2026 05:55:44 -0700 Subject: [PATCH 136/155] mtd: virt-concat: unlink concat node before freeing it mtd_virt_concat_create_item() links each new mtd_virt_concat_node to concat_node_list. mtd_virt_concat_destroy() later frees the node after destroying its concatenated MTD device, but fails to unlink it. A later list traversal dereferences the freed node, causing a use-after-free. Remove the node from concat_node_list before freeing it. Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6, smatch Signed-off-by: Harshit Mogalapalli Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index da4277ced4d6..4fad0967a96e 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -130,6 +130,7 @@ int mtd_virt_concat_destroy(struct mtd_info *mtd) mtd_concat_destroy(&concat->mtd); } + list_del(&item->head); for (idx = 0; idx < item->count; idx++) of_node_put(item->nodes[idx]); From 112a666bd82e96e4a01e0cd8a0fb9c88dd0bce38 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Fri, 25 Sep 2026 05:55:45 -0700 Subject: [PATCH 137/155] mtd: virt-concat: unlink discarded items before freeing mtd_virt_concat_destroy_items() frees each item without removing it from concat_node_list. list_for_each_entry_safe() protects the current traversal, but concat_node_list still points to the freed items afterward. The function also unwinds errors from mtd_virt_concat_node_create(). A later initialization can traverse a freed item in mtd_is_part_concat(), causing a use-after-free. Unlink each item before freeing it. Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6, smatch Signed-off-by: Harshit Mogalapalli Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index 4fad0967a96e..637219e20b80 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -202,6 +202,7 @@ void mtd_virt_concat_destroy_items(void) int i; list_for_each_entry_safe(item, temp, &concat_node_list, head) { + list_del(&item->head); for (i = 0; i < item->count; i++) of_node_put(item->nodes[i]); From 8e301fd598806b9480b00a2c16314140de7c4db6 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 25 Sep 2026 11:30:44 -0500 Subject: [PATCH 138/155] dt-bindings: mtd: arasan: Add "power-domains" for ZynqMP ZynqMP NAND controller nodes use "power-domains" property already, so add it to the schema. As ZynqMP is the only supported platform currently, no need for a conditional schema. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Signed-off-by: Miquel Raynal --- .../devicetree/bindings/mtd/arasan,nand-controller.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/mtd/arasan,nand-controller.yaml b/Documentation/devicetree/bindings/mtd/arasan,nand-controller.yaml index b90d3b48c2f2..7154245b00e3 100644 --- a/Documentation/devicetree/bindings/mtd/arasan,nand-controller.yaml +++ b/Documentation/devicetree/bindings/mtd/arasan,nand-controller.yaml @@ -35,6 +35,9 @@ properties: interrupts: maxItems: 1 + power-domains: + maxItems: 1 + required: - compatible - reg From 19961db84d806c8a7f6cbe457e752ae62bf65ee4 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:10 +0200 Subject: [PATCH 139/155] mtd: rawnand: qcom: make check_flash_errors() more generic The function gets a 'qcom_nand_host' pointer as a parameter but it is only used to extract the nand controller's private structure from that. In order to make the function more generic, change the declaration to get a 'qcom_nandc_controller' pointer directly, and update the callers accordingly. While at it, also change the function name to use a 'qcom_nandc' prefix. No functional changes. This is in preparation of moving the function into the 'qpic_common' code to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index d7642db2e2df..12bfb14cd9a3 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -449,10 +449,9 @@ struct read_stats { }; /* reads back FLASH_STATUS register set by the controller */ -static int check_flash_errors(struct qcom_nand_host *host, int cw_cnt) +static int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, + int cw_cnt) { - struct nand_chip *chip = &host->chip; - struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); int i; qcom_nandc_dev_to_mem(nandc, true); @@ -538,7 +537,7 @@ qcom_nandc_read_cw_raw(struct mtd_info *mtd, struct nand_chip *chip, return ret; } - return check_flash_errors(host, 1); + return qcom_nandc_check_flash_errors(nandc, 1); } /* @@ -1181,7 +1180,7 @@ static int qcom_nandc_block_bad(struct nand_chip *chip, loff_t ofs) if (ret) goto err; - if (check_flash_errors(host, 1)) { + if (qcom_nandc_check_flash_errors(nandc, 1)) { dev_warn(nandc->dev, "error when trying to read BBM\n"); goto err; } From 5c80d1392cb6d3f8f78c3c8e6b80c884d07e47db Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:11 +0200 Subject: [PATCH 140/155] mtd: rawnand: qcom: make config_nand_page_read() more generic The function gets a 'nand_chip' pointer as a parameter but it is only used to extract a pointer to the nand controller's private structure from that. In order to make the function more generic, change the declaration to get a 'qcom_nandc_controller' pointer directly, and update the callers accordingly. While at it, also change the function name to use a 'qcom_nandc' prefix. No functional changes intended. This is in preparation of moving the function into the 'qpic_common' code to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 12bfb14cd9a3..c65a8416c3f5 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -305,10 +305,8 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i * Helper to prepare DMA descriptors for configuring registers * before reading a NAND page. */ -static void config_nand_page_read(struct nand_chip *chip) +static void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc) { - struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); - qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); if (!nandc->props->qpic_version2) @@ -357,7 +355,9 @@ static void config_nand_single_cw_page_read(struct nand_chip *chip, bool use_ecc, int cw) { - config_nand_page_read(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + + qcom_nandc_config_page_read(nandc); config_nand_cw_read(chip, use_ecc, cw); } @@ -490,7 +490,7 @@ qcom_nandc_read_cw_raw(struct mtd_info *mtd, struct nand_chip *chip, qcom_clear_bam_transaction(nandc); set_address(host, host->cw_size * cw, page); update_rw_regs(host, 1, true, raw_cw); - config_nand_page_read(chip); + qcom_nandc_config_page_read(nandc); data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1); oob_size1 = host->bbm_size; @@ -727,7 +727,7 @@ static int read_page_ecc(struct qcom_nand_host *host, u8 *data_buf, u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf; int i, ret; - config_nand_page_read(chip); + qcom_nandc_config_page_read(nandc); /* queue cmd descs for each codeword */ for (i = 0; i < ecc->steps; i++) { From ee88ebffcd2abf7730e2e9ff638de5c04f3d19c6 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:12 +0200 Subject: [PATCH 141/155] mtd: rawnand: qcom: make config_nand_page_write() more generic The function gets a 'nand_chip' pointer as a parameter but it is only used to extract a pointer to the nand controller's private structure from that. In order to make the function more generic, change the declaration to get a 'qcom_nandc_controller' pointer directly, and update the callers accordingly. While at it, also change the function name to use a 'qcom_nandc' prefix. No functional changes intended. This is in preparation of moving the function into the 'qpic_common' code to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index c65a8416c3f5..1f3c00823309 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -365,10 +365,8 @@ config_nand_single_cw_page_read(struct nand_chip *chip, * Helper to prepare DMA descriptors used to configure registers needed for * before writing a NAND page. */ -static void config_nand_page_write(struct nand_chip *chip) +static void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc) { - struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); - qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); if (!nandc->props->qpic_version2) @@ -989,7 +987,7 @@ static int qcom_nandc_write_page(struct nand_chip *chip, const u8 *buf, host->use_ecc = true; update_rw_regs(host, ecc->steps, false, 0); - config_nand_page_write(chip); + qcom_nandc_config_page_write(nandc); for (i = 0; i < ecc->steps; i++) { int data_size, oob_size; @@ -1059,7 +1057,7 @@ static int qcom_nandc_write_page_raw(struct nand_chip *chip, host->use_ecc = false; update_rw_regs(host, ecc->steps, false, 0); - config_nand_page_write(chip); + qcom_nandc_config_page_write(nandc); for (i = 0; i < ecc->steps; i++) { int data_size1, data_size2, oob_size1, oob_size2; @@ -1143,7 +1141,7 @@ static int qcom_nandc_write_oob(struct nand_chip *chip, int page) set_address(host, host->cw_size * (ecc->steps - 1), page); update_rw_regs(host, 1, false, 0); - config_nand_page_write(chip); + qcom_nandc_config_page_write(nandc); qcom_write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, data_size + oob_size, 0); config_nand_cw_write(chip); @@ -1219,7 +1217,7 @@ static int qcom_nandc_block_markbad(struct nand_chip *chip, loff_t ofs) set_address(host, host->cw_size * (ecc->steps - 1), page); update_rw_regs(host, 1, false, ecc->steps - 1); - config_nand_page_write(chip); + qcom_nandc_config_page_write(nandc); qcom_write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, host->cw_size, 0); config_nand_cw_write(chip); From e4ff0d7c53a981690ae31d49f59efcbfeeef395f Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:13 +0200 Subject: [PATCH 142/155] mtd: rawnand: qcom: make config_nand_cw_write() more generic The function gets a 'nand_chip' pointer as a parameter but it is only used to extract a pointer to the nand controller's private structure from that. In order to make the function more generic, change the declaration to get a 'qcom_nandc_controller' pointer directly, and update the callers accordingly. While at it, also change the function name to use a 'qcom_nandc' prefix. No functional changes intended. This is in preparation of moving the function into the 'qpic_common' code to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 1f3c00823309..99fbf5458083 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -378,10 +378,8 @@ static void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc) * Helper to prepare DMA descriptors for configuring registers * before writing each codeword in NAND page. */ -static void config_nand_cw_write(struct nand_chip *chip) +static void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc) { - struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); - qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); @@ -1018,7 +1016,7 @@ static int qcom_nandc_write_page(struct nand_chip *chip, const u8 *buf, oob_buf, oob_size, 0); } - config_nand_cw_write(chip); + qcom_nandc_config_cw_write(nandc); data_buf += data_size; oob_buf += oob_size; @@ -1094,7 +1092,7 @@ static int qcom_nandc_write_page_raw(struct nand_chip *chip, qcom_write_data_dma(nandc, reg_off, oob_buf, oob_size2, 0); oob_buf += oob_size2; - config_nand_cw_write(chip); + qcom_nandc_config_cw_write(nandc); } ret = qcom_submit_descs(nandc); @@ -1144,7 +1142,7 @@ static int qcom_nandc_write_oob(struct nand_chip *chip, int page) qcom_nandc_config_page_write(nandc); qcom_write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, data_size + oob_size, 0); - config_nand_cw_write(chip); + qcom_nandc_config_cw_write(nandc); ret = qcom_submit_descs(nandc); if (ret) { @@ -1220,7 +1218,7 @@ static int qcom_nandc_block_markbad(struct nand_chip *chip, loff_t ofs) qcom_nandc_config_page_write(nandc); qcom_write_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, host->cw_size, 0); - config_nand_cw_write(chip); + qcom_nandc_config_cw_write(nandc); ret = qcom_submit_descs(nandc); if (ret) { From 137b9f25879245ad9825dbb6dff548b124b18b98 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:14 +0200 Subject: [PATCH 143/155] mtd: rawnand: qcom: make nandc_set_read_loc_first() more generic The function gets a 'nand_chip' pointer as a parameter but it is only used to extract a pointer to the nand controller's private structure from that. In order to make the function more generic, change the declaration to get a 'qcom_nandc_controller' pointer directly, and update the callers accordingly. While at it, also change the function name to use a 'qcom_nandc' prefix. No functional changes intended. This is in preparation of moving the function into the 'qpic_common' code to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 99fbf5458083..96a1a5a03de8 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -150,8 +150,8 @@ static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw) } /** - * nandc_set_read_loc_first() - to set read location first register - * @chip: NAND Private Flash Chip Data + * qcom_nandc_set_read_loc_first() - to set read location first register + * @nandc: NAND controller's private data * @reg_base: location register base * @cw_offset: code word offset * @read_size: code word read length @@ -159,11 +159,10 @@ static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw) * * This function will set location register value */ -static void nandc_set_read_loc_first(struct nand_chip *chip, - int reg_base, u32 cw_offset, - u32 read_size, u32 is_last_read_loc) +static void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) { - struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); __le32 locreg_val; u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | @@ -230,8 +229,9 @@ static void nandc_set_read_loc(struct nand_chip *chip, int cw, int reg, return nandc_set_read_loc_last(chip, reg_base, cw_offset, read_size, is_last_read_loc); else - return nandc_set_read_loc_first(chip, reg_base, cw_offset, - read_size, is_last_read_loc); + return qcom_nandc_set_read_loc_first(nandc, reg_base, cw_offset, + read_size, + is_last_read_loc); } /* helper to configure address register values */ @@ -1918,7 +1918,7 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_ if (nandc->props->qpic_version2) nandc_set_read_loc_last(chip, reg_base, 0, len, 1); else - nandc_set_read_loc_first(chip, reg_base, 0, len, 1); + qcom_nandc_set_read_loc_first(nandc, reg_base, 0, len, 1); if (!nandc->props->has_onfi_read_op) { qcom_write_reg_dma(nandc, &nandc->regs->vld, NAND_DEV_CMD_VLD, 1, 0); From cd93990ef5d5262e50b2a0d306a49e1a21019ac5 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:15 +0200 Subject: [PATCH 144/155] mtd: rawnand: qcom: make nandc_set_read_loc_last() more generic The function gets a 'nand_chip' pointer as a parameter but it is only used to extract a pointer to the nand controller's private structure from that. In order to make the function more generic, change the declaration to get a 'qcom_nandc_controller' pointer directly, and update the callers accordingly. While at it, also change the function name to use a 'qcom_nandc' prefix. No functional changes intended. This is in preparation of moving the function into the 'qpic_common' code to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 96a1a5a03de8..cc1e64ac4245 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -181,8 +181,8 @@ static void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, } /** - * nandc_set_read_loc_last - to set read location last register - * @chip: NAND Private Flash Chip Data + * qcom_nandc_set_read_loc_last - to set read location last register + * @nandc: NAND controller's private data * @reg_base: location register base * @cw_offset: code word offset * @read_size: code word read length @@ -190,11 +190,10 @@ static void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, * * This function will set location last register value */ -static void nandc_set_read_loc_last(struct nand_chip *chip, - int reg_base, u32 cw_offset, - u32 read_size, u32 is_last_read_loc) +static void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) { - struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); __le32 locreg_val; u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | @@ -226,8 +225,9 @@ static void nandc_set_read_loc(struct nand_chip *chip, int cw, int reg, reg_base += reg * 4; if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw)) - return nandc_set_read_loc_last(chip, reg_base, cw_offset, - read_size, is_last_read_loc); + return qcom_nandc_set_read_loc_last(nandc, reg_base, cw_offset, + read_size, + is_last_read_loc); else return qcom_nandc_set_read_loc_first(nandc, reg_base, cw_offset, read_size, @@ -1916,7 +1916,7 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_ len = nand_subop_get_data_len(subop, op_id); if (nandc->props->qpic_version2) - nandc_set_read_loc_last(chip, reg_base, 0, len, 1); + qcom_nandc_set_read_loc_last(nandc, reg_base, 0, len, 1); else qcom_nandc_set_read_loc_first(nandc, reg_base, 0, len, 1); From 555d48e52026f3e60f0b9acd1ee08c106d442a02 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:16 +0200 Subject: [PATCH 145/155] mtd: rawnand: qcom: rename 'nand_stats' structure Give 'read_stats' structure a 'qcom_nandc' prefix to indicate that it corresponds to the Qualcomm NAND driver. The definition is going to be moved into a common header this helps to avoid global namespace pollution/collision. No functional changes. This is in preparation of moving the declaration into the 'nand-qpic-common.h' header to allow reusing it from the 'spi-qpic-snand' driver. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index cc1e64ac4245..99fe2c434ce7 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -438,7 +438,7 @@ static bool erased_chunk_check_and_fixup(u8 *data_buf, int data_len) return true; } -struct read_stats { +struct qcom_nandc_read_stats { __le32 flash; __le32 buffer; __le32 erased_cw; @@ -619,12 +619,12 @@ static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf, struct mtd_info *mtd = nand_to_mtd(chip); struct nand_ecc_ctrl *ecc = &chip->ecc; unsigned int max_bitflips = 0, uncorrectable_cws = 0; - struct read_stats *buf; + struct qcom_nandc_read_stats *buf; bool flash_op_err = false, erased; int i; u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf; - buf = (struct read_stats *)nandc->reg_read_buf; + buf = (struct qcom_nandc_read_stats *)nandc->reg_read_buf; qcom_nandc_dev_to_mem(nandc, true); for (i = 0; i < ecc->steps; i++, buf++) { From cfae2182fa7b0f0e3caee02fa6acf5dc77410efc Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 25 Sep 2026 21:06:17 +0200 Subject: [PATCH 146/155] mtd: rawnand: qcom: move common code to the 'qpic_common' module The 'qpic_common' module provides some generic functions which are usable by both the 'qcom_nandc' and by the 'spi-qpic-snand' driver. Move more code from the 'qcom_nandc' driver to there in order to allow using those also from the 'spi-qpic-snand' driver. No functional changes. Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/qpic_common.c | 131 +++++++++++++++++++++++++++ drivers/mtd/nand/raw/qcom_nandc.c | 131 --------------------------- include/linux/mtd/nand-qpic-common.h | 17 ++++ 3 files changed, 148 insertions(+), 131 deletions(-) diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c index 4f3e4dd766da..91ae970734f8 100644 --- a/drivers/mtd/nand/qpic_common.c +++ b/drivers/mtd/nand/qpic_common.c @@ -775,5 +775,136 @@ int qcom_nandc_alloc(struct qcom_nand_controller *nandc) } EXPORT_SYMBOL(qcom_nandc_alloc); +/* reads back FLASH_STATUS register set by the controller */ +int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, + int cw_cnt) +{ + int i; + + qcom_nandc_dev_to_mem(nandc, true); + + for (i = 0; i < cw_cnt; i++) { + u32 flash = le32_to_cpu(nandc->reg_read_buf[i]); + + if (flash & (FS_OP_ERR | FS_MPU_ERR)) + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL(qcom_nandc_check_flash_errors); + +/* + * Helper to prepare DMA descriptors for configuring registers + * before reading a NAND page. + */ +void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc) +{ + qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + if (!nandc->props->qpic_version2) + qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr, + NAND_ERASED_CW_DETECT_CFG, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set, + NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); +} +EXPORT_SYMBOL(qcom_nandc_config_page_read); + +/* + * Helper to prepare DMA descriptors used to configure registers needed for + * before writing a NAND page. + */ +void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc) +{ + qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + if (!nandc->props->qpic_version2) + qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, + NAND_BAM_NEXT_SGL); +} +EXPORT_SYMBOL(qcom_nandc_config_page_write); + +/* + * Helper to prepare DMA descriptors for configuring registers + * before writing each codeword in NAND page. + */ +void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc) +{ + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); + + qcom_write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, + NAND_BAM_NEXT_SGL); +} +EXPORT_SYMBOL(qcom_nandc_config_cw_write); + +/** + * qcom_nandc_set_read_loc_first() - to set read location first register + * @nandc: NAND controller's private data + * @reg_base: location register base + * @cw_offset: code word offset + * @read_size: code word read length + * @is_last_read_loc: is this the last read location + * + * This function will set location register value + */ +void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) +{ + __le32 locreg_val; + u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | + FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | + FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); + + locreg_val = cpu_to_le32(val); + + if (reg_base == NAND_READ_LOCATION_0) + nandc->regs->read_location0 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_1) + nandc->regs->read_location1 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_2) + nandc->regs->read_location2 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_3) + nandc->regs->read_location3 = locreg_val; +} +EXPORT_SYMBOL(qcom_nandc_set_read_loc_first); + +/** + * qcom_nandc_set_read_loc_last - to set read location last register + * @nandc: NAND controller's private data + * @reg_base: location register base + * @cw_offset: code word offset + * @read_size: code word read length + * @is_last_read_loc: is this the last read location + * + * This function will set location last register value + */ +void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) +{ + __le32 locreg_val; + u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | + FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | + FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); + + locreg_val = cpu_to_le32(val); + + if (reg_base == NAND_READ_LOCATION_LAST_CW_0) + nandc->regs->read_location_last0 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_1) + nandc->regs->read_location_last1 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_2) + nandc->regs->read_location_last2 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_3) + nandc->regs->read_location_last3 = locreg_val; +} +EXPORT_SYMBOL(qcom_nandc_set_read_loc_last); + MODULE_DESCRIPTION("QPIC controller common api"); MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 99fe2c434ce7..d65c0101f471 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -149,68 +149,6 @@ static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw) return cw == (ecc->steps - 1); } -/** - * qcom_nandc_set_read_loc_first() - to set read location first register - * @nandc: NAND controller's private data - * @reg_base: location register base - * @cw_offset: code word offset - * @read_size: code word read length - * @is_last_read_loc: is this the last read location - * - * This function will set location register value - */ -static void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, - int reg_base, u32 cw_offset, - u32 read_size, u32 is_last_read_loc) -{ - __le32 locreg_val; - u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | - FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | - FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); - - locreg_val = cpu_to_le32(val); - - if (reg_base == NAND_READ_LOCATION_0) - nandc->regs->read_location0 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_1) - nandc->regs->read_location1 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_2) - nandc->regs->read_location2 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_3) - nandc->regs->read_location3 = locreg_val; -} - -/** - * qcom_nandc_set_read_loc_last - to set read location last register - * @nandc: NAND controller's private data - * @reg_base: location register base - * @cw_offset: code word offset - * @read_size: code word read length - * @is_last_read_loc: is this the last read location - * - * This function will set location last register value - */ -static void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, - int reg_base, u32 cw_offset, - u32 read_size, u32 is_last_read_loc) -{ - __le32 locreg_val; - u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | - FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | - FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); - - locreg_val = cpu_to_le32(val); - - if (reg_base == NAND_READ_LOCATION_LAST_CW_0) - nandc->regs->read_location_last0 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_LAST_CW_1) - nandc->regs->read_location_last1 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_LAST_CW_2) - nandc->regs->read_location_last2 = locreg_val; - else if (reg_base == NAND_READ_LOCATION_LAST_CW_3) - nandc->regs->read_location_last3 = locreg_val; -} - /* helper to configure location register values */ static void nandc_set_read_loc(struct nand_chip *chip, int cw, int reg, u32 cw_offset, u32 read_size, u32 is_last_read_loc) @@ -301,22 +239,6 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i host->cw_data : host->cw_size, 1); } -/* - * Helper to prepare DMA descriptors for configuring registers - * before reading a NAND page. - */ -static void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc) -{ - qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - if (!nandc->props->qpic_version2) - qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); - qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); -} - /* * Helper to prepare DMA descriptors for configuring registers * before reading each codeword in NAND page. @@ -361,35 +283,6 @@ config_nand_single_cw_page_read(struct nand_chip *chip, config_nand_cw_read(chip, use_ecc, cw); } -/* - * Helper to prepare DMA descriptors used to configure registers needed for - * before writing a NAND page. - */ -static void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc) -{ - qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - if (!nandc->props->qpic_version2) - qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, - NAND_BAM_NEXT_SGL); -} - -/* - * Helper to prepare DMA descriptors for configuring registers - * before writing each codeword in NAND page. - */ -static void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc) -{ - qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); - qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); - - qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); - - qcom_write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0); - qcom_write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, - NAND_BAM_NEXT_SGL); -} - /* * when using BCH ECC, the HW flags an error in NAND_FLASH_STATUS if it read * an erased CW, and reports an erased CW in NAND_ERASED_CW_DETECT_STATUS. @@ -438,30 +331,6 @@ static bool erased_chunk_check_and_fixup(u8 *data_buf, int data_len) return true; } -struct qcom_nandc_read_stats { - __le32 flash; - __le32 buffer; - __le32 erased_cw; -}; - -/* reads back FLASH_STATUS register set by the controller */ -static int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, - int cw_cnt) -{ - int i; - - qcom_nandc_dev_to_mem(nandc, true); - - for (i = 0; i < cw_cnt; i++) { - u32 flash = le32_to_cpu(nandc->reg_read_buf[i]); - - if (flash & (FS_OP_ERR | FS_MPU_ERR)) - return -EIO; - } - - return 0; -} - /* performs raw read for one codeword */ static int qcom_nandc_read_cw_raw(struct mtd_info *mtd, struct nand_chip *chip, diff --git a/include/linux/mtd/nand-qpic-common.h b/include/linux/mtd/nand-qpic-common.h index 0f40964eefe7..f31fd18d837f 100644 --- a/include/linux/mtd/nand-qpic-common.h +++ b/include/linux/mtd/nand-qpic-common.h @@ -457,6 +457,12 @@ struct qcom_nandc_props { bool use_codeword_fixup; }; +struct qcom_nandc_read_stats { + __le32 flash; + __le32 buffer; + __le32 erased_cw; +}; + void qcom_free_bam_transaction(struct qcom_nand_controller *nandc); struct bam_transaction *qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc); void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc); @@ -482,5 +488,16 @@ int qcom_submit_descs(struct qcom_nand_controller *nandc); void qcom_clear_read_regs(struct qcom_nand_controller *nandc); void qcom_nandc_unalloc(struct qcom_nand_controller *nandc); int qcom_nandc_alloc(struct qcom_nand_controller *nandc); +int qcom_nandc_check_flash_errors(struct qcom_nand_controller *nandc, + int cw_cnt); +void qcom_nandc_config_page_read(struct qcom_nand_controller *nandc); +void qcom_nandc_config_page_write(struct qcom_nand_controller *nandc); +void qcom_nandc_config_cw_write(struct qcom_nand_controller *nandc); +void qcom_nandc_set_read_loc_first(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc); +void qcom_nandc_set_read_loc_last(struct qcom_nand_controller *nandc, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc); #endif From 94623942a66e031826756f9c92dbde9a12e2b4d2 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Mon, 28 Sep 2026 09:01:53 +0200 Subject: [PATCH 147/155] mtd: rawnand: qcom: fix read location register usage in config_nand_cw_read() On QPIC v2 controllers, when the last codeword is being read, the read location values must be programmed into the READ_LOCATION_LAST_CW_* registers. Prior to commit 8c52932da5e6 ("mtd: rawnand: qcom: cleanup qcom_nandc driver"), the config_nand_cw_read() function did it right, but since then it always writes the read location values to the READ_LOCATION_* registers even if the actual codeword is the last one. Change the function to always configure the registers corresponding to the actual codeword to restore the correct behaviour. Cc: stable@vger.kernel.org Fixes: 8c52932da5e6 ("mtd: rawnand: qcom: cleanup qcom_nandc driver") Signed-off-by: Gabor Juhos Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index d65c0101f471..2d98f79b492d 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -248,14 +248,16 @@ config_nand_cw_read(struct nand_chip *chip, bool use_ecc, int cw) { struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); struct nand_ecc_ctrl *ecc = &chip->ecc; - __le32 *reg = &nandc->regs->read_location0; + int reg_base = NAND_READ_LOCATION_0; - if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw)) + if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw)) { reg = &nandc->regs->read_location_last0; + reg_base = NAND_READ_LOCATION_LAST_CW_0; + } if (nandc->props->supports_bam) - qcom_write_reg_dma(nandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, reg, reg_base, 4, NAND_BAM_NEXT_SGL); qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); From 104f277e8a469f71ac3c74a0e9ebb8bf64c3fa06 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 28 Sep 2026 13:07:29 -0700 Subject: [PATCH 148/155] mtd: rawnand: fsl_ifc: allocate shared ctrl with devm_kzalloc Allocate the shared fsl_ifc_nand_ctrl structure against the controller device, which outlives all NAND child devices, so it is freed automatically. This drops the manual kfree() and the broken chip counter that was decremented in remove() but never incremented anywhere, leaking the structure and leaving the freed pointer in ctrl->nand on re-probe. Assisted-by: LLM Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/fsl_ifc_nand.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c index be6bbc1c184d..cc75b6a67e07 100644 --- a/drivers/mtd/nand/raw/fsl_ifc_nand.c +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -49,7 +49,6 @@ struct fsl_ifc_nand_ctrl { unsigned int index; /* Pointer to next byte to 'read' */ unsigned int oob; /* Non zero if operating on OOB data */ unsigned int eccread; /* Non zero for a full-page ECC read */ - unsigned int counter; /* counter for the initializations */ unsigned int max_bitflips; /* Saved during READ0 cmd */ }; @@ -1023,15 +1022,13 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) mutex_lock(&fsl_ifc_nand_mutex); if (!fsl_ifc_ctrl_dev->nand) { - ifc_nand_ctrl = kzalloc_obj(*ifc_nand_ctrl); + ifc_nand_ctrl = devm_kzalloc(fsl_ifc_ctrl_dev->dev, sizeof(*ifc_nand_ctrl), + GFP_KERNEL); if (!ifc_nand_ctrl) { mutex_unlock(&fsl_ifc_nand_mutex); return -ENOMEM; } - ifc_nand_ctrl->read_bytes = 0; - ifc_nand_ctrl->index = 0; - ifc_nand_ctrl->addr = NULL; fsl_ifc_ctrl_dev->nand = ifc_nand_ctrl; nand_controller_init(&ifc_nand_ctrl->controller); @@ -1105,14 +1102,6 @@ static void fsl_ifc_nand_remove(struct platform_device *dev) nand_cleanup(chip); fsl_ifc_chip_remove(priv); - - mutex_lock(&fsl_ifc_nand_mutex); - ifc_nand_ctrl->counter--; - if (!ifc_nand_ctrl->counter) { - fsl_ifc_ctrl_dev->nand = NULL; - kfree(ifc_nand_ctrl); - } - mutex_unlock(&fsl_ifc_nand_mutex); } static const struct of_device_id fsl_ifc_nand_match[] = { From 0d1644566a1caaf71c8bf2a6689176e61749a85f Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 25 Sep 2026 18:48:02 -0700 Subject: [PATCH 149/155] dt-bindings: mtd: convert IBM NDFC binding to YAML The NDFC NAND flash controller was only described in a legacy text file, so dtbs_check reported "failed to match any schema with compatible: ['ibm,ndfc']" for the ndfc nodes of the bluestone, canyonlands, eiger, glacier, sequoia and warp device trees. Convert the binding to the YAML format so those nodes match the generic nand-controller-legacy schema. Describe the node's reg, ccr and bank-settings properties, along with its "nand" chip node which carries the legacy partition layout. The "nand" chip node carries no unit address, so the "nand@[a-f0-9]" pattern of nand-controller-legacy.yaml never matches it and raw-nand-chip.yaml is never applied. Describe it with mtd.yaml, nand-property.yaml and raw-nand-property.yaml instead, the way the controller level does, so that both the deprecated partition@ nodes and a modern "partitions" subnode are accepted. The node name does not matter, the driver picks the first child of the controller node. Assisted-by: LLM Signed-off-by: Rosen Penev Reviewed-by: Rob Herring (Arm) Signed-off-by: Miquel Raynal --- .../devicetree/bindings/mtd/ibm,ndfc.txt | 39 ------ .../devicetree/bindings/mtd/ibm,ndfc.yaml | 128 ++++++++++++++++++ 2 files changed, 128 insertions(+), 39 deletions(-) delete mode 100644 Documentation/devicetree/bindings/mtd/ibm,ndfc.txt create mode 100644 Documentation/devicetree/bindings/mtd/ibm,ndfc.yaml diff --git a/Documentation/devicetree/bindings/mtd/ibm,ndfc.txt b/Documentation/devicetree/bindings/mtd/ibm,ndfc.txt deleted file mode 100644 index 869f0b5f16e8..000000000000 --- a/Documentation/devicetree/bindings/mtd/ibm,ndfc.txt +++ /dev/null @@ -1,39 +0,0 @@ -AMCC NDFC (NanD Flash Controller) - -Required properties: -- compatible : "ibm,ndfc". -- reg : should specify chip select and size used for the chip (0x2000). - -Optional properties: -- ccr : NDFC config and control register value (default 0). -- bank-settings : NDFC bank configuration register value (default 0). - -Notes: -- partition(s) - follows the OF MTD standard for partitions - -Example: - -ndfc@1,0 { - compatible = "ibm,ndfc"; - reg = <0x00000001 0x00000000 0x00002000>; - ccr = <0x00001000>; - bank-settings = <0x80002222>; - #address-cells = <1>; - #size-cells = <1>; - - nand { - #address-cells = <1>; - #size-cells = <1>; - - partition@0 { - label = "kernel"; - reg = <0x00000000 0x00200000>; - }; - partition@200000 { - label = "root"; - reg = <0x00200000 0x03E00000>; - }; - }; -}; - - diff --git a/Documentation/devicetree/bindings/mtd/ibm,ndfc.yaml b/Documentation/devicetree/bindings/mtd/ibm,ndfc.yaml new file mode 100644 index 000000000000..d5c392001ad3 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/ibm,ndfc.yaml @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/ibm,ndfc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: IBM/AMCC NDFC (NAND Flash Controller) + +maintainers: + - Miquel Raynal + - Richard Weinberger + +description: | + The NDFC is the NAND flash controller integrated into the IBM/AMCC + PowerPC 44x SoCs. + +allOf: + - $ref: nand-controller-legacy.yaml# + +properties: + compatible: + const: ibm,ndfc + + reg: + description: Chip select and size used for the chip. + maxItems: 1 + + ccr: + description: NDFC config and control register value. + $ref: /schemas/types.yaml#/definitions/uint32 + + bank-settings: + description: NDFC bank configuration register value. + $ref: /schemas/types.yaml#/definitions/uint32 + + nand: + type: object + description: | + NAND chip attached to the controller. The driver picks the first + child of the controller node, so this node is not required to be + named "nand@". + + allOf: + - $ref: mtd.yaml# + - $ref: nand-property.yaml# + - $ref: raw-nand-property.yaml# + + properties: + "#address-cells": + const: 1 + "#size-cells": + const: 1 + + patternProperties: + "^partition@[0-9a-f]+$": + $ref: /schemas/mtd/partitions/partition.yaml#/$defs/partition-node + deprecated: true + + unevaluatedProperties: false + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + ebc { + #address-cells = <2>; + #size-cells = <1>; + + ndfc@1,0 { + compatible = "ibm,ndfc"; + reg = <0x00000001 0x00000000 0x00002000>; + ccr = <0x00001000>; + bank-settings = <0x80002222>; + #address-cells = <1>; + #size-cells = <1>; + + nand { + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "kernel"; + reg = <0x00000000 0x00200000>; + }; + partition@200000 { + label = "root"; + reg = <0x00200000 0x03e00000>; + }; + }; + }; + }; + - | + ebc { + #address-cells = <2>; + #size-cells = <1>; + + ndfc@1,0 { + compatible = "ibm,ndfc"; + reg = <0x00000001 0x00000000 0x00002000>; + #address-cells = <1>; + #size-cells = <1>; + + nand { + #address-cells = <1>; + #size-cells = <1>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "kernel"; + reg = <0x00000000 0x00200000>; + }; + partition@200000 { + label = "root"; + reg = <0x00200000 0x03e00000>; + }; + }; + }; + }; + }; +... From 8bef0196a4e21d4415091de2b6c498320e03ada2 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 2 Oct 2026 16:16:07 -0400 Subject: [PATCH 150/155] mtd: nand: use dmaengine_submit() instead of ->tx_submit() Replace direct dma_async_tx_descriptor->tx_submit() calls with the proper dmaengine_submit() API wrapper. Assisted-by: LLM Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/onenand_omap2.c | 4 ++-- drivers/mtd/nand/raw/fsmc_nand.c | 2 +- drivers/mtd/nand/raw/intel-nand-controller.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c index 0793251ada3b..644498187d8f 100644 --- a/drivers/mtd/nand/onenand/onenand_omap2.c +++ b/drivers/mtd/nand/onenand/onenand_omap2.c @@ -340,9 +340,9 @@ static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, tx->callback = omap2_onenand_dma_complete_func; tx->callback_param = &c->dma_done; - cookie = tx->tx_submit(tx); + cookie = dmaengine_submit(tx); if (dma_submit_error(cookie)) { - dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n"); + dev_err(&c->pdev->dev, "Failed to do dmaengine_submit\n"); return -EIO; } diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c index b13b2b0c3f30..0b1817cdfc5a 100644 --- a/drivers/mtd/nand/raw/fsmc_nand.c +++ b/drivers/mtd/nand/raw/fsmc_nand.c @@ -524,7 +524,7 @@ static int dma_xfer(struct fsmc_nand_data *host, void *buffer, int len, tx->callback = dma_complete; tx->callback_param = host; - cookie = tx->tx_submit(tx); + cookie = dmaengine_submit(tx); ret = dma_submit_error(cookie); if (ret) { diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c index 01cefdaf115d..477a427dac1f 100644 --- a/drivers/mtd/nand/raw/intel-nand-controller.c +++ b/drivers/mtd/nand/raw/intel-nand-controller.c @@ -322,7 +322,7 @@ static int ebu_dma_start(struct ebu_nand_controller *ebu_host, u32 dir, tx->callback = callback; tx->callback_param = ebu_host; - cookie = tx->tx_submit(tx); + cookie = dmaengine_submit(tx); ret = dma_submit_error(cookie); if (ret) { From 1ce45f0e74d60e8279ed380c97047e6c351aee1b Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 14:06:10 +0800 Subject: [PATCH 151/155] mtd: rawnand: pl353: fix clk reference leak in pl35x_nfc_setup_interface() pl35x_nfc_setup_interface() obtains a reference to the SMC "memclk" with of_clk_get_by_name() and only uses it to read the clock rate. The reference is never released: the function has many early return paths and no clk_put() call anywhere, so the clock reference is leaked. The hook is invoked repeatedly by the NAND core (nand_reset(), nand_setup_interface() and the NAND_DATA_IFACE_CHECK_ONLY mode probing loops), so the reference count keeps growing. Release the clock reference with clk_put() as soon as its rate has been read. Fixes: 08d8c62164a3 ("mtd: rawnand: pl353: Add support for the ARM PL353 SMC NAND controller") Assisted-by: DeepSeek-V4.1-Flash Signed-off-by: Haotian Zhang Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/pl35x-nand-controller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c index bd89aaadd1b2..26b61d856143 100644 --- a/drivers/mtd/nand/raw/pl35x-nand-controller.c +++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c @@ -807,6 +807,7 @@ static int pl35x_nfc_setup_interface(struct nand_chip *chip, int cs, * macro to convert from one to the other. */ period_ns = NSEC_PER_SEC / clk_get_rate(mclk); + clk_put(mclk); /* * PL35X SMC needs one extra read cycle in SDR Mode 5. This is not From 30daaf3e7897b4a8d9b72e5b0c86dd6e2a4d7d85 Mon Sep 17 00:00:00 2001 From: Navid Ghahremani Date: Sat, 10 Oct 2026 13:59:25 +0100 Subject: [PATCH 152/155] mtd: spinand: heyangtek: Reserve both bad block marker bytes Keep the two bytes read by the SPI-NAND bad-block check out of the free OOB region. This is a separate MTD fix, compiled but not exercised through OOB writes on hardware. Assisted-by: LLM Signed-off-by: Navid Ghahremani Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/heyangtek.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/spi/heyangtek.c b/drivers/mtd/nand/spi/heyangtek.c index 7fc50fd3de08..76d97ae9702c 100644 --- a/drivers/mtd/nand/spi/heyangtek.c +++ b/drivers/mtd/nand/spi/heyangtek.c @@ -35,8 +35,8 @@ static SPINAND_OP_VARIANTS(update_cache_variants, /* * HYF1GQ4UDACAE is a GD5F1GQ4-compatible die, so the OOB layout is taken * from gd5fxgq4xa: the on-die ECC parity occupies bytes 8..15 of each - * 16-byte section, the bad block marker sits in byte 0 and the remaining - * bytes are exposed as free. + * 16-byte section, and the remaining bytes are exposed as free, except the + * two bytes that spinand_isbad() reads as the bad block marker. */ static int hyf1gq4_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) @@ -60,9 +60,9 @@ static int hyf1gq4_ooblayout_free(struct mtd_info *mtd, int section, region->offset = 16 * section; region->length = 8; } else { - /* section 0 has one byte reserved for the bad block marker */ - region->offset = 1; - region->length = 7; + /* section 0 has two bytes reserved for the bad block marker */ + region->offset = 2; + region->length = 6; } return 0; From 2f7fa8d90694d21dce68ceabd8383252b111cfea Mon Sep 17 00:00:00 2001 From: Weigang He Date: Sat, 26 Sep 2026 14:28:53 +1000 Subject: [PATCH 153/155] mtd: parsers: qcom: fix offset/size overflow on large partitions In parse_qcomsmem_part() the partition offset and size are computed as parts[j].offset = le32_to_cpu(pentry->offset) * mtd->erasesize; parts[j].size = le32_to_cpu(pentry->length) * mtd->erasesize; Both le32_to_cpu() and mtd->erasesize are 32-bit (u32). The multiply is therefore evaluated in 32-bit arithmetic and the product is truncated to 32 bits before being widened and stored into the 64-bit (u64) parts[j].offset / parts[j].size fields, so the 64-bit destinations are never used at their full width. The SMEM partition table is firmware-provided and the per-entry offset and length are not bounds-checked against the device geometry (only numparts is capped). On a sufficiently large flash, a partition whose (offset|length) * erasesize is >= 2^32 gets a silently truncated offset/size and is mapped to the wrong region of the master MTD, which can overlap or extend into an unintended area. Cast one operand to u64 so the multiplication is performed in 64-bit and the result fits the 64-bit fields without truncation. Found by static analysis tool CodeQL. Fixes: 803eb124e1a6 ("mtd: parsers: Add Qcom SMEM parser") Cc: stable@vger.kernel.org Assisted-by: LLM codeql Signed-off-by: Weigang He Signed-off-by: Miquel Raynal --- drivers/mtd/parsers/qcomsmempart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/parsers/qcomsmempart.c b/drivers/mtd/parsers/qcomsmempart.c index d4fdc46a0073..1591aea48b28 100644 --- a/drivers/mtd/parsers/qcomsmempart.c +++ b/drivers/mtd/parsers/qcomsmempart.c @@ -143,9 +143,9 @@ static int parse_qcomsmem_part(struct mtd_info *mtd, *c = tolower(*c); parts[j].name = name; - parts[j].offset = le32_to_cpu(pentry->offset) * mtd->erasesize; + parts[j].offset = (u64)le32_to_cpu(pentry->offset) * mtd->erasesize; parts[j].mask_flags = pentry->attr; - parts[j].size = le32_to_cpu(pentry->length) * mtd->erasesize; + parts[j].size = (u64)le32_to_cpu(pentry->length) * mtd->erasesize; pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n", i, pentry->name, le32_to_cpu(pentry->offset), le32_to_cpu(pentry->length), pentry->attr); From 7dc9a4e7b9fcee871d021765c90943c4bab91474 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 15 Oct 2026 10:42:14 +0200 Subject: [PATCH 154/155] 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, From d7040c620d8b22f839af7750860b96e39cc58b7c Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 15 Oct 2026 13:31:52 +0200 Subject: [PATCH 155/155] 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 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) 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; }