From 01f9557233e3c04be5c6705864d390682f5d7236 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Apr 2026 09:01:05 +0200 Subject: [PATCH 01/63] mtd: qcom: Unify user-visible "Qualcomm" name Various names for Qualcomm as a company are used in user-visible config options: QCOM, Qualcomm and Qualcomm Technologies. Switch to unified "Qualcomm" so it will be easier for users to identify the options when for example running menuconfig. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 7408f34f0c68..d488213b631f 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -304,7 +304,7 @@ config MTD_NAND_HISI504 Enables support for NAND controller on Hisilicon SoC Hip04. config MTD_NAND_QCOM - tristate "QCOM NAND controller" + tristate "Qualcomm NAND controller" depends on ARCH_QCOM || COMPILE_TEST depends on HAS_IOMEM help From 6b07cdff176bc5f8fef459b85a7e2ea09e68543f Mon Sep 17 00:00:00 2001 From: Haoyu Lu Date: Fri, 10 Apr 2026 21:37:34 +0800 Subject: [PATCH 02/63] mtd: mtdoops: replace simple_strtoul with kstrtouint Replace deprecated simple_strtoul with kstrtouint for better error handling and type safety. The kstrtouint function provides stricter validation, automatically rejecting inputs like "123abc" that simple_strtoul would partially accept. Using kstrtouint avoids unsigned long to int conversion and is more appropriate for MTD device indices which are non-negative integers. Signed-off-by: Haoyu Lu Signed-off-by: Miquel Raynal --- drivers/mtd/mtdoops.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index b88083751a0c..39df7ce8f55f 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c @@ -403,8 +403,7 @@ static struct mtd_notifier mtdoops_notifier = { static int __init mtdoops_init(void) { struct mtdoops_context *cxt = &oops_cxt; - int mtd_index; - char *endp; + unsigned int mtd_index; if (strlen(mtddev) == 0) { pr_err("mtd device (mtddev=name/number) must be supplied\n"); @@ -421,9 +420,9 @@ static int __init mtdoops_init(void) /* Setup the MTD device to use */ cxt->mtd_index = -1; - mtd_index = simple_strtoul(mtddev, &endp, 0); - if (*endp == '\0') + if (kstrtouint(mtddev, 0, &mtd_index) == 0) { cxt->mtd_index = mtd_index; + } cxt->oops_buf = vmalloc(record_size); if (!cxt->oops_buf) From a92a9a49288d1aad67bd12457c80a1e3f463d85b Mon Sep 17 00:00:00 2001 From: Haoyu Lu Date: Fri, 10 Apr 2026 21:37:35 +0800 Subject: [PATCH 03/63] mtd: mtdsuper: replace simple_strtoul with kstrtouint Modernize string-to-number conversion in mtdsuper.c by replacing simple_strtoul with kstrtouint. This change provides proper type safety for MTD device numbers which are non-negative integers. Using kstrtouint avoids unsigned long to int conversion and is more appropriate for device indices. The debug output format specifier is updated to %u for unsigned int. Signed-off-by: Haoyu Lu Signed-off-by: Miquel Raynal --- drivers/mtd/mtdsuper.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/mtdsuper.c b/drivers/mtd/mtdsuper.c index b7e3763c47f0..c709ff7aa6b5 100644 --- a/drivers/mtd/mtdsuper.c +++ b/drivers/mtd/mtdsuper.c @@ -132,12 +132,12 @@ int get_tree_mtd(struct fs_context *fc, } else if (isdigit(fc->source[3])) { /* mount by MTD device number name */ - char *endptr; + unsigned int mtdnr_val; - mtdnr = simple_strtoul(fc->source + 3, &endptr, 0); - if (!*endptr) { + if (kstrtouint(fc->source + 3, 0, &mtdnr_val) == 0) { + mtdnr = mtdnr_val; /* It was a valid number */ - pr_debug("MTDSB: mtd%%d, mtdnr %d\n", mtdnr); + pr_debug("MTDSB: mtd%%d, mtdnr %u\n", mtdnr_val); return mtd_get_sb_by_nr(fc, mtdnr, fill_super); } } From 6eb7c193e751f057b2d75af9b174cfe5dd060696 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 29 Apr 2026 19:56:43 +0200 Subject: [PATCH 04/63] mtd: spinand: Use secondary ops for continuous reads In case a chip supports continuous reads, but uses a slightly different cache operation for these, it may provide a secondary operation template which will be used only during continuous cache read operations. From a vendor driver point of view, enabling this feature implies providing a new set of templates for these continuous read operations. The core will automatically pick the fastest variant, depending on the hardware capabilities. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 61 ++++++++++++++++++++++++++++++++++++- include/linux/mtd/spinand.h | 12 ++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 786e3fb4874d..99a2494554ef 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -503,6 +503,11 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, rdesc = spinand->dirmaps[req->pos.plane].rdesc; + if (spinand->op_templates->cont_read_cache && req->continuous) + rdesc->info.op_tmpl = &rdesc->info.secondary_op_tmpl; + else + rdesc->info.op_tmpl = &rdesc->info.primary_op_tmpl; + if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED && req->mode != MTD_OPS_RAW) rdesc->info.op_tmpl->data.ecc = true; @@ -1235,6 +1240,7 @@ static struct spi_mem_dirmap_desc *spinand_create_rdesc( * its spi controller, use regular reading */ spinand->cont_read_possible = false; + memset(&info->secondary_op_tmpl, 0, sizeof(info->secondary_op_tmpl)); info->length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); @@ -1251,11 +1257,24 @@ static int spinand_create_dirmap(struct spinand_device *spinand, struct nand_device *nand = spinand_to_nand(spinand); struct spi_mem_dirmap_info info = { 0 }; struct spi_mem_dirmap_desc *desc; - bool enable_ecc = false; + bool enable_ecc = false, secondary_op = false; if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED) enable_ecc = true; + if (spinand->cont_read_possible && spinand->op_templates->cont_read_cache) + secondary_op = true; + + /* + * Continuous read implies that only the main data is retrieved, backed + * by an on-die ECC engine. It is not possible to use a pipelind ECC + * engine with continuous read. + */ + if (enable_ecc && secondary_op) { + secondary_op = false; + spinand->cont_read_possible = false; + } + /* The plane number is passed in MSB just above the column address */ info.offset = plane << fls(nand->memorg.pagesize); @@ -1273,6 +1292,10 @@ static int spinand_create_dirmap(struct spinand_device *spinand, /* Read descriptor */ info.primary_op_tmpl = *spinand->op_templates->read_cache; info.primary_op_tmpl.data.ecc = enable_ecc; + if (secondary_op) { + info.secondary_op_tmpl = *spinand->op_templates->cont_read_cache; + info.secondary_op_tmpl.data.ecc = enable_ecc; + } desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -1622,6 +1645,33 @@ int spinand_match_and_init(struct spinand_device *spinand, if (ret) return ret; + if (info->op_variants.cont_read_cache) { + op = spinand_select_op_variant(spinand, SSDR, + info->op_variants.cont_read_cache); + if (op) { + const struct spi_mem_op *read_op; + + read_op = spinand->ssdr_op_templates.read_cache; + + /* + * Sometimes the fastest continuous read variant may not + * be supported. In this case, prefer to use the fastest + * read from cache variant and disable continuous reads. + */ + if (read_op->cmd.buswidth > op->cmd.buswidth || + (read_op->cmd.dtr && !op->cmd.dtr) || + read_op->addr.buswidth > op->addr.buswidth || + (read_op->addr.dtr && !op->addr.dtr) || + read_op->data.buswidth > op->data.buswidth || + (read_op->data.dtr && !op->data.dtr)) + spinand->cont_read_possible = false; + else + spinand->ssdr_op_templates.cont_read_cache = op; + } else { + spinand->cont_read_possible = false; + } + } + /* I/O variants selection with octo-spi DDR commands (optional) */ ret = spinand_init_odtr_instruction_set(spinand); @@ -1644,6 +1694,15 @@ int spinand_match_and_init(struct spinand_device *spinand, info->op_variants.update_cache); spinand->odtr_op_templates.update_cache = op; + if (info->op_variants.cont_read_cache) { + op = spinand_select_op_variant(spinand, ODTR, + info->op_variants.cont_read_cache); + if (op) + spinand->odtr_op_templates.cont_read_cache = op; + else + spinand->cont_read_possible = false; + } + return 0; } diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index f53f5f0499b4..44f4347104d6 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -583,6 +583,7 @@ enum spinand_bus_interface { * @op_variants.read_cache: variants of the read-cache operation * @op_variants.write_cache: variants of the write-cache operation * @op_variants.update_cache: variants of the update-cache operation + * @op_variants.cont_read_cache: variants of the continuous read-cache operation * @vendor_ops: vendor specific operations * @select_target: function used to select a target/die. Required only for * multi-die chips @@ -607,6 +608,7 @@ struct spinand_info { const struct spinand_op_variants *read_cache; const struct spinand_op_variants *write_cache; const struct spinand_op_variants *update_cache; + const struct spinand_op_variants *cont_read_cache; } op_variants; const struct spinand_op_variants *vendor_ops; int (*select_target)(struct spinand_device *spinand, @@ -636,6 +638,14 @@ struct spinand_info { .update_cache = __update, \ } +#define SPINAND_INFO_OP_VARIANTS_WITH_CONT(__read, __write, __update, __cont_read) \ + { \ + .read_cache = __read, \ + .write_cache = __write, \ + .update_cache = __update, \ + .cont_read_cache = __cont_read, \ + } + #define SPINAND_INFO_VENDOR_OPS(__ops) \ .vendor_ops = __ops @@ -707,6 +717,7 @@ struct spinand_dirmap { * @read_cache: read cache op template * @write_cache: write cache op template * @update_cache: update cache op template + * @cont_read_cache: continuous read cache op template (optional) */ struct spinand_mem_ops { struct spi_mem_op reset; @@ -721,6 +732,7 @@ struct spinand_mem_ops { const struct spi_mem_op *read_cache; const struct spi_mem_op *write_cache; const struct spi_mem_op *update_cache; + const struct spi_mem_op *cont_read_cache; }; /** From e1c172bbe1853e69e5fcf4692ea3c8b6fe9176a1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 29 Apr 2026 19:56:48 +0200 Subject: [PATCH 05/63] mtd: spinand: winbond: Add support for continuous reads on W25NxxJW As for the W35NxxJW family, add support for W25N{01,02}JW continuous read support. Similar operations require to be done, such as setting a specific bit in a configuration register, and providing a set of read variants without the address cycles. As read from cache variants are badly supported by SPI memory controllers, we create a new set of read from cache templates with a fake address cycle and just enough dummy cycles. There are two unsupported configurations (which would require 4.5 dummy bytes), so we just do not provide them. The same extra value in the ECC is possible as with the W35NxxJW family, so we reference the same helper to retrieve the ECC status. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 7cc0f0091430..f538cf2228f8 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -511,28 +511,6 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), - SPINAND_INFO("W35N02JW", /* 1.8V */ - SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22), - NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1), - NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, - &write_cache_octal_variants, - &update_cache_octal_variants), - SPINAND_ODTR_PACKED_PAGE_READ, - SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), - SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), - 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), - NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, - &write_cache_octal_variants, - &update_cache_octal_variants), - SPINAND_ODTR_PACKED_PAGE_READ, - SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), - SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), /* 2G-bit densities */ SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21), @@ -573,6 +551,17 @@ static const struct spinand_info winbond_spinand_table[] = { &update_cache_variants), 0, SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), + SPINAND_INFO("W35N02JW", /* 1.8V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22), + NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, + &write_cache_octal_variants, + &update_cache_octal_variants), + SPINAND_ODTR_PACKED_PAGE_READ, + SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), + SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), + SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), /* 4G-bit densities */ SPINAND_INFO("W25N04KV", /* 3.3V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23), @@ -592,6 +581,17 @@ static const struct spinand_info winbond_spinand_table[] = { &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), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, + &write_cache_octal_variants, + &update_cache_octal_variants), + SPINAND_ODTR_PACKED_PAGE_READ, + SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), + SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), + SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), }; static int winbond_spinand_init(struct spinand_device *spinand) From 5e85bff62bccf74bbce17d29c9818f83d8652b18 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 28 Nov 2025 18:53:46 +0100 Subject: [PATCH 06/63] mtd: spinand: winbond: Add support for continuous reads on W35NxxJW W35N{01,02,04}JW support being read continuously under certain circumstances. A bit must be set in their configuration register, and a specific read from cache operation, a bit shorter than usual because it no longer requires the address cycles, must be used for the occasion. Setting the "enable" bit is already supported by the core, aside from the subtlety of making sure the HFREQ bit is also set in octal DTR mode above 90MHz. However, handling two different read from cache templates involves creating a list of read from cache variants adapted the continuous reads, ie. without address cycles. Unfortunately, these operations, despite being very close to their original read from cache cousins, are often unsupported by smart SPI controller drivers because reading from cache historically allowed changing the offset at which the host would start by providing a 2-byte column address. In order to prevent issues with this, it has been decided to implement these variants with a single "ignored" address byte (respectively two in the octal DTR case), further reducing the amount of dummy cycles needed before the first bit of data. Enabling continuous reads has a side effect: the ECC status register now may also return the value b11, which means that more than 1 uncorrectable error happened during the read. This non standard behaviour requires to re-implement, almost identically the "get ECC" helper from the core, with just an extra case for this value (it is prefixed "w25w35nxxjw" because all these chips have the same behaviour). Speed gain is substantial, see below. The flash_speed -C benchmark has been run on a TI AM62A7 LP SK with CPU power management disabled, mounted with a W35N01JW chip. 1S-8S-8S: 1 page read speed is 15058 KiB/s 2 page read speed is 15058 KiB/s 3 page read speed is 16800 KiB/s 4 page read speed is 17066 KiB/s 5 page read speed is 18461 KiB/s 6 page read speed is 18461 KiB/s 7 page read speed is 19384 KiB/s 8 page read speed is 19692 KiB/s 9 page read speed is 19384 KiB/s 10 page read speed is 20000 KiB/s 11 page read speed is 20000 KiB/s 12 page read speed is 20000 KiB/s 13 page read speed is 20800 KiB/s 14 page read speed is 20363 KiB/s 15 page read speed is 20000 KiB/s 16 page read speed is 19692 KiB/s 32 page read speed is 19692 KiB/s 64 page read speed is 19692 KiB/s 8D-8D-8D: 1 page read speed is 23272 KiB/s 2 page read speed is 23272 KiB/s 3 page read speed is 28000 KiB/s 4 page read speed is 32000 KiB/s 5 page read speed is 34285 KiB/s 6 page read speed is 34285 KiB/s 7 page read speed is 36000 KiB/s 8 page read speed is 36571 KiB/s 9 page read speed is 36000 KiB/s 10 page read speed is 34285 KiB/s 11 page read speed is 36666 KiB/s 12 page read speed is 40000 KiB/s 13 page read speed is 41600 KiB/s 14 page read speed is 37333 KiB/s 15 page read speed is 40000 KiB/s 16 page read speed is 36571 KiB/s 32 page read speed is 42666 KiB/s 64 page read speed is 42666 KiB/s Signed-off-by: Miquel Raynal --- Not all configurations have been tested/validated yet. --- drivers/mtd/nand/spi/winbond.c | 126 +++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index f538cf2228f8..82383c8839f0 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -15,9 +15,11 @@ #define SPINAND_MFR_WINBOND 0xEF +#define WINBOND_CFG_HFREQ BIT(0) #define WINBOND_CFG_BUF_READ BIT(3) #define W25N04KV_STATUS_ECC_5_8_BITFLIPS (3 << 4) +#define W25W35NXXJW_STATUS_ECC_MULT_UNCOR (3 << 4) #define W25N0XJW_SR4 0xD0 #define W25N0XJW_SR4_HS BIT(2) @@ -29,6 +31,49 @@ #define W35N01JW_VCR_IO_MODE_OCTAL_DDR 0xC7 #define W35N01JW_VCR_DUMMY_CLOCK_REG 0x01 +/* + * Winbond chips ignore the address bytes during continuous reads, and + * because the dummy cycles are enough they indicate dropping the + * address cycles from the continuous read from cache variants. This is + * very poorly supported by SPI controller drivers which are "wired" to + * always at least provide the column. Keep using address cycles, but + * reduce the number of dummy cycles accordingly. + */ +#define WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x0b, 1), \ + SPI_MEM_OP_ADDR(1, 0, 1), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 1), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x8b, 1), \ + SPI_MEM_OP_ADDR(1, 0, 1), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 8), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x9d, 1), \ + SPI_MEM_DTR_OP_ADDR(1, 0, 1), \ + SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 1), \ + SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xcb, 1), \ + SPI_MEM_OP_ADDR(1, 0, 8), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 8), \ + SPI_MEM_OP_DATA_IN(len, buf, 8), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x9d, 8), \ + SPI_MEM_DTR_OP_ADDR(2, 0, 8), \ + SPI_MEM_DTR_OP_DUMMY(ndummy - 2, 8), \ + SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \ + SPI_MEM_OP_MAX_FREQ(freq)) + /* * "X2" in the core is equivalent to "dual output" in the datasheets, * "X4" in the core is equivalent to "quad output" in the datasheets. @@ -49,6 +94,19 @@ static SPINAND_OP_VARIANTS(read_cache_octal_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_octal_variants, + WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(24, NULL, 0, 120 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(16, NULL, 0, 86 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(3, NULL, 0, 120 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(2, NULL, 0, 105 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(20, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(16, NULL, 0, 162 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(12, NULL, 0, 124 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(8, NULL, 0, 86 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(2, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(1, NULL, 0, 133 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(1, NULL, 0, 0)); + static SPINAND_OP_VARIANTS(write_cache_octal_variants, SPINAND_PROG_LOAD_8D_8D_8D_OP(true, 0, NULL, 0), SPINAND_PROG_LOAD_1S_8S_8S_OP(true, 0, NULL, 0), @@ -326,6 +384,26 @@ static int w25n02kv_ecc_get_status(struct spinand_device *spinand, return -EINVAL; } +static int w25w35nxxjw_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_HAS_BITFLIPS: + return 1; + + case STATUS_ECC_UNCOR_ERROR: + case W25W35NXXJW_STATUS_ECC_MULT_UNCOR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + static int w25n0xjw_hs_cfg(struct spinand_device *spinand, enum spinand_bus_interface iface) { @@ -451,6 +529,18 @@ static int w35n0xjw_vcr_cfg(struct spinand_device *spinand, return 0; } +static int w35n0xjw_set_cont_read(struct spinand_device *spinand, bool enable) +{ + const struct spi_mem_op *cont_op = spinand->op_templates->cont_read_cache; + u8 mask = enable ? 0 : WINBOND_CFG_BUF_READ; + + if (cont_op && enable && spinand_op_is_odtr(cont_op) && + cont_op->max_freq >= 90 * HZ_PER_MHZ) + mask |= WINBOND_CFG_HFREQ; + + return spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ | WINBOND_CFG_HFREQ, mask); +} + static const struct spinand_info winbond_spinand_table[] = { /* 512M-bit densities */ SPINAND_INFO("W25N512GW", /* 1.8V */ @@ -504,13 +594,15 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc, 0x21), NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 1, 1), NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, - &write_cache_octal_variants, - &update_cache_octal_variants), + SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants, + &write_cache_octal_variants, + &update_cache_octal_variants, + &cont_read_cache_octal_variants), 0, SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), - SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), + SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status), + SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg), + SPINAND_CONT_READ(w35n0xjw_set_cont_read)), /* 2G-bit densities */ SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21), @@ -555,13 +647,15 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22), NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1), NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, - &write_cache_octal_variants, - &update_cache_octal_variants), + SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants, + &write_cache_octal_variants, + &update_cache_octal_variants, + &cont_read_cache_octal_variants), SPINAND_ODTR_PACKED_PAGE_READ, SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), - SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), + SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status), + SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg), + SPINAND_CONT_READ(w35n0xjw_set_cont_read)), /* 4G-bit densities */ SPINAND_INFO("W25N04KV", /* 3.3V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23), @@ -585,13 +679,15 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23), NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1), NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, - &write_cache_octal_variants, - &update_cache_octal_variants), + SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants, + &write_cache_octal_variants, + &update_cache_octal_variants, + &cont_read_cache_octal_variants), SPINAND_ODTR_PACKED_PAGE_READ, SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), - SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), + SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status), + SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg), + SPINAND_CONT_READ(w35n0xjw_set_cont_read)), }; static int winbond_spinand_init(struct spinand_device *spinand) From 240e65cb4402d62238667d71c0f9298607200b8b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 25 Mar 2026 15:01:40 +0100 Subject: [PATCH 07/63] mtd: spinand: winbond: Create a helper to write the HS bit Updating the HS bit is not complex but implies reading, setting/clearing a bit and writing. Clean a bit this section by moving this logic in its own helper. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 82383c8839f0..97758f495c73 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -404,13 +404,28 @@ static int w25w35nxxjw_ecc_get_status(struct spinand_device *spinand, u8 status) return -EINVAL; } +static int w25n0xjw_set_sr4_hs(struct spinand_device *spinand, bool enable) +{ + int ret; + u8 sr4; + + ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4); + if (ret) + return ret; + + if (enable) + sr4 |= W25N0XJW_SR4_HS; + else + sr4 &= ~W25N0XJW_SR4_HS; + + return spinand_write_reg_op(spinand, W25N0XJW_SR4, sr4); +} + static int w25n0xjw_hs_cfg(struct spinand_device *spinand, enum spinand_bus_interface iface) { const struct spi_mem_op *op; bool hs; - u8 sr4; - int ret; if (iface != SSDR) return -EOPNOTSUPP; @@ -429,20 +444,7 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand, else hs = true; - ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4); - if (ret) - return ret; - - if (hs) - sr4 |= W25N0XJW_SR4_HS; - else - sr4 &= ~W25N0XJW_SR4_HS; - - ret = spinand_write_reg_op(spinand, W25N0XJW_SR4, sr4); - if (ret) - return ret; - - return 0; + return w25n0xjw_set_sr4_hs(spinand, hs); } static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val) From ca842a62fe993019255b380ab53bed64ef31fe6f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 26 Mar 2026 17:02:29 +0100 Subject: [PATCH 08/63] mtd: spinand: winbond: Create a helper to detect the need for the HS bit The logic is not complex but might be reused to cleanup a bit the section by moving it to a dedicated helper. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 97758f495c73..a73a35adbf08 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -421,30 +421,33 @@ static int w25n0xjw_set_sr4_hs(struct spinand_device *spinand, bool enable) return spinand_write_reg_op(spinand, W25N0XJW_SR4, sr4); } +/* + * SDR dual and quad I/O operations over 104MHz require the HS bit to + * enable a few more dummy cycles. + */ +static bool w25n0xjw_op_needs_hs(const struct spi_mem_op *op) +{ + if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) + return false; + else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1) + return false; + else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ) + return false; + + return true; +} + static int w25n0xjw_hs_cfg(struct spinand_device *spinand, enum spinand_bus_interface iface) { const struct spi_mem_op *op; - bool hs; if (iface != SSDR) return -EOPNOTSUPP; - /* - * SDR dual and quad I/O operations over 104MHz require the HS bit to - * enable a few more dummy cycles. - */ op = spinand->op_templates->read_cache; - if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) - hs = false; - else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1) - hs = false; - else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ) - hs = false; - else - hs = true; - return w25n0xjw_set_sr4_hs(spinand, hs); + return w25n0xjw_set_sr4_hs(spinand, w25n0xjw_op_needs_hs(op)); } static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val) From e1ff8802ac57a917bca50dcabaf5ea78d8cd407f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 25 Mar 2026 15:37:36 +0100 Subject: [PATCH 09/63] mtd: spinand: winbond: Add support for continuous reads on W25NxxJW As for the W35NxxJW family, add support for W25N{01,02}JW continuous read support. Similar operations require to be done, such as setting a specific bit in a configuration register, and providing a set of read variants without the address cycles. As read from cache variants are badly supported by SPI memory controllers, we create a new set of read from cache templates with a fake address cycle and just enough dummy cycles. There are two unsupported configurations (which would require 4.5 dummy bytes), so we just do not provide them. The same extra value in the ECC is possible as with the W35NxxJW family, so we reference the same helper to retrieve the ECC status. Signed-off-by: Miquel Raynal --- All variants have been validated on a Nuvoton MA35D1 platform. --- drivers/mtd/nand/spi/winbond.c | 108 ++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 10 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index a73a35adbf08..9b78c1e6cbc9 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -46,6 +46,62 @@ SPI_MEM_OP_DATA_IN(len, buf, 1), \ SPI_MEM_OP_MAX_FREQ(freq)) +#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_1D_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x0d, 1), \ + SPI_MEM_DTR_OP_ADDR(2, 0, 1), \ + SPI_MEM_DTR_OP_DUMMY(ndummy, 1), \ + SPI_MEM_DTR_OP_DATA_IN(len, buf, 1), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \ + SPI_MEM_OP_ADDR(1, 0, 1), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 2), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \ + SPI_MEM_OP_ADDR(1, 0, 2), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 2), \ + SPI_MEM_OP_DATA_IN(len, buf, 2), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_2D_2D_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xbd, 1), \ + SPI_MEM_DTR_OP_ADDR(1, 0, 2), \ + SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 2), \ + SPI_MEM_DTR_OP_DATA_IN(len, buf, 2), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \ + SPI_MEM_OP_ADDR(1, 0, 1), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 4), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_4D_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x6d, 1), \ + SPI_MEM_DTR_OP_ADDR(1, 0, 1), \ + SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 1), \ + SPI_MEM_DTR_OP_DATA_IN(len, buf, 4), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \ + SPI_MEM_OP_ADDR(1, 0, 4), \ + SPI_MEM_OP_DUMMY(ndummy - 1, 4), \ + SPI_MEM_OP_DATA_IN(len, buf, 4), \ + SPI_MEM_OP_MAX_FREQ(freq)) + +#define WINBOND_CONT_READ_FROM_CACHE_1S_4D_4D_OP(ndummy, buf, len, freq) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xed, 1), \ + SPI_MEM_DTR_OP_ADDR(1, 0, 4), \ + SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 4), \ + SPI_MEM_DTR_OP_DATA_IN(len, buf, 4), \ + SPI_MEM_OP_MAX_FREQ(freq)) + #define WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(ndummy, buf, len, freq) \ SPI_MEM_OP(SPI_MEM_OP_CMD(0x8b, 1), \ SPI_MEM_OP_ADDR(1, 0, 1), \ @@ -133,6 +189,20 @@ static SPINAND_OP_VARIANTS(read_cache_dual_quad_dtr_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, 54 * HZ_PER_MHZ)); +static SPINAND_OP_VARIANTS(cont_read_cache_dual_quad_dtr_variants, + WINBOND_CONT_READ_FROM_CACHE_1S_4D_4D_OP(11, NULL, 0, 80 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_1D_4D_OP(5, NULL, 0, 80 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(7, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(6, NULL, 0, 104 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(4, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_2D_2D_OP(6, NULL, 0, 80 * HZ_PER_MHZ), + /* The 1S_1D_2D variant would require 4.5 dummy bytes, this is not possible */ + WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(5, NULL, 0, 0), + WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(4, NULL, 0, 104 * HZ_PER_MHZ), + WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(4, NULL, 0, 0), + /* The 1S_1D_1D variant would require 4.5 dummy bytes, this is not possible */ + WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(4, NULL, 0, 0)); + static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0), SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0), @@ -445,11 +515,25 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand, if (iface != SSDR) return -EOPNOTSUPP; + /* + * At this stage, we do not yet know the continuous read template, nor + * if there is going to be one. Let's assume the continuous read + * template will be selected with the same heuristics as the buffered + * read variant, as there cannot be a HS configuration mismatch between + * them. + */ op = spinand->op_templates->read_cache; return w25n0xjw_set_sr4_hs(spinand, w25n0xjw_op_needs_hs(op)); } +static int w25n0xjw_set_cont_read(struct spinand_device *spinand, bool enable) +{ + u8 mask = enable ? 0 : WINBOND_CFG_BUF_READ; + + return spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, mask); +} + static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val) { struct spi_mem_op op = SPINAND_OP(spinand, winbond_write_vcr, @@ -580,12 +664,14 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbc, 0x21), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants, - &write_cache_variants, - &update_cache_variants), + SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_dual_quad_dtr_variants, + &write_cache_variants, + &update_cache_variants, + &cont_read_cache_dual_quad_dtr_variants), SPINAND_HAS_QE_BIT, - SPINAND_ECCINFO(&w25n01jw_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)), + SPINAND_ECCINFO(&w25n01jw_ooblayout, w25w35nxxjw_ecc_get_status), + SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg), + SPINAND_CONT_READ(w25n0xjw_set_cont_read)), SPINAND_INFO("W25N01KV", /* 3.3V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae, 0x21), NAND_MEMORG(1, 2048, 96, 64, 1024, 20, 1, 1, 1), @@ -624,12 +710,14 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbf, 0x22), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 2, 1), NAND_ECCREQ(1, 512), - SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants, - &write_cache_variants, - &update_cache_variants), + SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_dual_quad_dtr_variants, + &write_cache_variants, + &update_cache_variants, + &cont_read_cache_dual_quad_dtr_variants), SPINAND_HAS_QE_BIT, - SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), - SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)), + SPINAND_ECCINFO(&w25m02gv_ooblayout, w25w35nxxjw_ecc_get_status), + SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg), + SPINAND_CONT_READ(w25n0xjw_set_cont_read)), SPINAND_INFO("W25N02KV", /* 3.3V */ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), From 7845161edef8008710230efea3839757d0b03d25 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 26 Mar 2026 17:47:16 +0100 Subject: [PATCH 10/63] mtd: spinand: Make sure continuous read is always disabled during probe Recent changes made sure whenever we were using continuous reads, we would first start by disabling the feature to ensure a well proven and stable probe sequence. For development purposes, it might also matter to make sure we always disable continuous reads at first, in case the ECC configuration would change. Doing this "automatically" will become even more relevant when we add extra controller flags to prevent continuous reads at all. Ensure we disable continuous reads if the feature is available on the chip, regardless of whether it will be used or not. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 99a2494554ef..1d631054bb24 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -967,11 +967,7 @@ static void spinand_cont_read_init(struct spinand_device *spinand) enum nand_ecc_engine_type engine_type = nand->ecc.ctx.conf.engine_type; /* OOBs cannot be retrieved so external/on-host ECC engine won't work */ - if (spinand->set_cont_read && - (engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE || - engine_type == NAND_ECC_ENGINE_TYPE_NONE)) { - spinand->cont_read_possible = true; - + if (spinand->set_cont_read) { /* * Ensure continuous read is disabled on probe. * Some devices retain this state across soft reset, @@ -979,6 +975,10 @@ static void spinand_cont_read_init(struct spinand_device *spinand) * in false positive returns from spinand_isbad(). */ spinand_cont_read_enable(spinand, false); + + if (engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE || + engine_type == NAND_ECC_ENGINE_TYPE_NONE) + spinand->cont_read_possible = true; } } From 9e3997d3ab661f99828fb86487be56c25e166410 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 26 Mar 2026 17:47:17 +0100 Subject: [PATCH 11/63] mtd: spinand: Prevent continuous reads on some controllers Some controllers do not have full control over the CS line state and may deassert it under certain conditions in the middle of a (long) transfer. Continuous reads are stopped with a CS deassert, hence both features cannot live together. Whenever a controller flags that it cannot maintain the CS state reliably, disable continuous reads entirely. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 1d631054bb24..f1084d5e04b9 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -965,6 +965,7 @@ static void spinand_cont_read_init(struct spinand_device *spinand) { struct nand_device *nand = spinand_to_nand(spinand); enum nand_ecc_engine_type engine_type = nand->ecc.ctx.conf.engine_type; + struct spi_controller *ctlr = spinand->spimem->spi->controller; /* OOBs cannot be retrieved so external/on-host ECC engine won't work */ if (spinand->set_cont_read) { @@ -976,8 +977,9 @@ static void spinand_cont_read_init(struct spinand_device *spinand) */ spinand_cont_read_enable(spinand, false); - if (engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE || - engine_type == NAND_ECC_ENGINE_TYPE_NONE) + if ((engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE || + engine_type == NAND_ECC_ENGINE_TYPE_NONE) && + !spi_mem_controller_is_capable(ctlr, no_cs_assertion)) spinand->cont_read_possible = true; } } From 87eee18f2974c0b1d854b6228408b93c6bc91744 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 30 Apr 2026 15:07:19 -0700 Subject: [PATCH 12/63] mtd: sm_ftl: allocate cis_buffer with main struct Use a flexible array member and kzalloc_flex() to do so. Simplifies memory allocation slightly. Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/sm_ftl.c | 30 +++++++++++------------------- drivers/mtd/sm_ftl.h | 2 +- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c index c8032755f9a4..fb0f97fb94f0 100644 --- a/drivers/mtd/sm_ftl.c +++ b/drivers/mtd/sm_ftl.c @@ -1133,7 +1133,7 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) struct sm_ftl *ftl; /* Allocate & initialize our private structure */ - ftl = kzalloc_obj(struct sm_ftl); + ftl = kzalloc_flex(*ftl, cis_buffer, SM_SECTOR_SIZE); if (!ftl) goto error1; @@ -1145,25 +1145,20 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) /* Read media information */ if (sm_get_media_info(ftl, mtd)) { dbg("found unsupported mtd device, aborting"); - goto error2; + goto error1; } - /* Allocate temporary CIS buffer for read retry support */ - ftl->cis_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL); - if (!ftl->cis_buffer) - goto error2; - /* Allocate zone array, it will be initialized on demand */ ftl->zones = kzalloc_objs(struct ftl_zone, ftl->zone_count); if (!ftl->zones) - goto error3; + goto error2; /* Allocate the cache*/ ftl->cache_data = kzalloc(ftl->block_size, GFP_KERNEL); if (!ftl->cache_data) - goto error4; + goto error3; sm_cache_init(ftl); @@ -1171,7 +1166,7 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) /* Allocate upper layer structure and initialize it */ trans = kzalloc_obj(struct mtd_blktrans_dev); if (!trans) - goto error5; + goto error4; ftl->trans = trans; trans->priv = ftl; @@ -1184,12 +1179,12 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) if (sm_find_cis(ftl)) { dbg("CIS not found on mtd device, aborting"); - goto error6; + goto error5; } ftl->disk_attributes = sm_create_sysfs_attributes(ftl); if (!ftl->disk_attributes) - goto error6; + goto error5; trans->disk_attributes = ftl->disk_attributes; sm_printk("Found %d MiB xD/SmartMedia FTL on mtd%d", @@ -1206,17 +1201,15 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) /* Register device*/ if (add_mtd_blktrans_dev(trans)) { dbg("error in mtdblktrans layer"); - goto error6; + goto error5; } return; -error6: - kfree(trans); error5: - kfree(ftl->cache_data); + kfree(trans); error4: - kfree(ftl->zones); + kfree(ftl->cache_data); error3: - kfree(ftl->cis_buffer); + kfree(ftl->zones); error2: kfree(ftl); error1: @@ -1242,7 +1235,6 @@ static void sm_remove_dev(struct mtd_blktrans_dev *dev) } sm_delete_sysfs_attributes(ftl); - kfree(ftl->cis_buffer); kfree(ftl->zones); kfree(ftl->cache_data); kfree(ftl); diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h index 6aed8b60de16..1a1a54ce836a 100644 --- a/drivers/mtd/sm_ftl.h +++ b/drivers/mtd/sm_ftl.h @@ -39,7 +39,6 @@ struct sm_ftl { int cis_block; /* CIS block location */ int cis_boffset; /* CIS offset in the block */ int cis_page_offset; /* CIS offset in the page */ - void *cis_buffer; /* tmp buffer for cis reads */ /* Cache */ int cache_block; /* block number of cached block */ @@ -56,6 +55,7 @@ struct sm_ftl { int cylinders; struct attribute_group *disk_attributes; + u8 cis_buffer[]; /* tmp buffer for cis reads */ }; struct chs_entry { From 829dff83597615208aedf0f5abb3878b47f2314d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:25 +0200 Subject: [PATCH 13/63] mtd: spi-nor: debugfs: Fix the flags list As mentioned above the spi_nor_option_flags enumeration in core.h, this list should be kept in sync with the one in the core. Add the missing flag. Fixes: 6a42bc97ccda ("mtd: spi-nor: core: Allow specifying the byte order in Octal DTR mode") Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/debugfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index fa6956144d2e..d700e0b27182 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -28,6 +28,7 @@ static const char *const snor_f_names[] = { SNOR_F_NAME(RWW), SNOR_F_NAME(ECC), SNOR_F_NAME(NO_WP), + SNOR_F_NAME(SWAP16), }; #undef SNOR_F_NAME From e1d456b26bf23e30db305a6184e8abd9ab68bbf2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:26 +0200 Subject: [PATCH 14/63] mtd: spi-nor: swp: Improve locking user experience In the case of the first block being locked (or the few first blocks), if the user want to fully unlock the device it has two possibilities: - either it asks to unlock the entire device, and this works; - or it asks to unlock just the block(s) that are currently locked, which fails. It fails because the conditions "can_be_top" and "can_be_bottom" are true. Indeed, in this case, we unlock everything, so the TB bit does not matter. However in the current implementation, use_top would be true (as this is the favourite option) and lock_len, which in practice should be reduced down to 0, is set to "nor->params->size - (ofs + len)" which is a positive number. This is wrong. An easy way is to simply add an extra condition. In the unlock() path, if we can achieve the same result from both sides, it means we unlock everything and lock_len must simply be 0. A comment is added to clarify that logic. Fixes: 3dd8012a8eeb ("mtd: spi-nor: add TB (Top/Bottom) protect support") Cc: stable@kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index e67a81dbb6bf..d5f4bf555cfc 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -282,8 +282,15 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) /* Prefer top, if both are valid */ use_top = can_be_top; - /* lock_len: length of region that should remain locked */ - if (use_top) + /* + * lock_len: length of region that should remain locked. + * + * When can_be_top and can_be_bottom booleans are true, both adjacent + * regions are unlocked, thus the entire flash can be unlocked. + */ + if (can_be_top && can_be_bottom) + lock_len = 0; + else if (use_top) lock_len = nor->params->size - (ofs + len); else lock_len = ofs; From a6470e2162e9c3779a4bd6ff3bed1b81d796e46e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:27 +0200 Subject: [PATCH 15/63] mtd: spi-nor: Drop duplicate Kconfig dependency I do not think the MTD dependency is needed twice. This is likely a duplicate coming from a former rebase when the spi-nor core got cleaned up a while ago. Remove the extra line. Fixes: b35b9a10362d ("mtd: spi-nor: Move m25p80 code in spi-nor.c") Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 24cd25de2b8b..fd05a24d64a9 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only menuconfig MTD_SPI_NOR tristate "SPI NOR device support" - depends on MTD depends on MTD && SPI_MASTER select SPI_MEM help From f316b8535887c50be009902bd02098fddb47e2e7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:28 +0200 Subject: [PATCH 16/63] mtd: spi-nor: Make sure the QE bit is kept enabled if useful Not all chips implement the 4BAIT table which typically indicates the program capability, while many of them do implement the relevant SFDP parts indicating the read capabilities. In such a situation, programs can happen in single mode (1-1-1) and reads in quad mode (1-1-4 or 1-4-4). For the reads to work in such condition, the QE bit must be set. In case we later use the spi_nor_write_16bit_sr_and_check() helper with a chip with such configuration, the QE bit would get incorrectly cleared. Make sure this doesn't happen by keeping the QE bit under a simpler condition: - the quad enable hook is there (no change) - and at least one of the two protocols is based on quad I/O cycles Signed-off-by: Miquel Raynal Reviewed-by: Pratyush Yadav Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 5dd0b3cb5250..394c27de02d6 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -869,8 +869,8 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1) 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 && + } 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 From 7c4e909b176f661e834853fa726a6e58acab00e1 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:29 +0200 Subject: [PATCH 17/63] mtd: spi-nor: Improve opcodes documentation There are two status registers, named 1 and 2. The current wording is misleading as "1" may refer to the status register ID as well as the number of bytes required (which, in this case can be 1 or 2). Clarify the comments by aligning them on the same pattern: "{read,write} status {1,2} register" Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- include/linux/mtd/spi-nor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index cdcfe0fd2e7d..90a0cf583512 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -21,8 +21,8 @@ /* Flash opcodes. */ #define SPINOR_OP_WRDI 0x04 /* Write disable */ #define SPINOR_OP_WREN 0x06 /* Write enable */ -#define SPINOR_OP_RDSR 0x05 /* Read status register */ -#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */ +#define SPINOR_OP_RDSR 0x05 /* Read status register 1 */ +#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_READ 0x03 /* Read data bytes (low frequency) */ From 154f375f7d31f2f57b4d312ac22562d64a7bb64f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:30 +0200 Subject: [PATCH 18/63] mtd: spi-nor: debugfs: Align variable access with the rest of the file The "params" variable is used everywhere else, align this particular line of the file to use "params" directly rather than the "nor" pointer. Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index d700e0b27182..69830ad43990 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -139,7 +139,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data) if (!(nor->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", nor->params->die_erase_opcode, buf); + seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf); } seq_puts(s, "\nsector map\n"); From 461e2f8a2d943ea2b1b966e8ffc8e5ac02c6a686 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:31 +0200 Subject: [PATCH 19/63] mtd: spi-nor: debugfs: Enhance output Align the number of dashes to the bigger column width (the title in this case) to make the output more pleasant and aligned with what is done in the "params" file output. Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 69830ad43990..d0191eb9f879 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) seq_puts(s, "\nsector map\n"); seq_puts(s, " region (in hex) | erase mask | overlaid\n"); - seq_puts(s, " ------------------+------------+----------\n"); + seq_puts(s, " ------------------+------------+---------\n"); for (i = 0; i < erase_map->n_regions; i++) { u64 start = region[i].offset; u64 end = start + region[i].size - 1; From 5eeff82d389e381422f48a4bde4b7f4a5a2dc584 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:32 +0200 Subject: [PATCH 20/63] mtd: spi-nor: swp: Explain the MEMLOCK ioctl implementation behaviour Add more details about how these requests are actually handled in the SPI NOR core. Their behaviour was not entirely clear to me at first, and explaining them in plain English sounds the way to go. Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index e838c40a2589..65bb4b5e1e2b 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -279,9 +279,14 @@ struct spi_nor_erase_map { /** * struct spi_nor_locking_ops - SPI NOR locking methods - * @lock: lock a region of the SPI NOR. - * @unlock: unlock a region of the SPI NOR. - * @is_locked: check if a region of the SPI NOR is completely locked + * @lock: lock a region of the SPI NOR, never locks more than what is + * requested, ie. may lock less. + * @unlock: unlock a region of the SPI NOR, may unlock more than what is + * requested. + * @is_locked: check if a region of the SPI NOR is completely locked, returns + * false otherwise. This feedback may be misleading because users + * may get an "unlocked" status even though a subpart of the region + * is effectively locked. */ struct spi_nor_locking_ops { int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len); From cb3021466daa3d7f87cdec8c294649315dad711f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:33 +0200 Subject: [PATCH 21/63] mtd: spi-nor: swp: Clarify a comment The comment states that some power of two sizes are not supported. This is very device dependent (based on the size), so modulate a bit the sentence to make it more accurate. Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index d5f4bf555cfc..f221d6361b57 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -305,7 +305,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) val = (val & ~SR_BP3) | SR_BP3_BIT6; - /* Some power-of-two sizes are not supported */ + /* Some power-of-two sizes may not be supported */ if (val & ~mask) return -EINVAL; } From 2188bbbb4a20ca1864de8e1447d13e5d19e8355a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:34 +0200 Subject: [PATCH 22/63] mtd: spi-nor: swp: Use a pointer for SR instead of a single byte At this stage, the Status Register is most often seen as a single byte. This is subject to change when we will need to read the CMP bit which is located in the Control Register (kind of secondary status register). Both will need to be carried. Change a few prototypes to carry a u8 pointer. This way it also makes it very clear where we access the first register, and where we will access the second. There is no functional change. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 48 ++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index f221d6361b57..61830f18a147 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -55,13 +55,13 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) return sector_size; } -static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs, +static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, u64 *len) { u64 min_prot_len; u8 mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 bp, val = sr & mask; + u8 bp, val = sr[0] & mask; if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; @@ -81,7 +81,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs, if (*len > nor->params->size) *len = nor->params->size; - if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask) + if (nor->flags & SNOR_F_HAS_SR_TB && sr[0] & tb_mask) *ofs = 0; else *ofs = nor->params->size - *len; @@ -92,7 +92,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs, * (if @locked is false); false otherwise. */ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, - u64 len, u8 sr, bool locked) + u64 len, const u8 *sr, bool locked) { loff_t lock_offs, lock_offs_max, offs_max; u64 lock_len; @@ -113,13 +113,13 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, return (ofs >= lock_offs_max) || (offs_max <= lock_offs); } -static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, u8 sr) +static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr) { return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true); } static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, - u8 sr) + const u8 *sr) { return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false); } @@ -160,7 +160,8 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) { u64 min_prot_len; - int ret, status_old, status_new; + int ret; + u8 status_old[1] = {}, status_new[1] = {}; u8 mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 pow, val; @@ -172,7 +173,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) if (ret) return ret; - status_old = nor->bouncebuf[0]; + status_old[0] = 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)) @@ -217,7 +218,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) return -EINVAL; } - status_new = (status_old & ~mask & ~tb_mask) | val; + status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val; /* * Disallow further writes if WP# pin is neither left floating nor @@ -225,20 +226,20 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) * WP# pin hard strapped to GND can be a valid use case. */ if (!(nor->flags & SNOR_F_NO_WP)) - status_new |= SR_SRWD; + status_new[0] |= SR_SRWD; if (!use_top) - status_new |= tb_mask; + status_new[0] |= tb_mask; /* Don't bother if they're the same */ - if (status_new == status_old) + if (status_new[0] == status_old[0]) return 0; /* Only modify protection if it will not unlock other areas */ - if ((status_new & mask) < (status_old & mask)) + if ((status_new[0] & mask) < (status_old[0] & mask)) return -EINVAL; - return spi_nor_write_sr_and_check(nor, status_new); + return spi_nor_write_sr_and_check(nor, status_new[0]); } /* @@ -249,7 +250,8 @@ 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) { u64 min_prot_len; - int ret, status_old, status_new; + int ret; + u8 status_old[1], status_new[1]; u8 mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 pow, val; @@ -261,7 +263,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) if (ret) return ret; - status_old = nor->bouncebuf[0]; + status_old[0] = 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)) @@ -310,24 +312,24 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) return -EINVAL; } - status_new = (status_old & ~mask & ~tb_mask) | val; + status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val; /* Don't protect status register if we're fully unlocked */ if (lock_len == 0) - status_new &= ~SR_SRWD; + status_new[0] &= ~SR_SRWD; if (!use_top) - status_new |= tb_mask; + status_new[0] |= tb_mask; /* Don't bother if they're the same */ - if (status_new == status_old) + if (status_new[0] == status_old[0]) return 0; /* Only modify protection if it will not lock other areas */ - if ((status_new & mask) > (status_old & mask)) + if ((status_new[0] & mask) > (status_old[0] & mask)) return -EINVAL; - return spi_nor_write_sr_and_check(nor, status_new); + return spi_nor_write_sr_and_check(nor, status_new[0]); } /* @@ -345,7 +347,7 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len) if (ret) return ret; - return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf[0]); + return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf); } static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = { From 82877fd772f7884474add88c476108b1dd670ef6 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:35 +0200 Subject: [PATCH 23/63] mtd: spi-nor: swp: Create a helper that writes SR, CR and checks There are many helpers already to either read and/or write SR and/or CR, as well as sometimes check the returned values. In order to be able to switch from a 1 byte status register to a 2 bytes status register while keeping the same level of verification, let's introduce a new helper that writes them both (atomically) and then reads them back (separated) to compare the values. In case 2 bytes registers are not supported, we still have the usual fallback available in the helper being exported to the rest of the core. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 65 ++++++++++++++++++++++++++++++++++++++ drivers/mtd/spi-nor/core.h | 1 + 2 files changed, 66 insertions(+) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 394c27de02d6..2799c21d0b67 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -976,6 +976,54 @@ int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) 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. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs) +{ + u8 written_regs[2]; + int ret; + + written_regs[0] = regs[0]; + written_regs[1] = regs[1]; + nor->bouncebuf[0] = regs[0]; + nor->bouncebuf[1] = regs[1]; + + ret = spi_nor_write_sr(nor, nor->bouncebuf, 2); + 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->flags & SNOR_F_NO_READ_CR) + return 0; + + ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]); + if (ret) + return ret; + + if (written_regs[1] != nor->bouncebuf[1]) { + dev_dbg(nor->dev, "CR: read back test failed\n"); + return -EIO; + } + + return 0; +} + /** * 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 @@ -993,6 +1041,23 @@ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 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->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. diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 65bb4b5e1e2b..0753d0a6f8b8 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -637,6 +637,7 @@ 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); ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); From 8851f82645b42307b74dba190bee5a19cec97ae3 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:36 +0200 Subject: [PATCH 24/63] mtd: spi-nor: swp: Rename a mask "mask" is not very descriptive when we already manipulate two masks, and soon will manipulate three. Rename it "bp_mask" to align with the existing "tb_mask" and soon "cmp_mask". Signed-off-by: Miquel Raynal Reviewed-by: Tudor Ambarus Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 61830f18a147..07269e09370a 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -59,9 +59,9 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_ u64 *len) { u64 min_prot_len; - u8 mask = spi_nor_get_sr_bp_mask(nor); + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 bp, val = sr[0] & mask; + u8 bp, val = sr[0] & bp_mask; if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; @@ -162,7 +162,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) u64 min_prot_len; int ret; u8 status_old[1] = {}, status_new[1] = {}; - u8 mask = spi_nor_get_sr_bp_mask(nor); + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 pow, val; loff_t lock_len; @@ -201,7 +201,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) { - val = mask; + val = bp_mask; } else { min_prot_len = spi_nor_get_min_prot_length_sr(nor); pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; @@ -210,15 +210,15 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) val = (val & ~SR_BP3) | SR_BP3_BIT6; - if (val & ~mask) + if (val & ~bp_mask) return -EINVAL; /* Don't "lock" with no region! */ - if (!(val & mask)) + if (!(val & bp_mask)) return -EINVAL; } - status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val; + status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val; /* * Disallow further writes if WP# pin is neither left floating nor @@ -236,7 +236,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) return 0; /* Only modify protection if it will not unlock other areas */ - if ((status_new[0] & mask) < (status_old[0] & mask)) + if ((status_new[0] & bp_mask) < (status_old[0] & bp_mask)) return -EINVAL; return spi_nor_write_sr_and_check(nor, status_new[0]); @@ -252,7 +252,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) u64 min_prot_len; int ret; u8 status_old[1], status_new[1]; - u8 mask = spi_nor_get_sr_bp_mask(nor); + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 pow, val; loff_t lock_len; @@ -308,11 +308,11 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) val = (val & ~SR_BP3) | SR_BP3_BIT6; /* Some power-of-two sizes may not be supported */ - if (val & ~mask) + if (val & ~bp_mask) return -EINVAL; } - status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val; + status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val; /* Don't protect status register if we're fully unlocked */ if (lock_len == 0) @@ -326,7 +326,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) return 0; /* Only modify protection if it will not lock other areas */ - if ((status_new[0] & mask) > (status_old[0] & mask)) + if ((status_new[0] & bp_mask) > (status_old[0] & bp_mask)) return -EINVAL; return spi_nor_write_sr_and_check(nor, status_new[0]); From ba32a259dd32af911e4f4fd49cdd7f32a40ccde5 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:37 +0200 Subject: [PATCH 25/63] mtd: spi-nor: swp: Create a TB intermediate variable Ease the future reuse of the tb (Top/Bottom) boolean by creating an intermediate variable. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 07269e09370a..540cd221c455 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -62,6 +62,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_ u8 bp_mask = spi_nor_get_sr_bp_mask(nor); u8 tb_mask = spi_nor_get_sr_tb_mask(nor); u8 bp, val = sr[0] & bp_mask; + bool tb = (nor->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0; if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; @@ -81,7 +82,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_ if (*len > nor->params->size) *len = nor->params->size; - if (nor->flags & SNOR_F_HAS_SR_TB && sr[0] & tb_mask) + if (tb) *ofs = 0; else *ofs = nor->params->size - *len; From 290e83833688b4df0da746af4c5fb87dbed2834a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:38 +0200 Subject: [PATCH 26/63] mtd: spi-nor: swp: Create helpers for building the SR register The status register contains 3 or 4 BP (Block Protect) bits, 0 or 1 TB (Top/Bottom) bit, soon 0 or 1 CMP (Complement) bit. The last BP bit and the TB bit locations change between vendors. The whole logic of buildling the content of the status register based on some input conditions is used two times and soon will be used 4 times. Create dedicated helpers for these steps. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 83 ++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 540cd221c455..8a75cd59cb10 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -125,6 +125,43 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len, return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false); } +static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow) +{ + 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) + val = (val & ~SR_BP3) | SR_BP3_BIT6; + + if (val & ~mask) + return -EINVAL; + + sr[0] |= val; + + return 0; +} + +static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, + u8 pow, bool use_top) +{ + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); + u8 tb_mask = spi_nor_get_sr_tb_mask(nor); + int ret; + + new_sr[0] = old_sr[0] & ~bp_mask & ~tb_mask; + + /* Build BP field */ + ret = spi_nor_sr_set_bp_mask(nor, &new_sr[0], pow); + if (ret) + return ret; + + /* Build TB field */ + if (!use_top) + new_sr[0] |= tb_mask; + + return 0; +} + /* * Lock a region of the flash. Compatible with ST Micro and similar flash. * Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status @@ -164,11 +201,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) int ret; u8 status_old[1] = {}, status_new[1] = {}; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); - u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 pow, val; loff_t lock_len; bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; + u8 pow; ret = spi_nor_read_sr(nor, nor->bouncebuf); if (ret) @@ -202,24 +238,19 @@ 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) { - val = bp_mask; + pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); } else { min_prot_len = spi_nor_get_min_prot_length_sr(nor); pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - val = pow << SR_BP_SHIFT; - - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) - val = (val & ~SR_BP3) | SR_BP3_BIT6; - - if (val & ~bp_mask) - return -EINVAL; - - /* Don't "lock" with no region! */ - if (!(val & bp_mask)) - return -EINVAL; } - status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val; + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); + if (ret) + return ret; + + /* Don't "lock" with no region! */ + if (!(status_new[0] & bp_mask)) + return -EINVAL; /* * Disallow further writes if WP# pin is neither left floating nor @@ -229,9 +260,6 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) if (!(nor->flags & SNOR_F_NO_WP)) status_new[0] |= SR_SRWD; - if (!use_top) - status_new[0] |= tb_mask; - /* Don't bother if they're the same */ if (status_new[0] == status_old[0]) return 0; @@ -254,11 +282,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) int ret; u8 status_old[1], status_new[1]; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); - u8 tb_mask = spi_nor_get_sr_tb_mask(nor); - u8 pow, val; loff_t lock_len; bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; + u8 pow; ret = spi_nor_read_sr(nor, nor->bouncebuf); if (ret) @@ -299,29 +326,21 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) lock_len = ofs; if (lock_len == 0) { - val = 0; /* fully unlocked */ + pow = 0; /* fully unlocked */ } else { min_prot_len = spi_nor_get_min_prot_length_sr(nor); pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - val = pow << SR_BP_SHIFT; - if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3) - val = (val & ~SR_BP3) | SR_BP3_BIT6; - - /* Some power-of-two sizes may not be supported */ - if (val & ~bp_mask) - return -EINVAL; } - status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val; + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); + if (ret) + return ret; /* Don't protect status register if we're fully unlocked */ if (lock_len == 0) status_new[0] &= ~SR_SRWD; - if (!use_top) - status_new[0] |= tb_mask; - /* Don't bother if they're the same */ if (status_new[0] == status_old[0]) return 0; From c672673c4b7b82d21537cd5aa32b98f48c20a2b2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:39 +0200 Subject: [PATCH 27/63] mtd: spi-nor: swp: Simplify checking the locked/unlocked range In both the locking/unlocking steps, at the end we verify whether we do not lock/unlock more than requested (in which case an error must be returned). While being possible to do that with very simple mask comparisons, it does not scale when adding extra locking features such as the CMP possibility. In order to make these checks slightly easier to read and more future proof, use existing helpers to read the (future) status register, extract the covered range, and compare it with very usual algebric comparisons. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 8a75cd59cb10..229074d4e121 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -200,7 +200,8 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) u64 min_prot_len; int ret; u8 status_old[1] = {}, status_new[1] = {}; - u8 bp_mask = spi_nor_get_sr_bp_mask(nor); + loff_t ofs_old, ofs_new; + u64 len_old, len_new; loff_t lock_len; bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; @@ -248,10 +249,6 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) if (ret) return ret; - /* Don't "lock" with no region! */ - if (!(status_new[0] & bp_mask)) - return -EINVAL; - /* * Disallow further writes if WP# pin is neither left floating nor * wrongly tied to GND (that includes internal pull-downs). @@ -260,12 +257,20 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) if (!(nor->flags & SNOR_F_NO_WP)) status_new[0] |= SR_SRWD; + spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + + /* Don't "lock" with no region! */ + if (!len_new) + return -EINVAL; + /* Don't bother if they're the same */ if (status_new[0] == status_old[0]) return 0; /* Only modify protection if it will not unlock other areas */ - if ((status_new[0] & bp_mask) < (status_old[0] & bp_mask)) + if (len_old && + (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old))) return -EINVAL; return spi_nor_write_sr_and_check(nor, status_new[0]); @@ -281,7 +286,8 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) u64 min_prot_len; int ret; u8 status_old[1], status_new[1]; - u8 bp_mask = spi_nor_get_sr_bp_mask(nor); + loff_t ofs_old, ofs_new; + u64 len_old, len_new; loff_t lock_len; bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; @@ -346,7 +352,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) return 0; /* Only modify protection if it will not lock other areas */ - if ((status_new[0] & bp_mask) > (status_old[0] & bp_mask)) + spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + if (len_old && len_new && + (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; return spi_nor_write_sr_and_check(nor, status_new[0]); From 0e005045292dde634ed3973b5070eb2de62dc1df Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:40 +0200 Subject: [PATCH 28/63] mtd: spi-nor: swp: Cosmetic changes As a final preparation step for the introduction of CMP support, make a few more cosmetic changes to simplify the reading of the diff when adding the CMP feature. In particular, define "min_prot_len" earlier as it will be reused and move the definition of the "ret" variable at the end of the stack just because it looks better. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/swp.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 229074d4e121..2d1b1c8a535a 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -197,14 +197,14 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, */ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) { - u64 min_prot_len; - int ret; + u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); u8 status_old[1] = {}, status_new[1] = {}; loff_t ofs_old, ofs_new; u64 len_old, len_new; loff_t lock_len; bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; + int ret; u8 pow; ret = spi_nor_read_sr(nor, nor->bouncebuf); @@ -238,12 +238,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) else lock_len = ofs + len; - if (lock_len == nor->params->size) { + if (lock_len == nor->params->size) pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0); - } else { - min_prot_len = spi_nor_get_min_prot_length_sr(nor); + else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - } ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); if (ret) @@ -283,14 +281,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) { - u64 min_prot_len; - int ret; + u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); u8 status_old[1], status_new[1]; loff_t ofs_old, ofs_new; u64 len_old, len_new; loff_t lock_len; bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; bool use_top; + int ret; u8 pow; ret = spi_nor_read_sr(nor, nor->bouncebuf); @@ -331,14 +329,11 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) else lock_len = ofs; - if (lock_len == 0) { + if (lock_len == 0) pow = 0; /* fully unlocked */ - } else { - min_prot_len = spi_nor_get_min_prot_length_sr(nor); + else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - } - ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); if (ret) return ret; From b7b63475903cc9967ae53c579bc1ad9ed2519080 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:41 +0200 Subject: [PATCH 29/63] mtd: spi-nor: Create a local SR cache In order to be able to generate debugfs output without having to actually reach the flash, create a SPI NOR local cache of the status registers. What matters in our case are all the bits related to sector locking. As such, in order to make it clear that this cache is not intended to be used anywhere else, we zero the irrelevant bits. The cache is initialized once during the early init, and then maintained every time the write protection scheme is updated. Suggested-by: Michael Walle Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 4 +++- drivers/mtd/spi-nor/core.h | 1 + drivers/mtd/spi-nor/swp.c | 35 +++++++++++++++++++++++++++++++++-- include/linux/mtd/spi-nor.h | 2 ++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 2799c21d0b67..1fc71d6e0fec 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3326,10 +3326,12 @@ static int spi_nor_init(struct spi_nor *nor) * protection bits are volatile. The latter is indicated by * SNOR_F_SWP_IS_VOLATILE. */ + 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)) + nor->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 && diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 0753d0a6f8b8..cd355e94b97e 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -679,6 +679,7 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor, void spi_nor_init_default_locking_ops(struct spi_nor *nor); void spi_nor_try_unlock_all(struct spi_nor *nor); +void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr); void spi_nor_set_mtd_locking_ops(struct spi_nor *nor); void spi_nor_set_mtd_otp_ops(struct spi_nor *nor); diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 2d1b1c8a535a..cd37fec08c0e 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -162,6 +162,25 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, return 0; } +/* + * 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) +{ + u8 bp_mask = spi_nor_get_sr_bp_mask(nor); + u8 tb_mask = spi_nor_get_sr_tb_mask(nor); + + if (!sr) { + if (spi_nor_read_sr(nor, nor->bouncebuf)) + return; + + sr = nor->bouncebuf; + } + + nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD); +} + /* * Lock a region of the flash. Compatible with ST Micro and similar flash. * Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status @@ -271,7 +290,13 @@ 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; - return spi_nor_write_sr_and_check(nor, status_new[0]); + ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (ret) + return ret; + + spi_nor_cache_sr_lock_bits(nor, status_new); + + return 0; } /* @@ -353,7 +378,13 @@ 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; - return spi_nor_write_sr_and_check(nor, status_new[0]); + ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (ret) + return ret; + + spi_nor_cache_sr_lock_bits(nor, status_new); + + return 0; } /* diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 90a0cf583512..9ad77f9e76c2 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -371,6 +371,7 @@ struct spi_nor_flash_parameter; * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations * @sfdp: the SFDP data of the flash * @debugfs_root: pointer to the debugfs directory + * @dfs_sr_cache: Status Register cached value for debugfs use only * @controller_ops: SPI NOR controller driver specific operations. * @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings. * The structure includes legacy flash parameters and @@ -409,6 +410,7 @@ struct spi_nor { enum spi_nor_cmd_ext cmd_ext_type; struct sfdp *sfdp; struct dentry *debugfs_root; + u8 dfs_sr_cache[2]; const struct spi_nor_controller_ops *controller_ops; From 47eb01f46e2d0741e058006dafa1fdbbbccf9b15 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:42 +0200 Subject: [PATCH 30/63] mtd: spi-nor: debugfs: Add locking support The ioctl output may be counter intuitive in some cases. Asking for a "locked status" over a region that is only partially locked will return "unlocked" whereas in practice maybe the biggest part is actually locked. Knowing what is the real software locking state through debugfs would be very convenient for development/debugging purposes, hence this proposal for adding an extra block at the end of the file: a "locked sectors" array which lists every section, if it is locked or not, showing both the address ranges and the sizes in numbers of "lock sectors" (which on small density devices is typically different than erase blocks). Here is an example of output, what is after the "sector map" is new. $ cat /sys/kernel/debug/spi-nor/spi0.0/params name (null) id ef a0 20 00 00 00 size 64.0 MiB write size 1 page size 256 address nbytes 4 flags HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP opcodes read 0xec dummy cycles 6 erase 0xdc program 0x34 8D extension none protocols read 1S-4S-4S write 1S-1S-4S register 1S-1S-1S erase commands 21 (4.00 KiB) [1] dc (64.0 KiB) [3] c7 (64.0 MiB) sector map region (in hex) | erase mask | overlaid ------------------+------------+--------- 00000000-03ffffff | [ 3] | no locked sectors region (in hex) | status | #sectors ------------------+----------+--------- 00000000-03ffffff | unlocked | 1024 Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.h | 8 ++++++++ drivers/mtd/spi-nor/debugfs.c | 28 ++++++++++++++++++++++++++++ drivers/mtd/spi-nor/swp.c | 13 +++++++++---- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index cd355e94b97e..ce46771ecdc8 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -287,6 +287,9 @@ struct spi_nor_erase_map { * false otherwise. This feedback may be misleading because users * may get an "unlocked" status even though a subpart of the region * is effectively locked. + * + * If in doubt during development, check-out the debugfs output which tries to + * be more user friendly. */ struct spi_nor_locking_ops { int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len); @@ -678,6 +681,7 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor, 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); void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr); void spi_nor_set_mtd_locking_ops(struct spi_nor *nor); @@ -712,6 +716,10 @@ static inline bool spi_nor_needs_sfdp(const struct spi_nor *nor) return !nor->info->size; } +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); +bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr); + #ifdef CONFIG_DEBUG_FS void spi_nor_debugfs_register(struct spi_nor *nor); void spi_nor_debugfs_shutdown(void); diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index d0191eb9f879..82df8ad4176c 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include @@ -77,11 +78,14 @@ static void spi_nor_print_flags(struct seq_file *s, unsigned long flags, static int spi_nor_params_show(struct seq_file *s, void *data) { struct spi_nor *nor = s->private; + u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); 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); @@ -159,6 +163,30 @@ static int spi_nor_params_show(struct seq_file *s, void *data) region[i].overlaid ? "yes" : "no"); } + if (!spi_nor_has_default_locking_ops(nor)) + return 0; + + seq_puts(s, "\nlocked sectors\n"); + seq_puts(s, " region (in hex) | status | #sectors\n"); + seq_puts(s, " ------------------+----------+---------\n"); + + spi_nor_get_locked_range_sr(nor, nor->dfs_sr_cache, &lock_start, &lock_length); + if (!lock_length || lock_length == params->size) { + seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, params->size - 1, + lock_length ? " locked" : "unlocked", + div_u64(params->size, min_prot_len)); + } else if (!lock_start) { + seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_length - 1, + " locked", div_u64(lock_length, min_prot_len)); + seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_length, params->size - 1, + "unlocked", div_u64(params->size - lock_length, min_prot_len)); + } else { + seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_start - 1, + "unlocked", div_u64(lock_start, min_prot_len)); + seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_start, params->size - 1, + " locked", div_u64(lock_length, min_prot_len)); + } + return 0; } DEFINE_SHOW_ATTRIBUTE(spi_nor_params); diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index cd37fec08c0e..2411d8f1012d 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -34,7 +34,7 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) return 0; } -static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) +u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) { unsigned int bp_slots, bp_slots_needed; /* @@ -55,8 +55,8 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) return sector_size; } -static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, - u64 *len) +void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, + u64 *len) { u64 min_prot_len; u8 bp_mask = spi_nor_get_sr_bp_mask(nor); @@ -114,7 +114,7 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, return (ofs >= lock_offs_max) || (offs_max <= lock_offs); } -static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr) +bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr) { return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true); } @@ -416,6 +416,11 @@ void spi_nor_init_default_locking_ops(struct spi_nor *nor) nor->params->locking_ops = &spi_nor_sr_locking_ops; } +bool spi_nor_has_default_locking_ops(struct spi_nor *nor) +{ + return nor->params->locking_ops == &spi_nor_sr_locking_ops; +} + static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, u64 len) { struct spi_nor *nor = mtd_to_spi_nor(mtd); From f05f0d62c5c6b981315bd10017ec98504165e355 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:43 +0200 Subject: [PATCH 31/63] mtd: spi-nor: debugfs: Add a locked sectors map In order to get a very clear view of the sectors being locked, besides the `params` output giving the ranges, we may want to see a proper map of the sectors and for each of them, their status. Depending on the use case, this map may be easier to parse by humans and gives a more acurate feeling of the situation. At least myself, for the few locking-related developments I recently went through, I found it very useful to get a clearer mental model of what was locked/unlocked. Here is an example of output: $ cat /sys/kernel/debug/spi-nor/spi0.0/locked-sectors-map Locked sectors map (x: locked, .: unlocked, unit: 64kiB) 0x00000000 (# 0): ................ ................ ................ ................ 0x00400000 (# 64): ................ ................ ................ ................ 0x00800000 (# 128): ................ ................ ................ ................ 0x00c00000 (# 192): ................ ................ ................ ................ 0x01000000 (# 256): ................ ................ ................ ................ 0x01400000 (# 320): ................ ................ ................ ................ 0x01800000 (# 384): ................ ................ ................ ................ 0x01c00000 (# 448): ................ ................ ................ ................ 0x02000000 (# 512): ................ ................ ................ ................ 0x02400000 (# 576): ................ ................ ................ ................ 0x02800000 (# 640): ................ ................ ................ ................ 0x02c00000 (# 704): ................ ................ ................ ................ 0x03000000 (# 768): ................ ................ ................ ................ 0x03400000 (# 832): ................ ................ ................ ................ 0x03800000 (# 896): ................ ................ ................ ................ 0x03c00000 (# 960): ................ ................ ................ ..............xx The output is wrapped at 64 sectors, spaces every 16 sectors are improving the readability, every line starts by the first sector offset (hex) and number (decimal). Signed-off-by: Miquel Raynal [pratyush@kernel.org: split the debugfs_create_file() into two lines] Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/debugfs.c | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index 82df8ad4176c..ef710bd4f307 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -191,6 +191,41 @@ static int spi_nor_params_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(spi_nor_params); +static int spi_nor_locked_sectors_map_show(struct seq_file *s, void *data) +{ + struct spi_nor *nor = s->private; + struct spi_nor_flash_parameter *params = nor->params; + u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); + unsigned int sector = 0; + u64 offset = 0; + bool locked; + int i; + + seq_printf(s, "Locked sectors map (x: locked, .: unlocked, unit: %lldkiB)\n", + min_prot_len / 1024); + while (offset < params->size) { + seq_printf(s, " 0x%08llx (#%5d): ", offset, sector); + for (i = 0; i < 64 && offset < params->size; i++) { + locked = spi_nor_is_locked_sr(nor, offset, min_prot_len, + nor->dfs_sr_cache); + if (locked) + seq_puts(s, "x"); + else + seq_puts(s, "."); + + if (((i + 1) % 16) == 0) + seq_puts(s, " "); + + offset += min_prot_len; + sector++; + } + seq_puts(s, "\n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(spi_nor_locked_sectors_map); + static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, struct spi_nor_read_command *cmd) { @@ -276,6 +311,9 @@ void spi_nor_debugfs_register(struct spi_nor *nor) debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops); debugfs_create_file("capabilities", 0444, d, nor, &spi_nor_capabilities_fops); + if (spi_nor_has_default_locking_ops(nor)) + debugfs_create_file("locked-sectors-map", 0444, d, nor, + &spi_nor_locked_sectors_map_fops); } void spi_nor_debugfs_shutdown(void) From 8507c2cc9e4fa402401819f44d1e8a5ef4d11d8b Mon Sep 17 00:00:00 2001 From: Arseniy Krasnov Date: Tue, 5 May 2026 11:30:30 +0300 Subject: [PATCH 32/63] mtd: rawnand: fix condition in 'nand_select_target()' 'cs' here must be in range [0:nanddev_ntargets[. Cc: stable@vger.kernel.org Fixes: 32813e288414 ("mtd: rawnand: Get rid of chip->numchips") Signed-off-by: Arseniy Krasnov Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/nand_base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index d6d3e17ab407..e4e1383bd5e1 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -175,7 +175,7 @@ void nand_select_target(struct nand_chip *chip, unsigned int cs) * cs should always lie between 0 and nanddev_ntargets(), when that's * not the case it's a bug and the caller should be fixed. */ - if (WARN_ON(cs > nanddev_ntargets(&chip->base))) + if (WARN_ON(cs >= nanddev_ntargets(&chip->base))) return; chip->cur_cs = cs; From 801f11633d5eb2021dd1a4b6a4c14479c54af244 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 6 May 2026 15:42:47 +0100 Subject: [PATCH 33/63] mtd: ts5500_flash: Remove mapping since board is no longer supported Since commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"), this board is no longer supported. Remove the mtd map too. Signed-off-by: Sean Young Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 18 ------ drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/ts5500_flash.c | 108 -------------------------------- 3 files changed, 127 deletions(-) delete mode 100644 drivers/mtd/maps/ts5500_flash.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 99d5ff9a1fbe..e48221e97f8d 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -141,24 +141,6 @@ config MTD_NETSC520 demonstration board. If you have one of these boards and would like to use the flash chips on it, say 'Y'. -config MTD_TS5500 - tristate "JEDEC Flash device mapped on Technologic Systems TS-5500" - depends on TS5500 || COMPILE_TEST - select MTD_JEDECPROBE - select MTD_CFI_AMDSTD - help - This provides a driver for the on-board flash of the Technologic - System's TS-5500 board. The 2MB flash is split into 3 partitions - which are accessed as separate MTD devices. - - mtd0 and mtd2 are the two BIOS drives, which use the resident - flash disk (RFD) flash translation layer. - - mtd1 allows you to reprogram your BIOS. BE VERY CAREFUL. - - Note that jumper 3 ("Write Enable Drive A") must be set - otherwise detection won't succeed. - config MTD_SBC_GXX tristate "CFI Flash device mapped on Arcom SBC-GXx boards" depends on X86 && MTD_CFI_INTELEXT && MTD_COMPLEX_MAPPINGS diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 51af1d2ebd52..46ff278006a7 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -28,7 +28,6 @@ obj-$(CONFIG_MTD_SA1100) += sa1100-flash.o obj-$(CONFIG_MTD_SBC_GXX) += sbc_gxx.o obj-$(CONFIG_MTD_SC520CDP) += sc520cdp.o obj-$(CONFIG_MTD_NETSC520) += netsc520.o -obj-$(CONFIG_MTD_TS5500) += ts5500_flash.o obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c deleted file mode 100644 index 70d6e865f555..000000000000 --- a/drivers/mtd/maps/ts5500_flash.c +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * ts5500_flash.c -- MTD map driver for Technology Systems TS-5500 board - * - * Copyright (C) 2004 Sean Young - * - * Note: - * - In order for detection to work, jumper 3 must be set. - * - Drive A and B use the resident flash disk (RFD) flash translation layer. - * - If you have created your own jffs file system and the bios overwrites - * it during boot, try disabling Drive A: and B: in the boot order. - */ - -#include -#include -#include -#include -#include -#include -#include - - -#define WINDOW_ADDR 0x09400000 -#define WINDOW_SIZE 0x00200000 - -static struct map_info ts5500_map = { - .name = "TS-5500 Flash", - .size = WINDOW_SIZE, - .bankwidth = 1, - .phys = WINDOW_ADDR -}; - -static const struct mtd_partition ts5500_partitions[] = { - { - .name = "Drive A", - .offset = 0, - .size = 0x0e0000 - }, - { - .name = "BIOS", - .offset = 0x0e0000, - .size = 0x020000, - }, - { - .name = "Drive B", - .offset = 0x100000, - .size = 0x100000 - } -}; - -#define NUM_PARTITIONS ARRAY_SIZE(ts5500_partitions) - -static struct mtd_info *mymtd; - -static int __init init_ts5500_map(void) -{ - int rc = 0; - - ts5500_map.virt = ioremap(ts5500_map.phys, ts5500_map.size); - - if (!ts5500_map.virt) { - printk(KERN_ERR "Failed to ioremap\n"); - rc = -EIO; - goto err2; - } - - simple_map_init(&ts5500_map); - - mymtd = do_map_probe("jedec_probe", &ts5500_map); - if (!mymtd) - mymtd = do_map_probe("map_rom", &ts5500_map); - - if (!mymtd) { - rc = -ENXIO; - goto err1; - } - - mymtd->owner = THIS_MODULE; - mtd_device_register(mymtd, ts5500_partitions, NUM_PARTITIONS); - - return 0; - -err1: - iounmap(ts5500_map.virt); -err2: - return rc; -} - -static void __exit cleanup_ts5500_map(void) -{ - if (mymtd) { - mtd_device_unregister(mymtd); - map_destroy(mymtd); - } - - if (ts5500_map.virt) { - iounmap(ts5500_map.virt); - ts5500_map.virt = NULL; - } -} - -module_init(init_ts5500_map); -module_exit(cleanup_ts5500_map); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sean Young "); -MODULE_DESCRIPTION("MTD map driver for Technology Systems TS-5500 board"); - From b60e5b5738d86735ebdb8b952054194b636b1cf6 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 6 May 2026 15:42:48 +0100 Subject: [PATCH 34/63] mtd: netsc520: Remove mapping since board is no longer supported Since commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"), this board is no longer supported. Remove the mtd map too. Signed-off-by: Sean Young Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 8 --- drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/netsc520.c | 127 ------------------------------------ 3 files changed, 136 deletions(-) delete mode 100644 drivers/mtd/maps/netsc520.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index e48221e97f8d..b34f3c34c626 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -133,14 +133,6 @@ config MTD_SC520CDP Dual-in-line JEDEC chip. This 'mapping' driver supports that arrangement, implementing three MTD devices. -config MTD_NETSC520 - tristate "CFI Flash device mapped on AMD NetSc520" - depends on (MELAN || COMPILE_TEST) && MTD_CFI - help - This enables access routines for the flash chips on the AMD NetSc520 - demonstration board. If you have one of these boards and would like - to use the flash chips on it, say 'Y'. - config MTD_SBC_GXX tristate "CFI Flash device mapped on Arcom SBC-GXx boards" depends on X86 && MTD_CFI_INTELEXT && MTD_COMPLEX_MAPPINGS diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 46ff278006a7..e8b5caa17ccc 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -27,7 +27,6 @@ obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o obj-$(CONFIG_MTD_SA1100) += sa1100-flash.o obj-$(CONFIG_MTD_SBC_GXX) += sbc_gxx.o obj-$(CONFIG_MTD_SC520CDP) += sc520cdp.o -obj-$(CONFIG_MTD_NETSC520) += netsc520.o obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o diff --git a/drivers/mtd/maps/netsc520.c b/drivers/mtd/maps/netsc520.c deleted file mode 100644 index 0bb651624f05..000000000000 --- a/drivers/mtd/maps/netsc520.c +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* netsc520.c -- MTD map driver for AMD NetSc520 Demonstration Board - * - * Copyright (C) 2001 Mark Langsdorf (mark.langsdorf@amd.com) - * based on sc520cdp.c by Sysgo Real-Time Solutions GmbH - * - * The NetSc520 is a demonstration board for the Elan Sc520 processor available - * from AMD. It has a single back of 16 megs of 32-bit Flash ROM and another - * 16 megs of SDRAM. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - - -/* -** The single, 16 megabyte flash bank is divided into four virtual -** partitions. The first partition is 768 KiB and is intended to -** store the kernel image loaded by the bootstrap loader. The second -** partition is 256 KiB and holds the BIOS image. The third -** partition is 14.5 MiB and is intended for the flash file system -** image. The last partition is 512 KiB and contains another copy -** of the BIOS image and the reset vector. -** -** Only the third partition should be mounted. The first partition -** should not be mounted, but it can erased and written to using the -** MTD character routines. The second and fourth partitions should -** not be touched - it is possible to corrupt the BIOS image by -** mounting these partitions, and potentially the board will not be -** recoverable afterwards. -*/ - -/* partition_info gives details on the logical partitions that the split the - * single flash device into. If the size if zero we use up to the end of the - * device. */ -static const struct mtd_partition partition_info[] = { - { - .name = "NetSc520 boot kernel", - .offset = 0, - .size = 0xc0000 - }, - { - .name = "NetSc520 Low BIOS", - .offset = 0xc0000, - .size = 0x40000 - }, - { - .name = "NetSc520 file system", - .offset = 0x100000, - .size = 0xe80000 - }, - { - .name = "NetSc520 High BIOS", - .offset = 0xf80000, - .size = 0x80000 - }, -}; -#define NUM_PARTITIONS ARRAY_SIZE(partition_info) - -#define WINDOW_SIZE 0x00100000 -#define WINDOW_ADDR 0x00200000 - -static struct map_info netsc520_map = { - .name = "netsc520 Flash Bank", - .size = WINDOW_SIZE, - .bankwidth = 4, - .phys = WINDOW_ADDR, -}; - -#define NUM_FLASH_BANKS ARRAY_SIZE(netsc520_map) - -static struct mtd_info *mymtd; - -static int __init init_netsc520(void) -{ - printk(KERN_NOTICE "NetSc520 flash device: 0x%Lx at 0x%Lx\n", - (unsigned long long)netsc520_map.size, - (unsigned long long)netsc520_map.phys); - netsc520_map.virt = ioremap(netsc520_map.phys, netsc520_map.size); - - if (!netsc520_map.virt) { - printk("Failed to ioremap\n"); - return -EIO; - } - - simple_map_init(&netsc520_map); - - mymtd = do_map_probe("cfi_probe", &netsc520_map); - if(!mymtd) - mymtd = do_map_probe("map_ram", &netsc520_map); - if(!mymtd) - mymtd = do_map_probe("map_rom", &netsc520_map); - - if (!mymtd) { - iounmap(netsc520_map.virt); - return -ENXIO; - } - - mymtd->owner = THIS_MODULE; - mtd_device_register(mymtd, partition_info, NUM_PARTITIONS); - return 0; -} - -static void __exit cleanup_netsc520(void) -{ - if (mymtd) { - mtd_device_unregister(mymtd); - map_destroy(mymtd); - } - if (netsc520_map.virt) { - iounmap(netsc520_map.virt); - netsc520_map.virt = NULL; - } -} - -module_init(init_netsc520); -module_exit(cleanup_netsc520); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Mark Langsdorf "); -MODULE_DESCRIPTION("MTD map driver for AMD NetSc520 Demonstration Board"); From f4aeb151b8b025202a3cc55fabc45234700ed470 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 6 May 2026 15:42:49 +0100 Subject: [PATCH 35/63] mtd: sc520cdp: Remove mapping since board is no longer supported Since commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"), this board is no longer supported. Remove the mtd map too. Signed-off-by: Sean Young Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 8 - drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/sc520cdp.c | 294 ------------------------------------ 3 files changed, 303 deletions(-) delete mode 100644 drivers/mtd/maps/sc520cdp.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index b34f3c34c626..db63b5af4b39 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -125,14 +125,6 @@ config MTD_SUN_UFLASH Sun Microsystems boardsets. This driver will require CFI support in the kernel, so if you did not enable CFI previously, do that now. -config MTD_SC520CDP - tristate "CFI Flash device mapped on AMD SC520 CDP" - depends on (MELAN || COMPILE_TEST) && MTD_CFI - help - The SC520 CDP board has two banks of CFI-compliant chips and one - Dual-in-line JEDEC chip. This 'mapping' driver supports that - arrangement, implementing three MTD devices. - config MTD_SBC_GXX tristate "CFI Flash device mapped on Arcom SBC-GXx boards" depends on X86 && MTD_CFI_INTELEXT && MTD_COMPLEX_MAPPINGS diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index e8b5caa17ccc..c545d1e68e91 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -26,7 +26,6 @@ obj-$(CONFIG_MTD_PISMO) += pismo.o obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o obj-$(CONFIG_MTD_SA1100) += sa1100-flash.o obj-$(CONFIG_MTD_SBC_GXX) += sbc_gxx.o -obj-$(CONFIG_MTD_SC520CDP) += sc520cdp.o obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o diff --git a/drivers/mtd/maps/sc520cdp.c b/drivers/mtd/maps/sc520cdp.c deleted file mode 100644 index 8ef7aec634c7..000000000000 --- a/drivers/mtd/maps/sc520cdp.c +++ /dev/null @@ -1,294 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* sc520cdp.c -- MTD map driver for AMD SC520 Customer Development Platform - * - * Copyright (C) 2001 Sysgo Real-Time Solutions GmbH - * - * The SC520CDP is an evaluation board for the Elan SC520 processor available - * from AMD. It has two banks of 32-bit Flash ROM, each 8 Megabytes in size, - * and up to 512 KiB of 8-bit DIL Flash ROM. - * For details see https://www.amd.com/products/epd/desiging/evalboards/18.elansc520/520_cdp_brief/index.html - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* -** The Embedded Systems BIOS decodes the first FLASH starting at -** 0x8400000. This is a *terrible* place for it because accessing -** the flash at this location causes the A22 address line to be high -** (that's what 0x8400000 binary's ought to be). But this is the highest -** order address line on the raw flash devices themselves!! -** This causes the top HALF of the flash to be accessed first. Beyond -** the physical limits of the flash, the flash chip aliases over (to -** 0x880000 which causes the bottom half to be accessed. This splits the -** flash into two and inverts it! If you then try to access this from another -** program that does NOT do this insanity, then you *will* access the -** first half of the flash, but not find what you expect there. That -** stuff is in the *second* half! Similarly, the address used by the -** BIOS for the second FLASH bank is also quite a bad choice. -** If REPROGRAM_PAR is defined below (the default), then this driver will -** choose more useful addresses for the FLASH banks by reprogramming the -** responsible PARxx registers in the SC520's MMCR region. This will -** cause the settings to be incompatible with the BIOS's settings, which -** shouldn't be a problem since you are running Linux, (i.e. the BIOS is -** not much use anyway). However, if you need to be compatible with -** the BIOS for some reason, just undefine REPROGRAM_PAR. -*/ -#define REPROGRAM_PAR - - - -#ifdef REPROGRAM_PAR - -/* These are the addresses we want.. */ -#define WINDOW_ADDR_0 0x08800000 -#define WINDOW_ADDR_1 0x09000000 -#define WINDOW_ADDR_2 0x09800000 - -/* .. and these are the addresses the BIOS gives us */ -#define WINDOW_ADDR_0_BIOS 0x08400000 -#define WINDOW_ADDR_1_BIOS 0x08c00000 -#define WINDOW_ADDR_2_BIOS 0x09400000 - -#else - -#define WINDOW_ADDR_0 0x08400000 -#define WINDOW_ADDR_1 0x08C00000 -#define WINDOW_ADDR_2 0x09400000 - -#endif - -#define WINDOW_SIZE_0 0x00800000 -#define WINDOW_SIZE_1 0x00800000 -#define WINDOW_SIZE_2 0x00080000 - - -static struct map_info sc520cdp_map[] = { - { - .name = "SC520CDP Flash Bank #0", - .size = WINDOW_SIZE_0, - .bankwidth = 4, - .phys = WINDOW_ADDR_0 - }, - { - .name = "SC520CDP Flash Bank #1", - .size = WINDOW_SIZE_1, - .bankwidth = 4, - .phys = WINDOW_ADDR_1 - }, - { - .name = "SC520CDP DIL Flash", - .size = WINDOW_SIZE_2, - .bankwidth = 1, - .phys = WINDOW_ADDR_2 - }, -}; - -#define NUM_FLASH_BANKS ARRAY_SIZE(sc520cdp_map) - -static struct mtd_info *mymtd[NUM_FLASH_BANKS]; -static struct mtd_info *merged_mtd; - -#ifdef REPROGRAM_PAR - -/* -** The SC520 MMCR (memory mapped control register) region resides -** at 0xFFFEF000. The 16 Programmable Address Region (PAR) registers -** are at offset 0x88 in the MMCR: -*/ -#define SC520_MMCR_BASE 0xFFFEF000 -#define SC520_MMCR_EXTENT 0x1000 -#define SC520_PAR(x) ((0x88/sizeof(unsigned long)) + (x)) -#define NUM_SC520_PAR 16 /* total number of PAR registers */ - -/* -** The highest three bits in a PAR register determine what target -** device is controlled by this PAR. Here, only ROMCS? and BOOTCS -** devices are of interest. -*/ -#define SC520_PAR_BOOTCS (0x4<<29) -#define SC520_PAR_ROMCS0 (0x5<<29) -#define SC520_PAR_ROMCS1 (0x6<<29) -#define SC520_PAR_TRGDEV (0x7<<29) - -/* -** Bits 28 thru 26 determine some attributes for the -** region controlled by the PAR. (We only use non-cacheable) -*/ -#define SC520_PAR_WRPROT (1<<26) /* write protected */ -#define SC520_PAR_NOCACHE (1<<27) /* non-cacheable */ -#define SC520_PAR_NOEXEC (1<<28) /* code execution denied */ - - -/* -** Bit 25 determines the granularity: 4K or 64K -*/ -#define SC520_PAR_PG_SIZ4 (0<<25) -#define SC520_PAR_PG_SIZ64 (1<<25) - -/* -** Build a value to be written into a PAR register. -** We only need ROM entries, 64K page size: -*/ -#define SC520_PAR_ENTRY(trgdev, address, size) \ - ((trgdev) | SC520_PAR_NOCACHE | SC520_PAR_PG_SIZ64 | \ - (address) >> 16 | (((size) >> 16) - 1) << 14) - -struct sc520_par_table -{ - unsigned long trgdev; - unsigned long new_par; - unsigned long default_address; -}; - -static const struct sc520_par_table par_table[NUM_FLASH_BANKS] = -{ - { /* Flash Bank #0: selected by ROMCS0 */ - SC520_PAR_ROMCS0, - SC520_PAR_ENTRY(SC520_PAR_ROMCS0, WINDOW_ADDR_0, WINDOW_SIZE_0), - WINDOW_ADDR_0_BIOS - }, - { /* Flash Bank #1: selected by ROMCS1 */ - SC520_PAR_ROMCS1, - SC520_PAR_ENTRY(SC520_PAR_ROMCS1, WINDOW_ADDR_1, WINDOW_SIZE_1), - WINDOW_ADDR_1_BIOS - }, - { /* DIL (BIOS) Flash: selected by BOOTCS */ - SC520_PAR_BOOTCS, - SC520_PAR_ENTRY(SC520_PAR_BOOTCS, WINDOW_ADDR_2, WINDOW_SIZE_2), - WINDOW_ADDR_2_BIOS - } -}; - - -static void sc520cdp_setup_par(void) -{ - unsigned long __iomem *mmcr; - unsigned long mmcr_val; - int i, j; - - /* map in SC520's MMCR area */ - mmcr = ioremap(SC520_MMCR_BASE, SC520_MMCR_EXTENT); - if(!mmcr) { /* ioremap failed: skip the PAR reprogramming */ - /* force physical address fields to BIOS defaults: */ - for(i = 0; i < NUM_FLASH_BANKS; i++) - sc520cdp_map[i].phys = par_table[i].default_address; - return; - } - - /* - ** Find the PARxx registers that are responsible for activating - ** ROMCS0, ROMCS1 and BOOTCS. Reprogram each of these with a - ** new value from the table. - */ - for(i = 0; i < NUM_FLASH_BANKS; i++) { /* for each par_table entry */ - for(j = 0; j < NUM_SC520_PAR; j++) { /* for each PAR register */ - mmcr_val = readl(&mmcr[SC520_PAR(j)]); - /* if target device field matches, reprogram the PAR */ - if((mmcr_val & SC520_PAR_TRGDEV) == par_table[i].trgdev) - { - writel(par_table[i].new_par, &mmcr[SC520_PAR(j)]); - break; - } - } - if(j == NUM_SC520_PAR) - { /* no matching PAR found: try default BIOS address */ - printk(KERN_NOTICE "Could not find PAR responsible for %s\n", - sc520cdp_map[i].name); - printk(KERN_NOTICE "Trying default address 0x%lx\n", - par_table[i].default_address); - sc520cdp_map[i].phys = par_table[i].default_address; - } - } - iounmap(mmcr); -} -#endif - - -static int __init init_sc520cdp(void) -{ - int i, j, devices_found = 0; - -#ifdef REPROGRAM_PAR - /* reprogram PAR registers so flash appears at the desired addresses */ - sc520cdp_setup_par(); -#endif - - for (i = 0; i < NUM_FLASH_BANKS; i++) { - printk(KERN_NOTICE "SC520 CDP flash device: 0x%Lx at 0x%Lx\n", - (unsigned long long)sc520cdp_map[i].size, - (unsigned long long)sc520cdp_map[i].phys); - - sc520cdp_map[i].virt = ioremap(sc520cdp_map[i].phys, sc520cdp_map[i].size); - - if (!sc520cdp_map[i].virt) { - printk("Failed to ioremap\n"); - for (j = 0; j < i; j++) { - if (mymtd[j]) { - map_destroy(mymtd[j]); - iounmap(sc520cdp_map[j].virt); - } - } - return -EIO; - } - - simple_map_init(&sc520cdp_map[i]); - - mymtd[i] = do_map_probe("cfi_probe", &sc520cdp_map[i]); - if(!mymtd[i]) - mymtd[i] = do_map_probe("jedec_probe", &sc520cdp_map[i]); - if(!mymtd[i]) - mymtd[i] = do_map_probe("map_rom", &sc520cdp_map[i]); - - if (mymtd[i]) { - mymtd[i]->owner = THIS_MODULE; - ++devices_found; - } - else { - iounmap(sc520cdp_map[i].virt); - } - } - if(devices_found >= 2) { - /* Combine the two flash banks into a single MTD device & register it: */ - merged_mtd = mtd_concat_create(mymtd, 2, "SC520CDP Flash Banks #0 and #1"); - if(merged_mtd) - mtd_device_register(merged_mtd, NULL, 0); - } - if(devices_found == 3) /* register the third (DIL-Flash) device */ - mtd_device_register(mymtd[2], NULL, 0); - return(devices_found ? 0 : -ENXIO); -} - -static void __exit cleanup_sc520cdp(void) -{ - int i; - - if (merged_mtd) { - mtd_device_unregister(merged_mtd); - mtd_concat_destroy(merged_mtd); - } - if (mymtd[2]) - mtd_device_unregister(mymtd[2]); - - for (i = 0; i < NUM_FLASH_BANKS; i++) { - if (mymtd[i]) - map_destroy(mymtd[i]); - if (sc520cdp_map[i].virt) { - iounmap(sc520cdp_map[i].virt); - sc520cdp_map[i].virt = NULL; - } - } -} - -module_init(init_sc520cdp); -module_exit(cleanup_sc520cdp); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sysgo Real-Time Solutions GmbH"); -MODULE_DESCRIPTION("MTD map driver for AMD SC520 Customer Development Platform"); From cf496ebf1380bde700c5d1790c31919eea2f4851 Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Tue, 5 May 2026 09:34:51 +0800 Subject: [PATCH 36/63] dt-bindings: mtd: nand: Add nand-randomizer property Add the 'nand-randomizer' property to control the data randomizer feature. This is used to improve data reliability by reducing cell-to-cell interference. Depending on the hardware architecture, this property is designed to be generic and can apply to either the NAND chip's internal randomizer or the hardware randomizer engine embedded in the NAND host controller. This property is defined as a uint32 enum (0 or 1) instead of a simple boolean. This design choice explicitly supports the "not present" case. If the property is omitted, the driver will not interfere and will leave the randomizer in its current state (e.g., as already configured by the bootloader or hardware default). Signed-off-by: Cheng Ming Lin Reviewed-by: Rob Herring (Arm) Signed-off-by: Miquel Raynal --- Documentation/devicetree/bindings/mtd/nand-chip.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/mtd/nand-chip.yaml b/Documentation/devicetree/bindings/mtd/nand-chip.yaml index 8800d1d07266..effdc4f99017 100644 --- a/Documentation/devicetree/bindings/mtd/nand-chip.yaml +++ b/Documentation/devicetree/bindings/mtd/nand-chip.yaml @@ -23,6 +23,15 @@ properties: description: Contains the chip-select IDs. + nand-randomizer: + description: | + Control the data randomizer feature. + 0: Disable randomizer. + 1: Enable randomizer. + If absent, the current hardware state is left unchanged. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + required: - reg From 54e1bc80af0c993afc6a2283722f8d4535b10d40 Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Tue, 5 May 2026 09:34:52 +0800 Subject: [PATCH 37/63] mtd: spinand: Add support for randomizer This patch adds support for the randomizer feature. It introduces a 'set_randomizer' callback in 'struct spinand_info' and 'struct spinand_device'. If a driver implements this callback, the core will invoke it during device initialization (spinand_init) to enable or disable the randomizer feature based on the device tree configuration. Signed-off-by: Cheng Ming Lin Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 20 ++++++++++++++++++++ include/linux/mtd/spinand.h | 9 +++++++++ 2 files changed, 29 insertions(+) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index f1084d5e04b9..f86786344d52 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1328,6 +1328,22 @@ static int spinand_create_dirmaps(struct spinand_device *spinand) return 0; } +static int spinand_randomizer_init(struct spinand_device *spinand) +{ + struct device_node *np = spinand->spimem->spi->dev.of_node; + u32 rand_val; + int ret; + + if (!spinand->set_randomizer) + return 0; + + ret = of_property_read_u32(np, "nand-randomizer", &rand_val); + if (ret) + return 0; + + return spinand->set_randomizer(spinand, rand_val); +} + static const struct nand_ops spinand_ops = { .erase = spinand_erase, .markbad = spinand_markbad, @@ -1619,6 +1635,7 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->user_otp = &table[i].user_otp; spinand->read_retries = table[i].read_retries; spinand->set_read_retry = table[i].set_read_retry; + spinand->set_randomizer = table[i].set_randomizer; /* I/O variants selection with single-spi SDR commands */ @@ -1942,6 +1959,9 @@ static int spinand_init(struct spinand_device *spinand) * ECC initialization must have happened previously. */ spinand_cont_read_init(spinand); + ret = spinand_randomizer_init(spinand); + if (ret) + goto err_cleanup_nanddev; mtd->_read_oob = spinand_mtd_read; mtd->_write_oob = spinand_mtd_write; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 44f4347104d6..ec6efcfeef83 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -593,6 +593,7 @@ enum spinand_bus_interface { * @user_otp: SPI NAND user OTP info. * @read_retries: the number of read retry modes supported * @set_read_retry: enable/disable read retry for data recovery + * @set_randomizer: enable/disable randomizer support * * Each SPI NAND manufacturer driver should have a spinand_info table * describing all the chips supported by the driver. @@ -622,6 +623,8 @@ struct spinand_info { unsigned int read_retries; int (*set_read_retry)(struct spinand_device *spinand, unsigned int read_retry); + int (*set_randomizer)(struct spinand_device *spinand, + bool enable); }; #define SPINAND_ID(__method, ...) \ @@ -686,6 +689,9 @@ struct spinand_info { .read_retries = __read_retries, \ .set_read_retry = __set_read_retry +#define SPINAND_RANDOMIZER(__set_randomizer) \ + .set_randomizer = __set_randomizer + #define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \ __flags, ...) \ { \ @@ -771,6 +777,7 @@ struct spinand_mem_ops { * @user_otp: SPI NAND user OTP info. * @read_retries: the number of read retry modes supported * @set_read_retry: Enable/disable the read retry feature + * @set_randomizer: Enable/disable the randomizer feature */ struct spinand_device { struct nand_device base; @@ -804,6 +811,8 @@ struct spinand_device { bool cont_read_possible; int (*set_cont_read)(struct spinand_device *spinand, bool enable); + int (*set_randomizer)(struct spinand_device *spinand, + bool enable); const struct spinand_fact_otp *fact_otp; const struct spinand_user_otp *user_otp; From 8ac45f4ff182586bb06fd9b2dfa0dff2af0734a1 Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Tue, 5 May 2026 09:34:53 +0800 Subject: [PATCH 38/63] mtd: spinand: macronix: Enable randomizer support Implement the 'set_randomizer' callback for Macronix SPI NAND chips. The randomizer is enabled by setting bit 1 of the Configuration Register (address 0x10). This patch adds support for the following chips: - MX35LFxG24AD series - MX35UFxG24AD series When the randomizer is enabled, data is scrambled internally during program operations and automatically descrambled during read operations. This helps reduce bit errors caused by program disturbance. Please refer to the following link for randomizer feature: Link: https://www.mxic.com.tw/Lists/ApplicationNote/Attachments/2151/AN1051V1-The%20Introduction%20of%20Randomizer%20Feature%20on%20MX30xFxG28AD_MX35xFxG24AD.pdf Signed-off-by: Cheng Ming Lin Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/macronix.c | 38 ++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 67cafa1bb8ef..7dfcc34e9b72 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -14,6 +14,8 @@ #define MACRONIX_ECCSR_BF_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr) #define MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(eccsr) FIELD_GET(GENMASK(7, 4), eccsr) #define MACRONIX_CFG_CONT_READ BIT(2) +#define MACRONIX_CFG_RANDOMIZER_EN BIT(1) +#define MACRONIX_FEATURE_ADDR_RANDOMIZER 0x10 #define MACRONIX_FEATURE_ADDR_READ_RETRY 0x70 #define MACRONIX_NUM_READ_RETRY_MODES 5 @@ -170,6 +172,12 @@ static int macronix_set_read_retry(struct spinand_device *spinand, return spi_mem_exec_op(spinand->spimem, &op); } +static int macronix_set_randomizer(struct spinand_device *spinand, bool enable) +{ + return spinand_write_reg_op(spinand, MACRONIX_FEATURE_ADDR_RANDOMIZER, + enable ? MACRONIX_CFG_RANDOMIZER_EN : 0); +} + static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO("MX35LF1GE4AB", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12), @@ -231,7 +239,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35LF2G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), @@ -243,7 +252,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35LF2G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x64, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -254,7 +264,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35LF4G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), @@ -266,7 +277,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35LF4G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x75, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -277,7 +289,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX31LF1GE4BC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x1e), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -327,7 +340,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35UF4G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xf5, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -340,7 +354,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35UF4GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -381,7 +396,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35UF2G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe4, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -394,7 +410,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35UF2GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -444,7 +461,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, - macronix_set_read_retry)), + macronix_set_read_retry), + SPINAND_RANDOMIZER(macronix_set_randomizer)), SPINAND_INFO("MX35UF1GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), From 79d1661502c6e4b6f626185cef72cf2fa78116e1 Mon Sep 17 00:00:00 2001 From: Florian Fuchs Date: Mon, 18 May 2026 13:45:20 +0200 Subject: [PATCH 39/63] mtd: maps: vmu-flash: fix fault in unaligned fixup Use kzalloc_obj() / kzalloc_objs() to allocate the memcard structs, instead of kmalloc_obj() / kmalloc_objs() to prevent access to uninitialized data. Fixes runtime error: Fault in unaligned fixup: 0000 [#1] at mtd_get_fact_prot_info. Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.") Cc: stable@vger.kernel.org Signed-off-by: Florian Fuchs Signed-off-by: Miquel Raynal --- drivers/mtd/maps/vmu-flash.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/maps/vmu-flash.c b/drivers/mtd/maps/vmu-flash.c index 75e06d249ce9..b76d0609d1b7 100644 --- a/drivers/mtd/maps/vmu-flash.c +++ b/drivers/mtd/maps/vmu-flash.c @@ -609,7 +609,7 @@ static int vmu_connect(struct maple_device *mdev) basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]); - card = kmalloc_obj(struct memcard); + card = kzalloc_obj(struct memcard); if (!card) { error = -ENOMEM; goto fail_nomem; @@ -627,13 +627,13 @@ static int vmu_connect(struct maple_device *mdev) * Not sure there are actually any multi-partition devices in the * real world, but the hardware supports them, so, so will we */ - card->parts = kmalloc_objs(struct vmupart, card->partitions); + card->parts = kzalloc_objs(struct vmupart, card->partitions); if (!card->parts) { error = -ENOMEM; goto fail_partitions; } - card->mtd = kmalloc_objs(struct mtd_info, card->partitions); + card->mtd = kzalloc_objs(struct mtd_info, card->partitions); if (!card->mtd) { error = -ENOMEM; goto fail_mtd_info; From 357e3b8e3a8769ba36eb8ec5e053e4825f1a9329 Mon Sep 17 00:00:00 2001 From: Florian Fuchs Date: Mon, 18 May 2026 13:45:21 +0200 Subject: [PATCH 40/63] mtd: maps: vmu-flash: fix NULL pointer dereference in initialization The mtd_info contains a struct device, which must be linked to its parent. Without this, the initialization of the MTD fails with a NULL pointer dereference. Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.") Cc: stable@vger.kernel.org Signed-off-by: Florian Fuchs Signed-off-by: Miquel Raynal --- drivers/mtd/maps/vmu-flash.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/maps/vmu-flash.c b/drivers/mtd/maps/vmu-flash.c index b76d0609d1b7..10244e6731d0 100644 --- a/drivers/mtd/maps/vmu-flash.c +++ b/drivers/mtd/maps/vmu-flash.c @@ -547,6 +547,7 @@ static void vmu_queryblocks(struct mapleq *mq) mpart->partition = card->partition; mtd_cur->priv = mpart; mtd_cur->owner = THIS_MODULE; + mtd_cur->dev.parent = &mdev->dev; pcache = kzalloc_obj(struct vmu_cache); if (!pcache) From 8e4531667d718e2e9b193928cf9b2497fa0d01ef Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 22 May 2026 11:17:39 +0200 Subject: [PATCH 41/63] mtd: rawnand: Pause continuous reads at block boundaries Some chips do not support sequential cached reads past block boundaries, like Winbond. In practice when using UBI, this should very rarely happen, but let's make sure it never happens. Cc: stable@vger.kernel.org Fixes: 003fe4b9545b ("mtd: rawnand: Support for sequential cache reads") Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/nand_base.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index e4e1383bd5e1..a5b278ab9384 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -1216,32 +1216,32 @@ static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page, return nand_exec_op(chip, &op); } -static unsigned int rawnand_last_page_of_lun(unsigned int pages_per_lun, unsigned int lun) +static unsigned int rawnand_last_page_of_block(unsigned int ppb, unsigned int block) { - /* lun is expected to be very small */ - return (lun * pages_per_lun) + pages_per_lun - 1; + /* block is expected to be very small */ + return (block * ppb) + ppb - 1; } static void rawnand_cap_cont_reads(struct nand_chip *chip) { struct nand_memory_organization *memorg; - unsigned int ppl, first_lun, last_lun; + unsigned int ppb, first_block, last_block; memorg = nanddev_get_memorg(&chip->base); - ppl = memorg->pages_per_eraseblock * memorg->eraseblocks_per_lun; - first_lun = chip->cont_read.first_page / ppl; - last_lun = chip->cont_read.last_page / ppl; + ppb = memorg->pages_per_eraseblock; + first_block = chip->cont_read.first_page / ppb; + last_block = chip->cont_read.last_page / ppb; - /* Prevent sequential cache reads across LUN boundaries */ - if (first_lun != last_lun) - chip->cont_read.pause_page = rawnand_last_page_of_lun(ppl, first_lun); + /* Prevent sequential cache reads across block boundaries */ + if (first_block != last_block) + chip->cont_read.pause_page = rawnand_last_page_of_block(ppb, first_block); else chip->cont_read.pause_page = chip->cont_read.last_page; if (chip->cont_read.first_page == chip->cont_read.pause_page) { chip->cont_read.first_page++; chip->cont_read.pause_page = min(chip->cont_read.last_page, - rawnand_last_page_of_lun(ppl, first_lun + 1)); + rawnand_last_page_of_block(ppb, first_block + 1)); } if (chip->cont_read.first_page >= chip->cont_read.last_page) From 7fbdbc7d028a20a78b7d28a9510a216c76b5fbfd Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 25 May 2026 15:04:40 -0700 Subject: [PATCH 42/63] mtd: rawnand: qcom: embed nand_controller into qcom_nand_controller The qcom_nand_controller had a struct nand_controller *controller pointer that was assigned to (struct nand_controller *)&nandc[1], with the allocation oversized by sizeof(*controller) to make room. get_qcom_nand_controller() then walked backwards from chip->controller using sizeof()-based arithmetic to recover the enclosing nandc. Embed the nand_controller directly into qcom_nand_controller and use container_of() in get_qcom_nand_controller(). The header now needs the full rawnand.h definition rather than a forward declaration. Assisted-by: Claude:Opus-4.7 Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/qcom_nandc.c | 16 ++++++---------- include/linux/mtd/nand-qpic-common.h | 4 +++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index b7e79b76654d..4b80ce084d9a 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -128,8 +128,8 @@ static struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip) static struct qcom_nand_controller * get_qcom_nand_controller(struct nand_chip *chip) { - return (struct qcom_nand_controller *) - ((u8 *)chip->controller - sizeof(struct qcom_nand_controller)); + return container_of(chip->controller, struct qcom_nand_controller, + controller); } static u32 nandc_read(struct qcom_nand_controller *nandc, int offset) @@ -2034,8 +2034,8 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc) { u32 nand_ctrl; - nand_controller_init(nandc->controller); - nandc->controller->ops = &qcom_nandc_ops; + nand_controller_init(&nandc->controller); + nandc->controller.ops = &qcom_nandc_ops; /* kill onenand */ if (!nandc->props->nandc_part_of_qpic) @@ -2175,7 +2175,7 @@ static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc, chip->legacy.block_bad = qcom_nandc_block_bad; chip->legacy.block_markbad = qcom_nandc_block_markbad; - chip->controller = nandc->controller; + chip->controller = &nandc->controller; chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_SKIP_BBTSCAN; @@ -2256,21 +2256,17 @@ static int qcom_nandc_parse_dt(struct platform_device *pdev) static int qcom_nandc_probe(struct platform_device *pdev) { struct qcom_nand_controller *nandc; - struct nand_controller *controller; const void *dev_data; struct device *dev = &pdev->dev; struct resource *res; int ret; - nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc) + sizeof(*controller), - GFP_KERNEL); + nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc), GFP_KERNEL); if (!nandc) return -ENOMEM; - controller = (struct nand_controller *)&nandc[1]; platform_set_drvdata(pdev, nandc); nandc->dev = dev; - nandc->controller = controller; dev_data = of_device_get_match_data(dev); if (!dev_data) { diff --git a/include/linux/mtd/nand-qpic-common.h b/include/linux/mtd/nand-qpic-common.h index e8201d1b7cf9..006ca8c978a9 100644 --- a/include/linux/mtd/nand-qpic-common.h +++ b/include/linux/mtd/nand-qpic-common.h @@ -9,6 +9,8 @@ #ifndef __MTD_NAND_QPIC_COMMON_H__ #define __MTD_NAND_QPIC_COMMON_H__ +#include + /* NANDc reg offsets */ #define NAND_FLASH_CMD 0x00 #define NAND_ADDR0 0x04 @@ -394,7 +396,7 @@ struct qcom_nand_controller { const struct qcom_nandc_props *props; - struct nand_controller *controller; + struct nand_controller controller; struct qpic_spi_nand *qspi; struct list_head host_list; From 19ed11aee966d91beebdef9d32ce926474872f79 Mon Sep 17 00:00:00 2001 From: Bastien Curutchet Date: Tue, 26 May 2026 09:10:00 +0200 Subject: [PATCH 43/63] mtd: rawnand: pl353: fix probe resource allocation During probe(), the devm_ioremap() is called with the parent device instead of the current one. So when the module is unloaded, the register area isn't released. Target the pl35x device in the devm_ioremap() instead of its parent. Cc: stable@vger.kernel.org Fixes: 08d8c62164a3 ("mtd: rawnand: pl353: Add support for the ARM PL353 SMC NAND controller") Signed-off-by: Bastien Curutchet Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/pl35x-nand-controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c index f2c65eb7a8d9..06f8f1e14b9c 100644 --- a/drivers/mtd/nand/raw/pl35x-nand-controller.c +++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c @@ -1155,7 +1155,7 @@ static int pl35x_nand_probe(struct platform_device *pdev) nfc->controller.ops = &pl35x_nandc_ops; INIT_LIST_HEAD(&nfc->chips); - nfc->conf_regs = devm_ioremap_resource(&smc_amba->dev, &smc_amba->res); + nfc->conf_regs = devm_ioremap_resource(nfc->dev, &smc_amba->res); if (IS_ERR(nfc->conf_regs)) return PTR_ERR(nfc->conf_regs); From ad1bcd3469bdb2c5d75c8c29f1944b3ac0f12f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 22 May 2026 19:32:34 +0200 Subject: [PATCH 44/63] mtd: Consistently define pci_device_ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initializing a pci_device_id using plain list expressions is hard to understand for a human who isn't into the Linux PCI subsystem. So for each initializer use one of the PCI_DEVICE macros for .vendor, .device, .subvendor and .subdevice and named initializers for the remaining assignments. This makes it easier to parse and also more robust against chagnes to the struct definition. Also drop useless zeros and commas. The mentioned robustness is relevant for a planned change to struct pci_device_id that replaces .driver_data by an anonymous union. This change doesn't introduce changes to the compiled pci_device_id array. Tested on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Signed-off-by: Miquel Raynal --- drivers/mtd/maps/amd76xrom.c | 10 ++++------ drivers/mtd/maps/ck804xrom.c | 2 +- drivers/mtd/maps/esb2rom.c | 20 +++++++------------- drivers/mtd/maps/ichxrom.c | 17 ++++++----------- drivers/mtd/maps/pci.c | 16 +++++----------- drivers/mtd/maps/scb2_flash.c | 9 ++------- drivers/mtd/nand/raw/cafe_nand.c | 3 +-- drivers/mtd/nand/raw/denali_pci.c | 4 ++-- drivers/mtd/nand/raw/r852.c | 4 ++-- 9 files changed, 30 insertions(+), 55 deletions(-) diff --git a/drivers/mtd/maps/amd76xrom.c b/drivers/mtd/maps/amd76xrom.c index 95f9b99ce5fc..457e6ab7f3fb 100644 --- a/drivers/mtd/maps/amd76xrom.c +++ b/drivers/mtd/maps/amd76xrom.c @@ -296,12 +296,10 @@ static void amd76xrom_remove_one(struct pci_dev *pdev) } static const struct pci_device_id amd76xrom_pci_tbl[] = { - { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7440, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_AMD, 0x7468 }, /* amd8111 support */ - { 0, } + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7440) }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_AMD, 0x7468, 0, 0) }, /* amd8111 support */ + { } }; MODULE_DEVICE_TABLE(pci, amd76xrom_pci_tbl); diff --git a/drivers/mtd/maps/ck804xrom.c b/drivers/mtd/maps/ck804xrom.c index 5e44134caa8e..ef4afa8c1931 100644 --- a/drivers/mtd/maps/ck804xrom.c +++ b/drivers/mtd/maps/ck804xrom.c @@ -335,7 +335,7 @@ static const struct pci_device_id ck804xrom_pci_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0365), .driver_data = DEV_MCP55 }, { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0366), .driver_data = DEV_MCP55 }, { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0367), .driver_data = DEV_MCP55 }, - { 0, } + { } }; #if 0 diff --git a/drivers/mtd/maps/esb2rom.c b/drivers/mtd/maps/esb2rom.c index fc7f2c3a62b0..043bad544414 100644 --- a/drivers/mtd/maps/esb2rom.c +++ b/drivers/mtd/maps/esb2rom.c @@ -385,19 +385,13 @@ static void esb2rom_remove_one(struct pci_dev *pdev) } static const struct pci_device_id esb2rom_pci_tbl[] = { - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { 0, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0) }, + { }, }; #if 0 diff --git a/drivers/mtd/maps/ichxrom.c b/drivers/mtd/maps/ichxrom.c index 6608b782149b..8fecd1820f6e 100644 --- a/drivers/mtd/maps/ichxrom.c +++ b/drivers/mtd/maps/ichxrom.c @@ -323,17 +323,12 @@ static void ichxrom_remove_one(struct pci_dev *pdev) } static const struct pci_device_id ichxrom_pci_tbl[] = { - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, - PCI_ANY_ID, PCI_ANY_ID, }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, - PCI_ANY_ID, PCI_ANY_ID, }, - { 0, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1), }, + { }, }; #if 0 diff --git a/drivers/mtd/maps/pci.c b/drivers/mtd/maps/pci.c index 4a84ca8aed40..34041cc8f173 100644 --- a/drivers/mtd/maps/pci.c +++ b/drivers/mtd/maps/pci.c @@ -227,22 +227,16 @@ static struct mtd_pci_info intel_dc21285_info = { static const struct pci_device_id mtd_pci_ids[] = { { - .vendor = PCI_VENDOR_ID_INTEL, - .device = 0x530d, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x530d), .class = PCI_CLASS_MEMORY_OTHER << 8, .class_mask = 0xffff00, .driver_data = (unsigned long)&intel_iq80310_info, - }, - { - .vendor = PCI_VENDOR_ID_DEC, - .device = PCI_DEVICE_ID_DEC_21285, - .subvendor = 0, /* DC21285 defaults to 0 on reset */ - .subdevice = 0, /* DC21285 defaults to 0 on reset */ + }, { + /* DC21285 defaults to 0 for .subvendor and .subdevice on reset */ + PCI_DEVICE_SUB(PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21285, 0, 0), .driver_data = (unsigned long)&intel_dc21285_info, }, - { 0, } + { } }; /* diff --git a/drivers/mtd/maps/scb2_flash.c b/drivers/mtd/maps/scb2_flash.c index 57303f904bc1..5c7e1dad101b 100644 --- a/drivers/mtd/maps/scb2_flash.c +++ b/drivers/mtd/maps/scb2_flash.c @@ -215,13 +215,8 @@ static void scb2_flash_remove(struct pci_dev *dev) } static struct pci_device_id scb2_flash_pci_ids[] = { - { - .vendor = PCI_VENDOR_ID_SERVERWORKS, - .device = PCI_DEVICE_ID_SERVERWORKS_CSB5, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID - }, - { 0, } + { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_CSB5) }, + { } }; static struct pci_driver scb2_flash_driver = { diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c index c4018bc59670..f3117b031cd2 100644 --- a/drivers/mtd/nand/raw/cafe_nand.c +++ b/drivers/mtd/nand/raw/cafe_nand.c @@ -830,8 +830,7 @@ static void cafe_nand_remove(struct pci_dev *pdev) } static const struct pci_device_id cafe_nand_tbl[] = { - { PCI_VENDOR_ID_MARVELL, PCI_DEVICE_ID_MARVELL_88ALP01_NAND, - PCI_ANY_ID, PCI_ANY_ID }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PCI_DEVICE_ID_MARVELL_88ALP01_NAND) }, { } }; diff --git a/drivers/mtd/nand/raw/denali_pci.c b/drivers/mtd/nand/raw/denali_pci.c index 97fa32d73441..f53df1b32dda 100644 --- a/drivers/mtd/nand/raw/denali_pci.c +++ b/drivers/mtd/nand/raw/denali_pci.c @@ -19,8 +19,8 @@ /* List of platforms this NAND controller has be integrated into */ static const struct pci_device_id denali_pci_ids[] = { - { PCI_VDEVICE(INTEL, 0x0701), INTEL_CE4100 }, - { PCI_VDEVICE(INTEL, 0x0809), INTEL_MRST }, + { PCI_VDEVICE(INTEL, 0x0701), .driver_data = INTEL_CE4100 }, + { PCI_VDEVICE(INTEL, 0x0809), .driver_data = INTEL_MRST }, { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE(pci, denali_pci_ids); diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c index 8a5572b30893..92c47d351c27 100644 --- a/drivers/mtd/nand/raw/r852.c +++ b/drivers/mtd/nand/raw/r852.c @@ -1070,8 +1070,8 @@ static int r852_resume(struct device *device) static const struct pci_device_id r852_pci_id_tbl[] = { - { PCI_VDEVICE(RICOH, 0x0852), }, - { }, + { PCI_VDEVICE(RICOH, 0x0852) }, + { } }; MODULE_DEVICE_TABLE(pci, r852_pci_id_tbl); From ff111dc2158562ddc5e9988c26b4c0a9cd9f978c Mon Sep 17 00:00:00 2001 From: Li Xinyu Date: Tue, 26 May 2026 17:45:40 +0800 Subject: [PATCH 45/63] mtd: inftlmount: convert printk(KERN_WARNING) to pr_warn Replace all printk(KERN_WARNING ...) calls with pr_warn() to follow the kernel's recommended logging style and resolve checkpatch warnings. Also convert one bare printk() that was missing a log level. No functional change. Signed-off-by: Li Xinyu Signed-off-by: Miquel Raynal --- drivers/mtd/inftlmount.c | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c index 6276daa296da..87e246a6f488 100644 --- a/drivers/mtd/inftlmount.c +++ b/drivers/mtd/inftlmount.c @@ -67,12 +67,12 @@ static int find_boot_record(struct INFTLrecord *inftl) static int warncount = 5; if (warncount) { - printk(KERN_WARNING "INFTL: block read at 0x%x " + pr_warn("INFTL: block read at 0x%x " "of mtd%d failed: %d\n", block * inftl->EraseSize, inftl->mbd.mtd->index, ret); if (!--warncount) - printk(KERN_WARNING "INFTL: further " + pr_warn("INFTL: further " "failures for this block will " "not be printed\n"); } @@ -89,7 +89,7 @@ static int find_boot_record(struct INFTLrecord *inftl) block * inftl->EraseSize + SECTORSIZE + 8, 8, &retlen,(char *)&h1); if (ret < 0) { - printk(KERN_WARNING "INFTL: ANAND header found at " + pr_warn("INFTL: ANAND header found at " "0x%x in mtd%d, but OOB data read failed " "(err %d)\n", block * inftl->EraseSize, inftl->mbd.mtd->index, ret); @@ -107,13 +107,13 @@ static int find_boot_record(struct INFTLrecord *inftl) mtd_read(mtd, block * inftl->EraseSize + 4096, SECTORSIZE, &retlen, buf); if (retlen != SECTORSIZE) { - printk(KERN_WARNING "INFTL: Unable to read spare " + pr_warn("INFTL: Unable to read spare " "Media Header\n"); return -1; } /* Check if this one is the same as the first one we found. */ if (memcmp(mh, buf, sizeof(struct INFTLMediaHeader))) { - printk(KERN_WARNING "INFTL: Primary and spare Media " + pr_warn("INFTL: Primary and spare Media " "Headers disagree.\n"); return -1; } @@ -141,14 +141,14 @@ static int find_boot_record(struct INFTLrecord *inftl) mh->OsakVersion, mh->PercentUsed); if (mh->NoOfBDTLPartitions == 0) { - printk(KERN_WARNING "INFTL: Media Header sanity check " + pr_warn("INFTL: Media Header sanity check " "failed: NoOfBDTLPartitions (%d) == 0, " "must be at least 1\n", mh->NoOfBDTLPartitions); return -1; } if ((mh->NoOfBDTLPartitions + mh->NoOfBinaryPartitions) > 4) { - printk(KERN_WARNING "INFTL: Media Header sanity check " + pr_warn("INFTL: Media Header sanity check " "failed: Total Partitions (%d) > 4, " "BDTL=%d Binary=%d\n", mh->NoOfBDTLPartitions + mh->NoOfBinaryPartitions, @@ -158,12 +158,12 @@ static int find_boot_record(struct INFTLrecord *inftl) } if (mh->BlockMultiplierBits > 1) { - printk(KERN_WARNING "INFTL: sorry, we don't support " + pr_warn("INFTL: sorry, we don't support " "UnitSizeFactor 0x%02x\n", mh->BlockMultiplierBits); return -1; } else if (mh->BlockMultiplierBits == 1) { - printk(KERN_WARNING "INFTL: support for INFTL with " + pr_warn("INFTL: support for INFTL with " "UnitSizeFactor 0x%02x is experimental\n", mh->BlockMultiplierBits); inftl->EraseSize = inftl->mbd.mtd->erasesize << @@ -207,7 +207,7 @@ static int find_boot_record(struct INFTLrecord *inftl) mtd_erase(mtd, instr); } if ((ip->lastUnit - ip->firstUnit + 1) < ip->virtualUnits) { - printk(KERN_WARNING "INFTL: Media Header " + pr_warn("INFTL: Media Header " "Partition %d sanity check failed\n" " firstUnit %d : lastUnit %d > " "virtualUnits %d\n", i, ip->lastUnit, @@ -215,7 +215,7 @@ static int find_boot_record(struct INFTLrecord *inftl) return -1; } if (ip->Reserved1 != 0) { - printk(KERN_WARNING "INFTL: Media Header " + pr_warn("INFTL: Media Header " "Partition %d sanity check failed: " "Reserved1 %d != 0\n", i, ip->Reserved1); @@ -227,7 +227,7 @@ static int find_boot_record(struct INFTLrecord *inftl) } if (i >= 4) { - printk(KERN_WARNING "INFTL: Media Header Partition " + pr_warn("INFTL: Media Header Partition " "sanity check failed:\n No partition " "marked as Disk Partition\n"); return -1; @@ -237,7 +237,7 @@ static int find_boot_record(struct INFTLrecord *inftl) inftl->numvunits = ip->virtualUnits; if (inftl->numvunits > (inftl->nb_blocks - inftl->nb_boot_blocks - 2)) { - printk(KERN_WARNING "INFTL: Media Header sanity check " + pr_warn("INFTL: Media Header sanity check " "failed:\n numvunits (%d) > nb_blocks " "(%d) - nb_boot_blocks(%d) - 2\n", inftl->numvunits, inftl->nb_blocks, @@ -385,7 +385,7 @@ int INFTL_formatblock(struct INFTLrecord *inftl, int block) ret = mtd_erase(inftl->mbd.mtd, instr); if (ret) { - printk(KERN_WARNING "INFTL: error while formatting block %d\n", + pr_warn("INFTL: error while formatting block %d\n", block); goto fail; } @@ -428,13 +428,13 @@ static void format_chain(struct INFTLrecord *inftl, unsigned int first_block) { unsigned int block = first_block, block1; - printk(KERN_WARNING "INFTL: formatting chain at block %d\n", + pr_warn("INFTL: formatting chain at block %d\n", first_block); for (;;) { block1 = inftl->PUtable[block]; - printk(KERN_WARNING "INFTL: formatting block %d\n", block); + pr_warn("INFTL: formatting block %d\n", block); if (INFTL_formatblock(inftl, block) < 0) { /* * Cannot format !!!! Mark it as Bad Unit, @@ -539,7 +539,7 @@ int INFTL_mount(struct INFTLrecord *s) /* Search for INFTL MediaHeader and Spare INFTL Media Header */ if (find_boot_record(s) < 0) { - printk(KERN_WARNING "INFTL: could not find valid boot record?\n"); + pr_warn("INFTL: could not find valid boot record?\n"); return -ENXIO; } @@ -610,7 +610,7 @@ int INFTL_mount(struct INFTLrecord *s) /* Check for invalid block */ if (erase_mark != ERASE_MARK) { - printk(KERN_WARNING "INFTL: corrupt block %d " + pr_warn("INFTL: corrupt block %d " "in chain %d, chain length %d, erase " "mark 0x%x?\n", block, first_block, chain_length, erase_mark); @@ -635,7 +635,7 @@ int INFTL_mount(struct INFTLrecord *s) ((prev_block >= s->nb_blocks) && (prev_block != BLOCK_NIL))) { if (chain_length > 0) { - printk(KERN_WARNING "INFTL: corrupt " + pr_warn("INFTL: corrupt " "block %d in chain %d?\n", block, first_block); do_format_chain++; @@ -670,7 +670,7 @@ int INFTL_mount(struct INFTLrecord *s) /* Validate next block before following it... */ if (block > s->lastEUN) { - printk(KERN_WARNING "INFTL: invalid previous " + pr_warn("INFTL: invalid previous " "block %d in chain %d?\n", block, first_block); do_format_chain++; @@ -714,7 +714,7 @@ int INFTL_mount(struct INFTLrecord *s) if (s->PUtable[block] == BLOCK_NIL) break; if (s->PUtable[block] > s->lastEUN) { - printk(KERN_WARNING "INFTL: invalid prev %d, " + pr_warn("INFTL: invalid prev %d, " "in virtual chain %d\n", s->PUtable[block], logical_block); s->PUtable[block] = BLOCK_NIL; @@ -757,7 +757,7 @@ int INFTL_mount(struct INFTLrecord *s) pr_debug("INFTL: pass 3, format unused blocks\n"); for (block = s->firstEUN; block <= s->lastEUN; block++) { if (s->PUtable[block] == BLOCK_NOTEXPLORED) { - printk("INFTL: unreferenced block %d, formatting it\n", + pr_warn("INFTL: unreferenced block %d, formatting it\n", block); if (INFTL_formatblock(s, block) < 0) s->PUtable[block] = BLOCK_RESERVED; From 6dcd852e9b43971a84bfc442ad45e69f224f3b11 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 26 May 2026 12:32:04 +0200 Subject: [PATCH 46/63] =?UTF-8?q?mtd:=20maps:=20remove=20AMD=20=C3=89lan?= =?UTF-8?q?=20specific=20drivers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were four MTD maps drivers that were used with AMD Élan SoCs. Since 486-class CPU support was removed in commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"), it is no longer possible to actually use these. Three of them have already been removed, so remove the remaining driver entirely. Signed-off-by: Arnd Bergmann [Miquel Raynal: Only NETtel was still in the tree, adapt the patch and the commit log] Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 6 - drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/nettel.c | 462 -------------------------------------- 3 files changed, 469 deletions(-) delete mode 100644 drivers/mtd/maps/nettel.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index db63b5af4b39..784dba14e908 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -204,12 +204,6 @@ config MTD_TSUNAMI help Support for the flash chip on Tsunami TIG bus. -config MTD_NETtel - tristate "CFI flash device on SnapGear/SecureEdge" - depends on X86 && MTD_JEDECPROBE - help - Support for flash chips on NETtel/SecureEdge/SnapGear boards. - config MTD_LANTIQ tristate "Lantiq SoC NOR support" depends on LANTIQ diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index c545d1e68e91..157f911f86d8 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -32,7 +32,6 @@ obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o obj-$(CONFIG_MTD_PCI) += pci.o obj-$(CONFIG_MTD_IMPA7) += impa7.o obj-$(CONFIG_MTD_UCLINUX) += uclinux.o -obj-$(CONFIG_MTD_NETtel) += nettel.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o diff --git a/drivers/mtd/maps/nettel.c b/drivers/mtd/maps/nettel.c deleted file mode 100644 index 7d349874ffeb..000000000000 --- a/drivers/mtd/maps/nettel.c +++ /dev/null @@ -1,462 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/****************************************************************************/ - -/* - * nettel.c -- mappings for NETtel/SecureEdge/SnapGear (x86) boards. - * - * (C) Copyright 2000-2001, Greg Ungerer (gerg@snapgear.com) - * (C) Copyright 2001-2002, SnapGear (www.snapgear.com) - */ - -/****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/****************************************************************************/ - -#define INTEL_BUSWIDTH 1 -#define AMD_WINDOW_MAXSIZE 0x00200000 -#define AMD_BUSWIDTH 1 - -/* - * PAR masks and shifts, assuming 64K pages. - */ -#define SC520_PAR_ADDR_MASK 0x00003fff -#define SC520_PAR_ADDR_SHIFT 16 -#define SC520_PAR_TO_ADDR(par) \ - (((par)&SC520_PAR_ADDR_MASK) << SC520_PAR_ADDR_SHIFT) - -#define SC520_PAR_SIZE_MASK 0x01ffc000 -#define SC520_PAR_SIZE_SHIFT 2 -#define SC520_PAR_TO_SIZE(par) \ - ((((par)&SC520_PAR_SIZE_MASK) << SC520_PAR_SIZE_SHIFT) + (64*1024)) - -#define SC520_PAR(cs, addr, size) \ - ((cs) | \ - ((((size)-(64*1024)) >> SC520_PAR_SIZE_SHIFT) & SC520_PAR_SIZE_MASK) | \ - (((addr) >> SC520_PAR_ADDR_SHIFT) & SC520_PAR_ADDR_MASK)) - -#define SC520_PAR_BOOTCS 0x8a000000 -#define SC520_PAR_ROMCS1 0xaa000000 -#define SC520_PAR_ROMCS2 0xca000000 /* Cache disabled, 64K page */ - -static void *nettel_mmcrp = NULL; - -#ifdef CONFIG_MTD_CFI_INTELEXT -static struct mtd_info *intel_mtd; -#endif -static struct mtd_info *amd_mtd; - -/****************************************************************************/ - -/****************************************************************************/ - -#ifdef CONFIG_MTD_CFI_INTELEXT -static struct map_info nettel_intel_map = { - .name = "SnapGear Intel", - .size = 0, - .bankwidth = INTEL_BUSWIDTH, -}; - -static struct mtd_partition nettel_intel_partitions[] = { - { - .name = "SnapGear kernel", - .offset = 0, - .size = 0x000e0000 - }, - { - .name = "SnapGear filesystem", - .offset = 0x00100000, - }, - { - .name = "SnapGear config", - .offset = 0x000e0000, - .size = 0x00020000 - }, - { - .name = "SnapGear Intel", - .offset = 0 - }, - { - .name = "SnapGear BIOS Config", - .offset = 0x007e0000, - .size = 0x00020000 - }, - { - .name = "SnapGear BIOS", - .offset = 0x007e0000, - .size = 0x00020000 - }, -}; -#endif - -static struct map_info nettel_amd_map = { - .name = "SnapGear AMD", - .size = AMD_WINDOW_MAXSIZE, - .bankwidth = AMD_BUSWIDTH, -}; - -static const struct mtd_partition nettel_amd_partitions[] = { - { - .name = "SnapGear BIOS config", - .offset = 0x000e0000, - .size = 0x00010000 - }, - { - .name = "SnapGear BIOS", - .offset = 0x000f0000, - .size = 0x00010000 - }, - { - .name = "SnapGear AMD", - .offset = 0 - }, - { - .name = "SnapGear high BIOS", - .offset = 0x001f0000, - .size = 0x00010000 - } -}; - -#define NUM_AMD_PARTITIONS ARRAY_SIZE(nettel_amd_partitions) - -/****************************************************************************/ - -#ifdef CONFIG_MTD_CFI_INTELEXT - -/* - * Set the Intel flash back to read mode since some old boot - * loaders don't. - */ -static int nettel_reboot_notifier(struct notifier_block *nb, unsigned long val, void *v) -{ - struct cfi_private *cfi = nettel_intel_map.fldrv_priv; - unsigned long b; - - /* Make sure all FLASH chips are put back into read mode */ - for (b = 0; (b < nettel_intel_partitions[3].size); b += 0x100000) { - cfi_send_gen_cmd(0xff, 0x55, b, &nettel_intel_map, cfi, - cfi->device_type, NULL); - } - return(NOTIFY_OK); -} - -static struct notifier_block nettel_notifier_block = { - nettel_reboot_notifier, NULL, 0 -}; - -#endif - -/****************************************************************************/ - -static int __init nettel_init(void) -{ - volatile unsigned long *amdpar; - unsigned long amdaddr, maxsize; - int num_amd_partitions=0; -#ifdef CONFIG_MTD_CFI_INTELEXT - volatile unsigned long *intel0par, *intel1par; - unsigned long orig_bootcspar, orig_romcs1par; - unsigned long intel0addr, intel0size; - unsigned long intel1addr, intel1size; - int intelboot, intel0cs, intel1cs; - int num_intel_partitions; -#endif - int rc = 0; - - nettel_mmcrp = (void *) ioremap(0xfffef000, 4096); - if (nettel_mmcrp == NULL) { - printk("SNAPGEAR: failed to disable MMCR cache??\n"); - return(-EIO); - } - - /* Set CPU clock to be 33.000MHz */ - *((unsigned char *) (nettel_mmcrp + 0xc64)) = 0x01; - - amdpar = (volatile unsigned long *) (nettel_mmcrp + 0xc4); - -#ifdef CONFIG_MTD_CFI_INTELEXT - intelboot = 0; - intel0cs = SC520_PAR_ROMCS1; - intel0par = (volatile unsigned long *) (nettel_mmcrp + 0xc0); - intel1cs = SC520_PAR_ROMCS2; - intel1par = (volatile unsigned long *) (nettel_mmcrp + 0xbc); - - /* - * Save the CS settings then ensure ROMCS1 and ROMCS2 are off, - * otherwise they might clash with where we try to map BOOTCS. - */ - orig_bootcspar = *amdpar; - orig_romcs1par = *intel0par; - *intel0par = 0; - *intel1par = 0; -#endif - - /* - * The first thing to do is determine if we have a separate - * boot FLASH device. Typically this is a small (1 to 2MB) - * AMD FLASH part. It seems that device size is about the - * only way to tell if this is the case... - */ - amdaddr = 0x20000000; - maxsize = AMD_WINDOW_MAXSIZE; - - *amdpar = SC520_PAR(SC520_PAR_BOOTCS, amdaddr, maxsize); - __asm__ ("wbinvd"); - - nettel_amd_map.phys = amdaddr; - nettel_amd_map.virt = ioremap(amdaddr, maxsize); - if (!nettel_amd_map.virt) { - printk("SNAPGEAR: failed to ioremap() BOOTCS\n"); - iounmap(nettel_mmcrp); - return(-EIO); - } - simple_map_init(&nettel_amd_map); - - if ((amd_mtd = do_map_probe("jedec_probe", &nettel_amd_map))) { - printk(KERN_NOTICE "SNAPGEAR: AMD flash device size = %dK\n", - (int)(amd_mtd->size>>10)); - - amd_mtd->owner = THIS_MODULE; - - /* The high BIOS partition is only present for 2MB units */ - num_amd_partitions = NUM_AMD_PARTITIONS; - if (amd_mtd->size < AMD_WINDOW_MAXSIZE) - num_amd_partitions--; - /* Don't add the partition until after the primary INTEL's */ - -#ifdef CONFIG_MTD_CFI_INTELEXT - /* - * Map the Intel flash into memory after the AMD - * It has to start on a multiple of maxsize. - */ - maxsize = SC520_PAR_TO_SIZE(orig_romcs1par); - if (maxsize < (32 * 1024 * 1024)) - maxsize = (32 * 1024 * 1024); - intel0addr = amdaddr + maxsize; -#endif - } else { -#ifdef CONFIG_MTD_CFI_INTELEXT - /* INTEL boot FLASH */ - intelboot++; - - if (!orig_romcs1par) { - intel0cs = SC520_PAR_BOOTCS; - intel0par = (volatile unsigned long *) - (nettel_mmcrp + 0xc4); - intel1cs = SC520_PAR_ROMCS1; - intel1par = (volatile unsigned long *) - (nettel_mmcrp + 0xc0); - - intel0addr = SC520_PAR_TO_ADDR(orig_bootcspar); - maxsize = SC520_PAR_TO_SIZE(orig_bootcspar); - } else { - /* Kernel base is on ROMCS1, not BOOTCS */ - intel0cs = SC520_PAR_ROMCS1; - intel0par = (volatile unsigned long *) - (nettel_mmcrp + 0xc0); - intel1cs = SC520_PAR_BOOTCS; - intel1par = (volatile unsigned long *) - (nettel_mmcrp + 0xc4); - - intel0addr = SC520_PAR_TO_ADDR(orig_romcs1par); - maxsize = SC520_PAR_TO_SIZE(orig_romcs1par); - } - - /* Destroy useless AMD MTD mapping */ - amd_mtd = NULL; - iounmap(nettel_amd_map.virt); - nettel_amd_map.virt = NULL; -#else - /* Only AMD flash supported */ - rc = -ENXIO; - goto out_unmap2; -#endif - } - -#ifdef CONFIG_MTD_CFI_INTELEXT - /* - * We have determined the INTEL FLASH configuration, so lets - * go ahead and probe for them now. - */ - - /* Set PAR to the maximum size */ - if (maxsize < (32 * 1024 * 1024)) - maxsize = (32 * 1024 * 1024); - *intel0par = SC520_PAR(intel0cs, intel0addr, maxsize); - - /* Turn other PAR off so the first probe doesn't find it */ - *intel1par = 0; - - /* Probe for the size of the first Intel flash */ - nettel_intel_map.size = maxsize; - nettel_intel_map.phys = intel0addr; - nettel_intel_map.virt = ioremap(intel0addr, maxsize); - if (!nettel_intel_map.virt) { - printk("SNAPGEAR: failed to ioremap() ROMCS1\n"); - rc = -EIO; - goto out_unmap2; - } - simple_map_init(&nettel_intel_map); - - intel_mtd = do_map_probe("cfi_probe", &nettel_intel_map); - if (!intel_mtd) { - rc = -ENXIO; - goto out_unmap1; - } - - /* Set PAR to the detected size */ - intel0size = intel_mtd->size; - *intel0par = SC520_PAR(intel0cs, intel0addr, intel0size); - - /* - * Map second Intel FLASH right after first. Set its size to the - * same maxsize used for the first Intel FLASH. - */ - intel1addr = intel0addr + intel0size; - *intel1par = SC520_PAR(intel1cs, intel1addr, maxsize); - __asm__ ("wbinvd"); - - maxsize += intel0size; - - /* Delete the old map and probe again to do both chips */ - map_destroy(intel_mtd); - intel_mtd = NULL; - iounmap(nettel_intel_map.virt); - - nettel_intel_map.size = maxsize; - nettel_intel_map.virt = ioremap(intel0addr, maxsize); - if (!nettel_intel_map.virt) { - printk("SNAPGEAR: failed to ioremap() ROMCS1/2\n"); - rc = -EIO; - goto out_unmap2; - } - - intel_mtd = do_map_probe("cfi_probe", &nettel_intel_map); - if (! intel_mtd) { - rc = -ENXIO; - goto out_unmap1; - } - - intel1size = intel_mtd->size - intel0size; - if (intel1size > 0) { - *intel1par = SC520_PAR(intel1cs, intel1addr, intel1size); - __asm__ ("wbinvd"); - } else { - *intel1par = 0; - } - - printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %lldKiB\n", - (unsigned long long)(intel_mtd->size >> 10)); - - intel_mtd->owner = THIS_MODULE; - - num_intel_partitions = ARRAY_SIZE(nettel_intel_partitions); - - if (intelboot) { - /* - * Adjust offset and size of last boot partition. - * Must allow for BIOS region at end of FLASH. - */ - nettel_intel_partitions[1].size = (intel0size + intel1size) - - (1024*1024 + intel_mtd->erasesize); - nettel_intel_partitions[3].size = intel0size + intel1size; - nettel_intel_partitions[4].offset = - (intel0size + intel1size) - intel_mtd->erasesize; - nettel_intel_partitions[4].size = intel_mtd->erasesize; - nettel_intel_partitions[5].offset = - nettel_intel_partitions[4].offset; - nettel_intel_partitions[5].size = - nettel_intel_partitions[4].size; - } else { - /* No BIOS regions when AMD boot */ - num_intel_partitions -= 2; - } - rc = mtd_device_register(intel_mtd, nettel_intel_partitions, - num_intel_partitions); - if (rc) - goto out_map_destroy; -#endif - - if (amd_mtd) { - rc = mtd_device_register(amd_mtd, nettel_amd_partitions, - num_amd_partitions); - if (rc) - goto out_mtd_unreg; - } - -#ifdef CONFIG_MTD_CFI_INTELEXT - register_reboot_notifier(&nettel_notifier_block); -#endif - - return rc; - -out_mtd_unreg: -#ifdef CONFIG_MTD_CFI_INTELEXT - mtd_device_unregister(intel_mtd); -out_map_destroy: - map_destroy(intel_mtd); -out_unmap1: - iounmap(nettel_intel_map.virt); -#endif - -out_unmap2: - iounmap(nettel_mmcrp); - iounmap(nettel_amd_map.virt); - - return rc; -} - -/****************************************************************************/ - -static void __exit nettel_cleanup(void) -{ -#ifdef CONFIG_MTD_CFI_INTELEXT - unregister_reboot_notifier(&nettel_notifier_block); -#endif - if (amd_mtd) { - mtd_device_unregister(amd_mtd); - map_destroy(amd_mtd); - } - if (nettel_mmcrp) { - iounmap(nettel_mmcrp); - nettel_mmcrp = NULL; - } - if (nettel_amd_map.virt) { - iounmap(nettel_amd_map.virt); - nettel_amd_map.virt = NULL; - } -#ifdef CONFIG_MTD_CFI_INTELEXT - if (intel_mtd) { - mtd_device_unregister(intel_mtd); - map_destroy(intel_mtd); - } - if (nettel_intel_map.virt) { - iounmap(nettel_intel_map.virt); - nettel_intel_map.virt = NULL; - } -#endif -} - -/****************************************************************************/ - -module_init(nettel_init); -module_exit(nettel_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Greg Ungerer "); -MODULE_DESCRIPTION("SnapGear/SecureEdge FLASH support"); - -/****************************************************************************/ From c584b8a7ad01a334a89732f3c5791ba14e58642b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 26 May 2026 12:32:05 +0200 Subject: [PATCH 47/63] mtd: maps: remove uclinux map driver Rather than using platform data or DT properties, the configuration for this mtd map driver used to be passed through the global uclinux_ram_map structure, but the last instance was removed in commit 4ba66a976072 ("arch: remove blackfin port") in 2018. After commit 251f26c9e828 ("mtd: maps: Make uclinux_ram_map static"), it became impossible to configure it at all, even with out-of-tree platform code. Clearly nobody is using it, so remove it. Signed-off-by: Arnd Bergmann Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 6 -- drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/uclinux.c | 118 ------------------------------------- 3 files changed, 125 deletions(-) delete mode 100644 drivers/mtd/maps/uclinux.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 784dba14e908..1e8f2b518ddd 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -284,12 +284,6 @@ config MTD_PCMCIA_ANONYMOUS If unsure, say N. -config MTD_UCLINUX - bool "Generic uClinux RAM/ROM filesystem support" - depends on (MTD_RAM=y || MTD_ROM=y) && (!MMU || COLDFIRE) - help - Map driver to support image based filesystems for uClinux. - config MTD_PLATRAM tristate "Map driver for platform device RAM (mtd-ram)" select MTD_RAM diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 157f911f86d8..eff28fc0b60e 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -31,7 +31,6 @@ obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o obj-$(CONFIG_MTD_PCI) += pci.o obj-$(CONFIG_MTD_IMPA7) += impa7.o -obj-$(CONFIG_MTD_UCLINUX) += uclinux.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o diff --git a/drivers/mtd/maps/uclinux.c b/drivers/mtd/maps/uclinux.c deleted file mode 100644 index de4c46318abb..000000000000 --- a/drivers/mtd/maps/uclinux.c +++ /dev/null @@ -1,118 +0,0 @@ -/****************************************************************************/ - -/* - * uclinux.c -- generic memory mapped MTD driver for uclinux - * - * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com) - * - * License: GPL - */ - -/****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/****************************************************************************/ - -#ifdef CONFIG_MTD_ROM -#define MAP_NAME "rom" -#else -#define MAP_NAME "ram" -#endif - -static struct map_info uclinux_ram_map = { - .name = MAP_NAME, - .size = 0, -}; - -static unsigned long physaddr = -1; -module_param(physaddr, ulong, S_IRUGO); - -static struct mtd_info *uclinux_ram_mtdinfo; - -/****************************************************************************/ - -static const struct mtd_partition uclinux_romfs[] = { - { .name = "ROMfs" } -}; - -#define NUM_PARTITIONS ARRAY_SIZE(uclinux_romfs) - -/****************************************************************************/ - -static int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, void **virt, resource_size_t *phys) -{ - struct map_info *map = mtd->priv; - *virt = map->virt + from; - if (phys) - *phys = map->phys + from; - *retlen = len; - return(0); -} - -/****************************************************************************/ - -static int __init uclinux_mtd_init(void) -{ - struct mtd_info *mtd; - struct map_info *mapp; - - mapp = &uclinux_ram_map; - - if (physaddr == -1) - mapp->phys = (resource_size_t)__bss_stop; - else - mapp->phys = physaddr; - - if (!mapp->size) - mapp->size = PAGE_ALIGN(ntohl(*((unsigned long *)(mapp->phys + 8)))); - mapp->bankwidth = 4; - - printk("uclinux[mtd]: probe address=0x%x size=0x%x\n", - (int) mapp->phys, (int) mapp->size); - - /* - * The filesystem is guaranteed to be in direct mapped memory. It is - * directly following the kernels own bss region. Following the same - * mechanism used by architectures setting up traditional initrds we - * use phys_to_virt to get the virtual address of its start. - */ - mapp->virt = phys_to_virt(mapp->phys); - - if (mapp->virt == 0) { - printk("uclinux[mtd]: no virtual mapping?\n"); - return(-EIO); - } - - simple_map_init(mapp); - - mtd = do_map_probe("map_" MAP_NAME, mapp); - if (!mtd) { - printk("uclinux[mtd]: failed to find a mapping?\n"); - return(-ENXIO); - } - - mtd->owner = THIS_MODULE; - mtd->_point = uclinux_point; - mtd->priv = mapp; - - uclinux_ram_mtdinfo = mtd; - mtd_device_register(mtd, uclinux_romfs, NUM_PARTITIONS); - - return(0); -} -device_initcall(uclinux_mtd_init); - -/****************************************************************************/ From 72de4a7c8e2953ae489120203d2c45bb59c5c54f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 26 May 2026 12:32:06 +0200 Subject: [PATCH 48/63] mtd: maps: remove obsolete impa7 map driver This driver was originally merged in 2002 for a board using the Cirrus Logic CL/PS711x platform, but the actual board file never made it upstream. The SoC platform is still supported but uses devicetree based probing, so if anyone ever wanted to upstream board support, they would just use the regular physmap driver. Signed-off-by: Arnd Bergmann Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 7 --- drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/impa7.c | 115 -------------------------------------- 3 files changed, 123 deletions(-) delete mode 100644 drivers/mtd/maps/impa7.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 1e8f2b518ddd..dce5e67ce3c2 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -249,13 +249,6 @@ config MTD_DC21285 21285 bridge used with Intel's StrongARM processors. More info at . -config MTD_IMPA7 - tristate "JEDEC Flash device mapped on impA7" - depends on ARM && MTD_JEDECPROBE - help - This enables access to the NOR Flash on the impA7 board of - implementa GmbH. If you have such a board, say 'Y' here. - # This needs CFI or JEDEC, depending on the cards found. config MTD_PCI tristate "PCI MTD driver" diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index eff28fc0b60e..fbed278157f6 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -30,7 +30,6 @@ obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o obj-$(CONFIG_MTD_PCI) += pci.o -obj-$(CONFIG_MTD_IMPA7) += impa7.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o diff --git a/drivers/mtd/maps/impa7.c b/drivers/mtd/maps/impa7.c deleted file mode 100644 index b41401852fb7..000000000000 --- a/drivers/mtd/maps/impa7.c +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Handle mapping of the NOR flash on implementa A7 boards - * - * Copyright 2002 SYSGO Real-Time Solutions GmbH - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define WINDOW_ADDR0 0x00000000 /* physical properties of flash */ -#define WINDOW_SIZE0 0x00800000 -#define WINDOW_ADDR1 0x10000000 /* physical properties of flash */ -#define WINDOW_SIZE1 0x00800000 -#define NUM_FLASHBANKS 2 -#define BUSWIDTH 4 - -#define MSG_PREFIX "impA7:" /* prefix for our printk()'s */ -#define MTDID "impa7-%d" /* for mtdparts= partitioning */ - -static struct mtd_info *impa7_mtd[NUM_FLASHBANKS]; - -static const char * const rom_probe_types[] = { "jedec_probe", NULL }; - -static struct map_info impa7_map[NUM_FLASHBANKS] = { - { - .name = "impA7 NOR Flash Bank #0", - .size = WINDOW_SIZE0, - .bankwidth = BUSWIDTH, - }, - { - .name = "impA7 NOR Flash Bank #1", - .size = WINDOW_SIZE1, - .bankwidth = BUSWIDTH, - }, -}; - -/* - * MTD partitioning stuff - */ -static const struct mtd_partition partitions[] = -{ - { - .name = "FileSystem", - .size = 0x800000, - .offset = 0x00000000 - }, -}; - -static int __init init_impa7(void) -{ - const char * const *type; - int i; - static struct { u_long addr; u_long size; } pt[NUM_FLASHBANKS] = { - { WINDOW_ADDR0, WINDOW_SIZE0 }, - { WINDOW_ADDR1, WINDOW_SIZE1 }, - }; - int devicesfound = 0; - - for(i=0; iowner = THIS_MODULE; - devicesfound++; - mtd_device_register(impa7_mtd[i], partitions, - ARRAY_SIZE(partitions)); - } else { - iounmap((void __iomem *)impa7_map[i].virt); - } - } - return devicesfound == 0 ? -ENXIO : 0; -} - -static void __exit cleanup_impa7(void) -{ - int i; - for (i=0; i"); -MODULE_DESCRIPTION("MTD map driver for implementa impA7"); From 6000eab4515a2a381557a48c35e248a3418f2bc6 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:44 +0200 Subject: [PATCH 49/63] mtd: spi-nor: Add steps for testing locking support As recently raised on the mailing list, it may be useful to propose a list of steps to go through in order to prove the devices have been described correctly, especially since all the block protection information is not stored in any kind of table and is instead filled manually by developers. Use the debugfs output to ease the comparison between expectations and reality. [rdunlap@infradead.org: fix build warning] Link: https://lore.kernel.org/linux-mtd/20260526172341.773398-1-rdunlap@infradead.org/ Signed-off-by: Miquel Raynal Signed-off-by: Randy Dunlap Signed-off-by: Pratyush Yadav --- Documentation/driver-api/mtd/spi-nor.rst | 133 +++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst index 148fa4288760..f817a9193731 100644 --- a/Documentation/driver-api/mtd/spi-nor.rst +++ b/Documentation/driver-api/mtd/spi-nor.rst @@ -203,3 +203,136 @@ section, after the ``---`` marker. mtd.writesize = 1 mtd.oobsize = 0 regions = 0 + +5) If your flash supports locking, please go through the following test + procedure to make sure it correctly behaves. The below example + expects the typical situation where eraseblocks and lock sectors have + the same size. In case you enabled MTD_SPI_NOR_USE_4K_SECTORS, you + must adapt `bs` accordingly. + + Warning: These tests may hard lock your device! Make sure: + + - The device is not hard locked already (#WP strapped to low and + SR_SRWD bit set) + - If you have a WPn pin, you may want to set `no-wp` in your DT for + the time of the test, to only make use of software protection. + Otherwise, clearing the locking state depends on the WPn + signal and if it is tied to low, the flash will be permanently + locked. + + Test full chip locking and make sure expectations, the MEMISLOCKED + ioctl output, the debugfs output and experimental results are all + aligned:: + + root@1:~# alias show_sectors='grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params' + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -i /dev/mtd0 + Device: /dev/mtd0 + Start: 0 + Len: 0x4000000 + Lock status: unlocked + Return code: 0 + root@1:~# mtd_debug erase /dev/mtd0 0 2097152 + Erased 2097152 bytes from address 0x00000000 in flash + root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test + Copied 2097152 bytes from spi_test to address 0x00000000 in flash + root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read + Copied 2097152 bytes from address 0x00000000 in flash to spi_read + root@1:~# sha256sum spi* + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03ffffff | unlocked | 1024 + + root@1:~# flash_lock -l /dev/mtd0 + root@1:~# flash_lock -i /dev/mtd0 + Device: /dev/mtd0 + Start: 0 + Len: 0x4000000 + Lock status: locked + Return code: 1 + root@1:~# mtd_debug erase /dev/mtd0 0 2097152 + Erased 2097152 bytes from address 0x00000000 in flash + root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read + Copied 2097152 bytes from address 0x00000000 in flash to spi_read + root@1:~# sha256sum spi* + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test + root@1:~# dd if=/dev/urandom of=./spi_test2 bs=1M count=2 + 2+0 records in + 2+0 records out + root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test2 + Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash + root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read2 + Copied 2097152 bytes from address 0x00000000 in flash to spi_read2 + root@1:~# sha256sum spi* + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read2 + c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test + bea9334df51c620440f86751cba0799214a016329f1736f9456d40cf40efdc88 spi_test2 + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03ffffff | locked | 1024 + root@1:~# flash_lock -u /dev/mtd0 + + Once we trust the debugfs output we can use it to test various + situations. Check top locking/unlocking (end of the device):: + + root@1:~# size=$(cat /sys/class/mtd/mtd0/size) + root@1:~# bs=$(cat /sys/class/mtd/mtd0/erasesize) + root@1:~# nsectors=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*unlocked | //') + root@1:~# ss=$(($size / $nsectors)) + root@1:~# bps=$(($ss / $bs)) + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 $(($size - (2 * $ss))) $((2 * $bps)) # last two + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03fdffff | unlocked | 1022 + 03fe0000-03ffffff | locked | 2 + root@1:~# flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $((1 * $bps)) # last one + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03feffff | unlocked | 1023 + 03ff0000-03ffffff | locked | 1 + + If the flash features 4 block protection bits (BP), we can protect + more than 4MB (typically 128 64kiB-blocks or more), with a finer + grain than locking the entire device:: + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 $(($size - (2**7 * $ss))) $((2**7 * $bps)) + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-037fffff | unlocked | 896 + 03800000-03ffffff | locked | 128 + + If the flash features a Top/Bottom (TB) bit, we can protect the + beginning of the flash:: + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 0 $((2 * $bps)) # first two + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0001ffff | locked | 2 + 00020000-03ffffff | unlocked | 1022 + root@1:~# flash_lock -u /dev/mtd0 $ss $((1 * $bps)) # first one + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0000ffff | locked | 1 + 00010000-03ffffff | unlocked | 1023 From e3fb31d8847fef2ce37c5f60bc77d3f731a2419b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:45 +0200 Subject: [PATCH 50/63] mtd: spi-nor: swp: Add support for the complement feature The current locking implementation allows to select a power of two number of blocks, which is going to be the protected amount, as well as telling whether this is the data at the top (end of the device) or the bottom (beginning of the device). This means at most we can cover half of the device or the entire device, but nothing in between. The complement feature allows a much finer grain of configuration, by allowing to invert what is considered locked and unlocked. Add support for this feature. The only known position for the CMP bit is bit 6 of the configuration register. The locking and unlocking logics are kept unchanged if the CMP bit is unavailable. Otherwise, once the regular logic has been applied, we check if we already found an optimal configuration. If not, we try with the CMP bit set. If the coverage is closer to the request, we use it. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 3 + drivers/mtd/spi-nor/core.h | 4 + drivers/mtd/spi-nor/debugfs.c | 1 + drivers/mtd/spi-nor/swp.c | 214 ++++++++++++++++++++++++++++------ include/linux/mtd/spi-nor.h | 1 + 5 files changed, 190 insertions(+), 33 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 1fc71d6e0fec..b25d1a870a22 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2974,6 +2974,9 @@ static void spi_nor_init_flags(struct spi_nor *nor) nor->flags |= SNOR_F_HAS_SR_BP3_BIT6; } + if (flags & SPI_NOR_HAS_CMP) + nor->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; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index ce46771ecdc8..ba2d1a862c9d 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -141,6 +141,7 @@ enum spi_nor_option_flags { SNOR_F_ECC = BIT(15), SNOR_F_NO_WP = BIT(16), SNOR_F_SWAP16 = BIT(17), + SNOR_F_HAS_SR2_CMP_BIT6 = BIT(18), }; struct spi_nor_read_command { @@ -491,6 +492,8 @@ struct spi_nor_id { * SPI_NOR_NO_ERASE: no erase command needed. * SPI_NOR_QUAD_PP: flash supports Quad Input Page Program. * SPI_NOR_RWW: flash supports reads while write. + * SPI_NOR_HAS_CMP: flash SR2 has complement (CMP) protect bit. Must + * 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 @@ -539,6 +542,7 @@ struct flash_info { #define SPI_NOR_NO_ERASE BIT(6) #define SPI_NOR_QUAD_PP BIT(8) #define SPI_NOR_RWW BIT(9) +#define SPI_NOR_HAS_CMP BIT(10) u8 no_sfdp_flags; #define SPI_NOR_SKIP_SFDP BIT(0) diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index ef710bd4f307..e80c03dd5461 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -30,6 +30,7 @@ static const char *const snor_f_names[] = { SNOR_F_NAME(ECC), SNOR_F_NAME(NO_WP), SNOR_F_NAME(SWAP16), + SNOR_F_NAME(HAS_SR2_CMP_BIT6), }; #undef SNOR_F_NAME diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 2411d8f1012d..235070b215d1 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -34,6 +34,15 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) return 0; } +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) + return SR2_CMP_BIT6; + else + return 0; +} + u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) { unsigned int bp_slots, bp_slots_needed; @@ -61,8 +70,10 @@ void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, 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 cmp = sr[1] & cmp_mask; if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6) val = (val & ~SR_BP3_BIT6) | SR_BP3; @@ -70,22 +81,37 @@ void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, bp = val >> SR_BP_SHIFT; if (!bp) { - /* No protection */ - *ofs = 0; - *len = 0; + if (!cmp) { + /* No protection */ + *ofs = 0; + *len = 0; + } else { + /* Full protection */ + *ofs = 0; + *len = nor->params->size; + } return; } min_prot_len = spi_nor_get_min_prot_length_sr(nor); *len = min_prot_len << (bp - 1); - if (*len > nor->params->size) *len = nor->params->size; - if (tb) - *ofs = 0; - else - *ofs = nor->params->size - *len; + if (cmp) + *len = nor->params->size - *len; + + if (!cmp) { + if (tb) + *ofs = 0; + else + *ofs = nor->params->size - *len; + } else { + if (tb) + *ofs = nor->params->size - *len; + else + *ofs = 0; + } } /* @@ -142,13 +168,15 @@ static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow) } static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, - u8 pow, bool use_top) + u8 pow, bool use_top, bool cmp) { 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); int ret; new_sr[0] = old_sr[0] & ~bp_mask & ~tb_mask; + new_sr[1] = old_sr[1] & ~cmp_mask; /* Build BP field */ ret = spi_nor_sr_set_bp_mask(nor, &new_sr[0], pow); @@ -156,9 +184,13 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr, return ret; /* Build TB field */ - if (!use_top) + if ((!cmp && !use_top) || (cmp && use_top)) new_sr[0] |= tb_mask; + /* Build CMP field */ + if (cmp) + new_sr[1] |= cmp_mask; + return 0; } @@ -170,15 +202,27 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) { 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 sr_cr[2] = {}; + if (!sr) { if (spi_nor_read_sr(nor, nor->bouncebuf)) return; - sr = nor->bouncebuf; + sr_cr[0] = nor->bouncebuf[0]; + + if (!(nor->flags & SNOR_F_NO_READ_CR)) { + if (spi_nor_read_cr(nor, nor->bouncebuf)) + return; + } + + sr_cr[1] = nor->bouncebuf[0]; + sr = sr_cr; } nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD); + nor->dfs_sr_cache[1] = sr[1] & cmp_mask; } /* @@ -187,10 +231,11 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr) * register * (SR). Does not support these features found in newer SR bitfields: * - SEC: sector/block protect - only handle SEC=0 (block protect) - * - CMP: complement protect - only support CMP=0 (range is not complemented) * * Support for the following is provided conditionally for some flash: * - TB: top/bottom protect + * - CMP: complement protect (BP and TP describe the unlocked part, while + * the reminder is locked) * * Sample table portion for 8MB flash (Winbond w25q64fw): * @@ -217,11 +262,13 @@ 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) { u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); - u8 status_old[1] = {}, status_new[1] = {}; - loff_t ofs_old, ofs_new; - u64 len_old, len_new; + 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 = nor->flags & SNOR_F_HAS_SR_TB, + can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; u8 pow; @@ -232,6 +279,14 @@ 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)) { + 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; @@ -262,27 +317,60 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len) else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false); if (ret) return ret; + /* + * In case the region asked is not fully met, maybe we can try with the + * complement feature + */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + if (can_be_cmp && len_new != lock_len) { + pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + + /* + * ilog2() "floors" the result, which means in some cases we may have to + * manually reduce the scope when the complement feature is used. + * The uAPI is to never lock more than what is requested, but less is accepted. + * Make sure we are not covering a too wide range, reduce it otherwise. + */ + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp > lock_len) { + pow++; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + } + + /* Pick the CMP configuration if we cover a closer range */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp <= lock_len && + (lock_len - len_new_cmp) < (lock_len - len_new)) + best_status_new = status_new_cmp; + } + /* * Disallow further writes if WP# pin is neither left floating nor * 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)) - status_new[0] |= SR_SRWD; + best_status_new[0] |= SR_SRWD; spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); - spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new); /* Don't "lock" with no region! */ if (!len_new) return -EINVAL; /* Don't bother if they're the same */ - if (status_new[0] == status_old[0]) + if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1]) return 0; /* Only modify protection if it will not unlock other areas */ @@ -290,11 +378,14 @@ 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; - ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (nor->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); if (ret) return ret; - spi_nor_cache_sr_lock_bits(nor, status_new); + spi_nor_cache_sr_lock_bits(nor, best_status_new); return 0; } @@ -307,11 +398,13 @@ 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) { u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor); - u8 status_old[1], status_new[1]; - loff_t ofs_old, ofs_new; - u64 len_old, len_new; + 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 = nor->flags & SNOR_F_HAS_SR_TB, + can_be_cmp = spi_nor_get_sr_cmp_mask(nor); bool use_top; int ret; u8 pow; @@ -322,6 +415,14 @@ 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)) { + 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; @@ -359,30 +460,66 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len) else pow = ilog2(lock_len) - ilog2(min_prot_len) + 1; - ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top); + ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false); if (ret) return ret; + /* + * In case the region asked is not fully met, maybe we can try with the + * complement feature + */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + if (can_be_cmp && len_new != lock_len) { + pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + + /* + * ilog2() "floors" the result, which means in some cases we may have to + * manually reduce the scope when the complement feature is used. + * The uAPI is to never unlock more than what is requested, but less is accepted. + * Make sure we are not covering a too small range, increase it otherwise. + */ + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp < lock_len) { + pow--; + ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true); + if (ret) + return ret; + } + + /* Pick the CMP configuration if we cover a closer range */ + spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp); + if (len_new_cmp <= lock_len && + (lock_len - len_new_cmp) < (lock_len - len_new)) + best_status_new = status_new_cmp; + } + /* Don't protect status register if we're fully unlocked */ if (lock_len == 0) - status_new[0] &= ~SR_SRWD; + best_status_new[0] &= ~SR_SRWD; /* Don't bother if they're the same */ - if (status_new[0] == status_old[0]) + if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1]) return 0; /* Only modify protection if it will not lock other areas */ spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old); - spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new); + spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new); if (len_old && len_new && (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new))) return -EINVAL; - ret = spi_nor_write_sr_and_check(nor, status_new[0]); + if (nor->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); if (ret) return ret; - spi_nor_cache_sr_lock_bits(nor, status_new); + spi_nor_cache_sr_lock_bits(nor, best_status_new); return 0; } @@ -396,13 +533,24 @@ 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) { + u8 sr_cr[2] = {}; int ret; ret = spi_nor_read_sr(nor, nor->bouncebuf); if (ret) return ret; - return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf); + sr_cr[0] = nor->bouncebuf[0]; + + if (!(nor->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); } static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = { diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 9ad77f9e76c2..4b92494827b1 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -125,6 +125,7 @@ #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 */ From f5d81415a7e12cc73b510bfac181411fedfe6345 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:46 +0200 Subject: [PATCH 51/63] mtd: spi-nor: Add steps for testing locking with CMP Extend the test coverage by giving guidelines to verify the CMP bit acts according to our expectations. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- Documentation/driver-api/mtd/spi-nor.rst | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst index f817a9193731..747a326fb6c0 100644 --- a/Documentation/driver-api/mtd/spi-nor.rst +++ b/Documentation/driver-api/mtd/spi-nor.rst @@ -336,3 +336,40 @@ section, after the ``---`` marker. ------------------+----------+--------- 00000000-0000ffff | locked | 1 00010000-03ffffff | unlocked | 1023 + + If the flash features a Complement (CMP) bit, we can protect with + more granularity above half of the capacity. Let's lock all but one + block, then unlock one more block:: + + root@1:~# all_but_one=$((($size / $bs) - ($ss / $bs))) + + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0000ffff | unlocked | 1 + 00010000-03ffffff | locked | 1023 + root@1:~# flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-0001ffff | unlocked | 2 + 00020000-03ffffff | locked | 1022 + root@1:~# flash_lock -u /dev/mtd0 + root@1:~# flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03feffff | locked | 1023 + 03ff0000-03ffffff | unlocked | 1 + root@1:~# flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two + root@1:~# show_sectors + software locked sectors + region (in hex) | status | #sectors + ------------------+----------+--------- + 00000000-03fdffff | locked | 1022 + 03fe0000-03ffffff | unlocked | 2 From 591be2ba7861be3ca3aabeb3fd2a3ce96ddb8601 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:47 +0200 Subject: [PATCH 52/63] mtd: spi-nor: winbond: Add W25H512NWxxAM CMP locking support This chip has support for the locking complement (CMP) feature. Add the relevant bit to enable it. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- 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 eaa547d36aad..ef73c855cedb 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -360,7 +360,8 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25H512NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x20), - .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, }, { /* W25H01NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x21), From 855599425e1aefb499e7bf90c34f708ebbb63045 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:48 +0200 Subject: [PATCH 53/63] mtd: spi-nor: winbond: Add W25H01NWxxAM CMP locking support This chip has support for the locking complement (CMP) feature. Add the relevant bit to enable it. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- 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 ef73c855cedb..7628fa7fb64f 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -365,7 +365,8 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25H01NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x21), - .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, }, { /* W25H02NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x22), From 751e4b02c469ac84e390c472fd30e2c512e3f587 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:49 +0200 Subject: [PATCH 54/63] mtd: spi-nor: winbond: Add W25H02NWxxAM CMP locking support This chip has support for the locking complement (CMP) feature. Add the relevant bit to enable it. Unfortunately, this chip also comes with an incorrect BFPT table, indicating the Control Register cannot be read back. This is wrong, reading back the register works and has no (observed) side effect. The datasheet clearly indicates supporting the 35h command and all bits from the CR are marked readable. QE and CMP bits are inside, and can be properly read back. Add a fixup for this, otherwise it would defeat the use of the CMP feature. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/winbond.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 7628fa7fb64f..2c48d6c4a0aa 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -73,6 +73,26 @@ 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->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'. @@ -370,7 +390,9 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25H02NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x22), - .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, + .fixups = &winbond_rdcr_fixup, }, }; From f468f05f0724cf7f7a993f9805185ea8cc72453c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:50 +0200 Subject: [PATCH 55/63] mtd: spi-nor: winbond: Add W25Q01NWxxIQ CMP locking support This chip has support for the locking complement (CMP) feature. Add the relevant bit to enable it. Unfortunately, this chip also comes with an incorrect BFPT table, indicating the Control Register cannot be read back. This is wrong, reading back the register works and has no (observed) side effect. The datasheet clearly indicates supporting the 35h command and all bits from the CR are marked readable. QE and CMP bits are inside, and can be properly read back. Add a fixup for this, otherwise it would defeat the use of the CMP feature. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- 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 2c48d6c4a0aa..3eca7baa4d5a 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -368,7 +368,9 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* 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, + .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 eb403cb56e13d7efd742511c1a92b89dc9db8658 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:51 +0200 Subject: [PATCH 56/63] mtd: spi-nor: winbond: Add W25Q01NWxxIM CMP locking support This chip has support for the locking complement (CMP) feature. Add the relevant bit to enable it. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- 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 3eca7baa4d5a..3a3b7f2f1659 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -374,7 +374,8 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25Q01NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x21), - .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, }, { /* W25Q02NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x22), From 48007986232858f94fc1ba2af4b360008e3e146b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Tue, 26 May 2026 16:56:52 +0200 Subject: [PATCH 57/63] mtd: spi-nor: winbond: Add W25Q02NWxxIM CMP locking support This chip has support for the locking complement (CMP) feature. Add the relevant bit to enable it. Unfortunately, this chip also comes with an incorrect BFPT table, indicating the Control Register cannot be read back. This is wrong, reading back the register works and has no (observed) side effect. The datasheet clearly indicates supporting the 35h command and all bits from the CR are marked readable. QE and CMP bits are inside, and can be properly read back. Add a fixup for this, otherwise it would defeat the use of the CMP feature. Signed-off-by: Miquel Raynal Signed-off-by: Pratyush Yadav --- 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 3a3b7f2f1659..8ebdbcec0b3f 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -379,7 +379,9 @@ static const struct flash_info winbond_nor_parts[] = { }, { /* W25Q02NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x22), - .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, + .fixups = &winbond_rdcr_fixup, }, { /* W25H512NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x20), From 306e443156b82a2a14a3f33da908c303be45529c Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Wed, 27 May 2026 18:05:39 +0900 Subject: [PATCH 58/63] mtd: spi-nor: spansion: use die erase for multi-die devices only Die erase opcode is supported in multi-die devices only. For single die devices, default chip erase opcode must be used. In s25hx_t_late_init(), die erase opcode is set only when the device is multi-die. Fixes: 461d0babb544 ("mtd: spi-nor: spansion: enable die erase for multi die flashes") Cc: stable@kernel.org Reviewed-by: Pratyush Yadav Reviewed-by: Tudor Ambarus Reviewed-by: Miquel Raynal Signed-off-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/spansion.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 8498c7003d88..b6023076903a 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -674,7 +674,9 @@ static int s25hx_t_late_init(struct spi_nor *nor) params->ready = cypress_nor_sr_ready_and_clear; cypress_nor_ecc_init(nor); - params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + if (params->n_dice > 1) + params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + return 0; } From df415c5e1de0f1aeefacb4e6252ff98d38c04437 Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Wed, 27 May 2026 18:05:40 +0900 Subject: [PATCH 59/63] mtd: spi-nor: spansion: add die erase support in s28hx-t S28Hx-T family has multi-die devices that support die erase opcode. Update die erase opcode when the device is multi-die. Reviewed-by: Tudor Ambarus Reviewed-by: Pratyush Yadav Reviewed-by: Miquel Raynal Signed-off-by: Takahiro Kuwano Signed-off-by: Pratyush Yadav --- drivers/mtd/spi-nor/spansion.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index b6023076903a..65227d989de1 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -762,6 +762,9 @@ static int s28hx_t_late_init(struct spi_nor *nor) params->ready = cypress_nor_sr_ready_and_clear; cypress_nor_ecc_init(nor); + if (params->n_dice > 1) + params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE; + return 0; } From 4f2692a5383e4bdd43ae940cda012360f7217a2d Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 1 Jun 2026 19:07:23 -0700 Subject: [PATCH 60/63] mtd: rawnand: ndfc: use ioread32be/iowrite32be and allow COMPILE_TEST Replace ppc4xx-specific in_be32/out_be32 with generic ioread32be/ iowrite32be to make the driver portable. Add COMPILE_TEST dependency to get build coverage on non-ppc4xx architectures. While at it, replace 4xx with 44x. The latter was removed a while ago and is only kept for compatibility. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/Kconfig | 2 +- drivers/mtd/nand/raw/ndfc.c | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 7408f34f0c68..ee01b845694d 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -71,7 +71,7 @@ config MTD_NAND_AU1550 config MTD_NAND_NDFC tristate "IBM/MCC 4xx NAND controller" - depends on 4xx + depends on 44x || COMPILE_TEST select MTD_NAND_ECC_SW_HAMMING select MTD_NAND_ECC_SW_HAMMING_SMC help diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c index 7ad8bc04be1a..a937ca3eeff5 100644 --- a/drivers/mtd/nand/raw/ndfc.c +++ b/drivers/mtd/nand/raw/ndfc.c @@ -44,13 +44,13 @@ static void ndfc_select_chip(struct nand_chip *nchip, int chip) uint32_t ccr; struct ndfc_controller *ndfc = nand_get_controller_data(nchip); - ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); + ccr = ioread32be(ndfc->ndfcbase + NDFC_CCR); if (chip >= 0) { ccr &= ~NDFC_CCR_BS_MASK; ccr |= NDFC_CCR_BS(chip + ndfc->chip_select); } else ccr |= NDFC_CCR_RESET_CE; - out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); + iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR); } static void ndfc_hwcontrol(struct nand_chip *chip, int cmd, unsigned int ctrl) @@ -70,7 +70,7 @@ static int ndfc_ready(struct nand_chip *chip) { struct ndfc_controller *ndfc = nand_get_controller_data(chip); - return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; + return ioread32be(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; } static void ndfc_enable_hwecc(struct nand_chip *chip, int mode) @@ -78,9 +78,9 @@ static void ndfc_enable_hwecc(struct nand_chip *chip, int mode) uint32_t ccr; struct ndfc_controller *ndfc = nand_get_controller_data(chip); - ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); + ccr = ioread32be(ndfc->ndfcbase + NDFC_CCR); ccr |= NDFC_CCR_RESET_ECC; - out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); + iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR); wmb(); } @@ -92,7 +92,7 @@ static int ndfc_calculate_ecc(struct nand_chip *chip, uint8_t *p = (uint8_t *)&ecc; wmb(); - ecc = in_be32(ndfc->ndfcbase + NDFC_ECC); + ecc = ioread32be(ndfc->ndfcbase + NDFC_ECC); /* The NDFC uses Smart Media (SMC) bytes order */ ecc_code[0] = p[1]; ecc_code[1] = p[2]; @@ -114,7 +114,7 @@ static void ndfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) uint32_t *p = (uint32_t *) buf; for(;len > 0; len -= 4) - *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA); + *p++ = ioread32be(ndfc->ndfcbase + NDFC_DATA); } static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) @@ -123,7 +123,7 @@ static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) uint32_t *p = (uint32_t *) buf; for(;len > 0; len -= 4) - out_be32(ndfc->ndfcbase + NDFC_DATA, *p++); + iowrite32be(*p++, ndfc->ndfcbase + NDFC_DATA); } /* @@ -223,13 +223,13 @@ static int ndfc_probe(struct platform_device *ofdev) if (reg) ccr |= be32_to_cpup(reg); - out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); + iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR); /* Set the bank settings if given */ reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL); if (reg) { int offset = NDFC_BCFG0 + (ndfc->chip_select << 2); - out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg)); + iowrite32be(be32_to_cpup(reg), ndfc->ndfcbase + offset); } err = ndfc_chip_init(ndfc, ofdev->dev.of_node); From 36f1648644d769c496a8e47e53603e863e358d73 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Tue, 9 Jun 2026 16:45:27 +0800 Subject: [PATCH 61/63] mtd: slram: remove failed entries from the device list register_device() links a new slram_mtdlist entry before allocating all of the state needed by the entry. If a later allocation, memremap(), or mtd_device_register() fails, the partially initialized entry remains on the global list. A later cleanup can then dereference or free invalid state from that failed entry. Unwind the partially initialized entry and clear the list tail on each failure path after the entry has been linked. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ruoyu Wang Signed-off-by: Miquel Raynal --- drivers/mtd/devices/slram.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c index 69cb63d99f57..48c2bc6b65ee 100644 --- a/drivers/mtd/devices/slram.c +++ b/drivers/mtd/devices/slram.c @@ -129,6 +129,7 @@ static int slram_write(struct mtd_info *mtd, loff_t to, size_t len, static int register_device(char *name, unsigned long start, unsigned long length) { slram_mtd_list_t **curmtd; + int ret = -ENOMEM; curmtd = &slram_mtdlist; while (*curmtd) { @@ -155,14 +156,15 @@ static int register_device(char *name, unsigned long start, unsigned long length if (!(*curmtd)->mtdinfo) { E("slram: Cannot allocate new MTD device.\n"); - return(-ENOMEM); + goto err_free_list; } if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start = memremap(start, length, MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC))) { E("slram: memremap failed\n"); - return -EIO; + ret = -EIO; + goto err_free_priv; } ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end = ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length; @@ -183,10 +185,8 @@ static int register_device(char *name, unsigned long start, unsigned long length if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) { E("slram: Failed to register new device\n"); - memunmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); - kfree((*curmtd)->mtdinfo->priv); - kfree((*curmtd)->mtdinfo); - return(-EAGAIN); + ret = -EAGAIN; + goto err_unmap; } T("slram: Registered device %s from %luKiB to %luKiB\n", name, (start / 1024), ((start + length) / 1024)); @@ -194,6 +194,16 @@ static int register_device(char *name, unsigned long start, unsigned long length ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start, ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end); return(0); + +err_unmap: + memunmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); +err_free_priv: + kfree((*curmtd)->mtdinfo->priv); +err_free_list: + kfree((*curmtd)->mtdinfo); + kfree(*curmtd); + *curmtd = NULL; + return ret; } static void unregister_devices(void) From 4fe97a54daddb221009964161fb362b2b930d657 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Tue, 9 Jun 2026 16:45:28 +0800 Subject: [PATCH 62/63] mtd: slram: simplify register_device() cleanup Use local variables for the list entry, mtd_info, and private data while initializing a new device. This keeps the initialization path easier to read and publishes the new list entry only after mtd_device_register() has succeeded. Signed-off-by: Ruoyu Wang Signed-off-by: Miquel Raynal --- drivers/mtd/devices/slram.c | 84 ++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c index 48c2bc6b65ee..aa38ecdffc97 100644 --- a/drivers/mtd/devices/slram.c +++ b/drivers/mtd/devices/slram.c @@ -129,6 +129,9 @@ static int slram_write(struct mtd_info *mtd, loff_t to, size_t len, static int register_device(char *name, unsigned long start, unsigned long length) { slram_mtd_list_t **curmtd; + slram_mtd_list_t *new_mtd; + struct mtd_info *mtdinfo; + slram_priv_t *priv; int ret = -ENOMEM; curmtd = &slram_mtdlist; @@ -136,73 +139,68 @@ static int register_device(char *name, unsigned long start, unsigned long length curmtd = &(*curmtd)->next; } - *curmtd = kmalloc_obj(slram_mtd_list_t); - if (!(*curmtd)) { + new_mtd = kmalloc_obj(slram_mtd_list_t); + if (!new_mtd) { E("slram: Cannot allocate new MTD device.\n"); return(-ENOMEM); } - (*curmtd)->mtdinfo = kzalloc_obj(struct mtd_info); - (*curmtd)->next = NULL; + new_mtd->next = NULL; - if ((*curmtd)->mtdinfo) { - (*curmtd)->mtdinfo->priv = - kzalloc_obj(slram_priv_t); - - if (!(*curmtd)->mtdinfo->priv) { - kfree((*curmtd)->mtdinfo); - (*curmtd)->mtdinfo = NULL; - } - } - - if (!(*curmtd)->mtdinfo) { + mtdinfo = kzalloc_obj(struct mtd_info); + if (!mtdinfo) { E("slram: Cannot allocate new MTD device.\n"); goto err_free_list; } + new_mtd->mtdinfo = mtdinfo; - if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start = - memremap(start, length, - MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC))) { + priv = kzalloc_obj(slram_priv_t); + if (!priv) { + E("slram: Cannot allocate new MTD device.\n"); + goto err_free_mtdinfo; + } + mtdinfo->priv = priv; + + priv->start = memremap(start, length, + MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC); + if (!priv->start) { E("slram: memremap failed\n"); ret = -EIO; goto err_free_priv; } - ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end = - ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length; + priv->end = priv->start + length; + mtdinfo->name = name; + mtdinfo->size = length; + mtdinfo->flags = MTD_CAP_RAM; + mtdinfo->_erase = slram_erase; + mtdinfo->_point = slram_point; + mtdinfo->_unpoint = slram_unpoint; + mtdinfo->_read = slram_read; + mtdinfo->_write = slram_write; + mtdinfo->owner = THIS_MODULE; + mtdinfo->type = MTD_RAM; + mtdinfo->erasesize = SLRAM_BLK_SZ; + mtdinfo->writesize = 1; - (*curmtd)->mtdinfo->name = name; - (*curmtd)->mtdinfo->size = length; - (*curmtd)->mtdinfo->flags = MTD_CAP_RAM; - (*curmtd)->mtdinfo->_erase = slram_erase; - (*curmtd)->mtdinfo->_point = slram_point; - (*curmtd)->mtdinfo->_unpoint = slram_unpoint; - (*curmtd)->mtdinfo->_read = slram_read; - (*curmtd)->mtdinfo->_write = slram_write; - (*curmtd)->mtdinfo->owner = THIS_MODULE; - (*curmtd)->mtdinfo->type = MTD_RAM; - (*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ; - (*curmtd)->mtdinfo->writesize = 1; - - if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) { + if (mtd_device_register(mtdinfo, NULL, 0)) { E("slram: Failed to register new device\n"); ret = -EAGAIN; goto err_unmap; } + *curmtd = new_mtd; T("slram: Registered device %s from %luKiB to %luKiB\n", name, (start / 1024), ((start + length) / 1024)); - T("slram: Mapped from 0x%p to 0x%p\n", - ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start, - ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end); - return(0); + T("slram: Mapped from 0x%p to 0x%p\n", priv->start, priv->end); + return 0; err_unmap: - memunmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); + memunmap(priv->start); err_free_priv: - kfree((*curmtd)->mtdinfo->priv); + kfree(priv); +err_free_mtdinfo: + kfree(mtdinfo); err_free_list: - kfree((*curmtd)->mtdinfo); - kfree(*curmtd); - *curmtd = NULL; + kfree(new_mtd); return ret; } From cf4ff717af69cfe2dff8ddb843dce36205cb3c40 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 10 Jun 2026 09:15:52 +0200 Subject: [PATCH 63/63] mtd: cfi: Use common error handling code in two functions Use additional labels so that a bit of exception handling can be better reused at the end of two function implementations. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Signed-off-by: Miquel Raynal --- drivers/mtd/chips/cfi_cmdset_0002.c | 13 +++++++------ drivers/mtd/chips/cfi_cmdset_0020.c | 24 +++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index a38aceb6612d..517db2f2707f 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -661,8 +661,7 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) extp->MajorVersion, extp->MinorVersion, extp->MajorVersion, extp->MinorVersion); kfree(extp); - kfree(mtd); - return NULL; + goto free_mtd; } printk(KERN_INFO " Amd/Fujitsu Extended Query version %c.%c.\n", @@ -714,10 +713,8 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) } cfi_fixup(mtd, cfi_nopri_fixup_table); - if (!cfi->addr_unlock1 || !cfi->addr_unlock2) { - kfree(mtd); - return NULL; - } + if (!cfi->addr_unlock1 || !cfi->addr_unlock2) + goto free_mtd; } /* CFI mode */ else if (cfi->cfi_mode == CFI_MODE_JEDEC) { @@ -755,6 +752,10 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) map->fldrv = &cfi_amdstd_chipdrv; return cfi_amdstd_setup(mtd); + +free_mtd: + kfree(mtd); + return NULL; } struct mtd_info *cfi_cmdset_0006(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002"))); struct mtd_info *cfi_cmdset_0701(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002"))); diff --git a/drivers/mtd/chips/cfi_cmdset_0020.c b/drivers/mtd/chips/cfi_cmdset_0020.c index 6b5727eaae69..593ac65a7e2f 100644 --- a/drivers/mtd/chips/cfi_cmdset_0020.c +++ b/drivers/mtd/chips/cfi_cmdset_0020.c @@ -174,11 +174,8 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) mtd = kzalloc_obj(*mtd); //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips); - - if (!mtd) { - kfree(cfi->cmdset_priv); - return NULL; - } + if (!mtd) + goto free_cmdset_priv; mtd->priv = map; mtd->type = MTD_NORFLASH; @@ -187,11 +184,8 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; mtd->eraseregions = kmalloc_objs(struct mtd_erase_region_info, mtd->numeraseregions); - if (!mtd->eraseregions) { - kfree(cfi->cmdset_priv); - kfree(mtd); - return NULL; - } + if (!mtd->eraseregions) + goto free_mtd; for (i=0; icfiq->NumEraseRegions; i++) { unsigned long ernum, ersize; @@ -213,9 +207,7 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) /* Argh */ printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize); kfree(mtd->eraseregions); - kfree(cfi->cmdset_priv); - kfree(mtd); - return NULL; + goto free_mtd; } for (i=0; inumeraseregions;i++){ @@ -242,6 +234,12 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) __module_get(THIS_MODULE); mtd->name = map->name; return mtd; + +free_mtd: + kfree(mtd); +free_cmdset_priv: + kfree(cfi->cmdset_priv); + return NULL; }