From 5c543de856c463235cef0808c2b100e475fc8438 Mon Sep 17 00:00:00 2001 From: Amit Kumar Mahapatra Date: Wed, 4 Feb 2026 09:32:16 +0100 Subject: [PATCH 01/44] dt-bindings: mtd: Describe MTD partitions concatenation The AMD QSPI controller supports an advanced connection modes called Stacked mode which allow the controller to treat two different flashes as one storage. In Stacked connection mode flashes share the same SPI bus, but different CS line, controller driver asserts the CS of the flash to which it needs to communicate. Stacked mode is a software abstraction rather than a controller feature or capability. At any given time, the controller communicates with one of the two connected flash devices, as determined by the requested address and data length. If an operation starts on one flash and ends on the other, the mtd layer needs to split it into two separate operations and adjust the data length accordingly. For more information on the modes please feel free to go through the controller flash interface below [1]. To support stacked mode, the existing MTD concat driver has been extended to be more generic, enabling multiple sets of MTD partitions to be virtually concatenated, with each set forming a distinct logical MTD device. A new Device Tree property is introduced to facilitate this, containing phandles of the partitions to be concatenated with the one where the property is defined. This approach supports multiple sets of concatenated partitions. [1] https://docs.amd.com/r/en-US/am011-versal-acap-trm/QSPI-Flash-Device-Interface Suggested-by: Miquel Raynal Suggested-by: Rob Herring Signed-off-by: Amit Kumar Mahapatra Reviewed-by: Rob Herring (Arm) Signed-off-by: Luca Ceresoli Signed-off-by: Miquel Raynal --- .../bindings/mtd/partitions/partition.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/mtd/partitions/partition.yaml b/Documentation/devicetree/bindings/mtd/partitions/partition.yaml index 2397d97ecac5..eaeac2f2ea94 100644 --- a/Documentation/devicetree/bindings/mtd/partitions/partition.yaml +++ b/Documentation/devicetree/bindings/mtd/partitions/partition.yaml @@ -57,6 +57,15 @@ properties: user space from type: boolean + part-concat-next: + description: List of phandles to MTD partitions that need be concatenated + with the current partition. + $ref: /schemas/types.yaml#/definitions/phandle-array + minItems: 1 + maxItems: 16 + items: + maxItems: 1 + align: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 2 @@ -180,4 +189,15 @@ examples: reg = <0x200000 0x100000>; align = <0x4000>; }; + + part0: partition@400000 { + part-concat-next = <&part1>; + label = "part0_0"; + reg = <0x400000 0x100000>; + }; + + part1: partition@800000 { + label = "part0_1"; + reg = <0x800000 0x800000>; + }; }; From 59509da0cb51dc48e4edc57d7d3ef1d424c58fc9 Mon Sep 17 00:00:00 2001 From: Amit Kumar Mahapatra Date: Wed, 4 Feb 2026 09:32:17 +0100 Subject: [PATCH 02/44] mtd: Move struct mtd_concat definition to header file To enable a more generic approach for concatenating MTD devices, struct mtd_concat should be accessible beyond the mtdconcat driver. Therefore, the definition is being moved to a header file. Signed-off-by: Amit Kumar Mahapatra Signed-off-by: Luca Ceresoli Signed-off-by: Miquel Raynal --- drivers/mtd/mtdconcat.c | 12 ------------ include/linux/mtd/concat.h | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 9eb5d919d9ba..241d15235d01 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -20,18 +20,6 @@ #include -/* - * Our storage structure: - * Subdev points to an array of pointers to struct mtd_info objects - * which is allocated along with this structure - * - */ -struct mtd_concat { - struct mtd_info mtd; - int num_subdev; - struct mtd_info **subdev; -}; - /* * how to calculate the size required for the above structure, * including the pointer array subdev points to: diff --git a/include/linux/mtd/concat.h b/include/linux/mtd/concat.h index d6f653e07426..b42d9af87c4e 100644 --- a/include/linux/mtd/concat.h +++ b/include/linux/mtd/concat.h @@ -9,6 +9,18 @@ #define MTD_CONCAT_H +/* + * Our storage structure: + * Subdev points to an array of pointers to struct mtd_info objects + * which is allocated along with this structure + * + */ +struct mtd_concat { + struct mtd_info mtd; + int num_subdev; + struct mtd_info **subdev; +}; + struct mtd_info *mtd_concat_create( struct mtd_info *subdev[], /* subdevices to concatenate */ int num_devs, /* number of subdevices */ From 43db6366fc2de02050e66389f5628d3fdc9af10a Mon Sep 17 00:00:00 2001 From: Amit Kumar Mahapatra Date: Wed, 4 Feb 2026 09:32:18 +0100 Subject: [PATCH 03/44] mtd: Add driver for concatenating devices Introducing CONFIG_MTD_VIRT_CONCAT to separate the legacy flow from the new approach, where only the concatenated partition is registered as an MTD device, while the individual partitions that form it are not registered independently, as they are typically not required by the user. CONFIG_MTD_VIRT_CONCAT is a boolean configuration option that depends on CONFIG_MTD_PARTITIONED_MASTER. When enabled, it allows flash nodes to be exposed as individual MTD devices along with the other partitions. The solution focuses on fixed-partitions description only as it depends on device boundaries. It supports multiple sets of concatenated devices, each comprising two or more partitions. flash@0 { reg = <0>; partitions { compatible = "fixed-partitions"; part0@0 { part-concat-next = <&flash0_part1>; label = "part0_0"; reg = <0x0 0x800000>; }; flash0_part1: part1@800000 { label = "part0_1"; reg = <800000 0x800000>; }; part2@1000000 { part-concat-next = <&flash1_part0>; label = "part0_2"; reg = <0x800000 0x800000>; }; }; }; flash@1 { reg = <1>; partitions { compatible = "fixed-partitions"; flash1_part0: part1@0 { label = "part1_0"; reg = <0x0 0x800000>; }; part1@800000 { label = "part1_1"; reg = <0x800000 0x800000>; }; }; }; The partitions that gets created are flash@0 part0_0-part0_1-concat flash@1 part1_1 part0_2-part1_0-concat Suggested-by: Bernhard Frauendienst Suggested-by: Miquel Raynal Signed-off-by: Amit Kumar Mahapatra Signed-off-by: Luca Ceresoli Signed-off-by: Miquel Raynal --- drivers/mtd/Kconfig | 9 + drivers/mtd/Makefile | 1 + drivers/mtd/mtd_virt_concat.c | 363 ++++++++++++++++++++++++++++++++++ drivers/mtd/mtdcore.c | 21 ++ drivers/mtd/mtdpart.c | 6 + include/linux/mtd/concat.h | 51 ++++- 6 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/mtd_virt_concat.c diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 796a2eccbef0..0421c6208de7 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -206,6 +206,15 @@ config MTD_PARTITIONED_MASTER the parent of the partition device be the master device, rather than what lies behind the master. +config MTD_VIRT_CONCAT + bool "Virtual concatenated MTD devices" + depends on MTD_PARTITIONED_MASTER + help + The driver enables the creation of virtual MTD device by + concatenating multiple physical MTD devices into a single + entity. This allows for the creation of partitions larger than + the individual physical chips, extending across chip boundaries. + source "drivers/mtd/chips/Kconfig" source "drivers/mtd/maps/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 593d0593a038..7b6dd53e8150 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -6,6 +6,7 @@ # Core functionality. obj-$(CONFIG_MTD) += mtd.o mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o +mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o obj-y += parsers/ diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c new file mode 100644 index 000000000000..aea88d1c9bc5 --- /dev/null +++ b/drivers/mtd/mtd_virt_concat.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Virtual concat MTD device driver + * + * Copyright (C) 2018 Bernhard Frauendienst + * Author: Bernhard Frauendienst + */ + +#include +#include +#include "mtdcore.h" +#include +#include +#include +#include +#include + +#define CONCAT_PROP "part-concat-next" +#define CONCAT_POSTFIX "concat" +#define MIN_DEV_PER_CONCAT 1 + +static LIST_HEAD(concat_node_list); + +/** + * struct mtd_virt_concat_node - components of a concatenation + * @head: List handle + * @count: Number of nodes + * @nodes: Pointer to the nodes (partitions) to concatenate + * @concat: Concatenation container + */ +struct mtd_virt_concat_node { + struct list_head head; + unsigned int count; + struct device_node **nodes; + struct mtd_concat *concat; +}; + +/** + * mtd_is_part_concat - Check if the device is already part + * of a concatenated device + * @dev: pointer to 'device_node' + * + * Return: true if the device is already part of a concatenation, + * false otherwise. + */ +static bool mtd_is_part_concat(struct device_node *dev) +{ + struct mtd_virt_concat_node *item; + int idx; + + list_for_each_entry(item, &concat_node_list, head) { + for (idx = 0; idx < item->count; idx++) { + if (item->nodes[idx] == dev) + return true; + } + } + return false; +} + +static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat) +{ + int i; + + for (i = 0; i < concat->num_subdev; i++) + put_mtd_device(concat->subdev[i]); +} + +void mtd_virt_concat_destroy_joins(void) +{ + struct mtd_virt_concat_node *item, *tmp; + struct mtd_info *mtd; + + list_for_each_entry_safe(item, tmp, &concat_node_list, head) { + mtd = &item->concat->mtd; + if (item->concat) { + mtd_device_unregister(mtd); + kfree(mtd->name); + mtd_concat_destroy(mtd); + mtd_virt_concat_put_mtd_devices(item->concat); + } + } +} + +/** + * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object + * @mtd: pointer to 'mtd_info' + * + * Return: 0 on success, -error otherwise. + */ +int mtd_virt_concat_destroy(struct mtd_info *mtd) +{ + struct mtd_info *child, *master = mtd_get_master(mtd); + struct mtd_virt_concat_node *item, *tmp; + struct mtd_concat *concat; + int idx, ret = 0; + bool is_mtd_found; + + list_for_each_entry_safe(item, tmp, &concat_node_list, head) { + is_mtd_found = false; + + /* Find the concat item that hold the mtd device */ + for (idx = 0; idx < item->count; idx++) { + if (item->nodes[idx] == mtd->dev.of_node) { + is_mtd_found = true; + break; + } + } + if (!is_mtd_found) + continue; + concat = item->concat; + + /* + * Since this concatenated device is being removed, retrieve + * all MTD devices that are part of it and register them + * individually. + */ + for (idx = 0; idx < concat->num_subdev; idx++) { + child = concat->subdev[idx]; + if (child->dev.of_node != mtd->dev.of_node) { + ret = add_mtd_device(child); + if (ret) + goto out; + } + } + /* Destroy the concat */ + if (concat->mtd.name) { + del_mtd_device(&concat->mtd); + kfree(concat->mtd.name); + mtd_concat_destroy(&concat->mtd); + mtd_virt_concat_put_mtd_devices(item->concat); + } + + for (idx = 0; idx < item->count; idx++) + of_node_put(item->nodes[idx]); + + kfree(item->nodes); + kfree(item); + } + return 0; +out: + mutex_lock(&master->master.partitions_lock); + list_del(&child->part.node); + mutex_unlock(&master->master.partitions_lock); + kfree(mtd->name); + kfree(mtd); + + return ret; +} + +/** + * mtd_virt_concat_create_item - Create a concat item + * @parts: pointer to 'device_node' + * @count: number of mtd devices that make up + * the concatenated device. + * + * Return: 0 on success, -error otherwise. + */ +static int mtd_virt_concat_create_item(struct device_node *parts, + unsigned int count) +{ + struct mtd_virt_concat_node *item; + struct mtd_concat *concat; + int i; + + for (i = 0; i < (count - 1); i++) { + if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i))) + return 0; + } + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + item->count = count; + item->nodes = kcalloc(count, sizeof(*item->nodes), GFP_KERNEL); + if (!item->nodes) { + kfree(item); + return -ENOMEM; + } + + /* + * The partition in which "part-concat-next" property + * is defined is the first device in the list of concat + * devices. + */ + item->nodes[0] = parts; + + for (i = 1; i < count; i++) + item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1)); + + concat = kzalloc(sizeof(*concat), GFP_KERNEL); + if (!concat) { + kfree(item); + return -ENOMEM; + } + + concat->subdev = kcalloc(count, sizeof(*concat->subdev), GFP_KERNEL); + if (!concat->subdev) { + kfree(item); + kfree(concat); + return -ENOMEM; + } + item->concat = concat; + + list_add_tail(&item->head, &concat_node_list); + + return 0; +} + +void mtd_virt_concat_destroy_items(void) +{ + struct mtd_virt_concat_node *item, *temp; + int i; + + list_for_each_entry_safe(item, temp, &concat_node_list, head) { + for (i = 0; i < item->count; i++) + of_node_put(item->nodes[i]); + + kfree(item->nodes); + kfree(item); + } +} + +/** + * mtd_virt_concat_create_add - Add a mtd device to the concat list + * @mtd: pointer to 'mtd_info' + * + * Return: true on success, false otherwise. + */ +bool mtd_virt_concat_add(struct mtd_info *mtd) +{ + struct mtd_virt_concat_node *item; + struct mtd_concat *concat; + int idx; + + list_for_each_entry(item, &concat_node_list, head) { + concat = item->concat; + for (idx = 0; idx < item->count; idx++) { + if (item->nodes[idx] == mtd->dev.of_node) { + concat->subdev[concat->num_subdev++] = mtd; + return true; + } + } + } + return false; +} + +/** + * mtd_virt_concat_node_create - List all the concatenations found in DT + * + * Return: 0 on success, -error otherwise. + */ +int mtd_virt_concat_node_create(void) +{ + struct device_node *parts = NULL; + int ret = 0, count = 0; + + /* List all the concatenations found in DT */ + do { + parts = of_find_node_with_property(parts, CONCAT_PROP); + if (!of_device_is_available(parts)) + continue; + + if (mtd_is_part_concat(parts)) + continue; + + count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL); + if (count < MIN_DEV_PER_CONCAT) + continue; + + /* + * The partition in which "part-concat-next" property is defined + * is also part of the concat device, so increament count by 1. + */ + count++; + + ret = mtd_virt_concat_create_item(parts, count); + if (ret) { + of_node_put(parts); + goto destroy_items; + } + } while (parts); + + return ret; + +destroy_items: + mtd_virt_concat_destroy_items(); + + return ret; +} + +/** + * mtd_virt_concat_create_join - Create and register the concatenated + * MTD device. + * + * Return: 0 on success, -error otherwise. + */ +int mtd_virt_concat_create_join(void) +{ + struct mtd_virt_concat_node *item; + struct mtd_concat *concat; + struct mtd_info *mtd; + ssize_t name_sz; + int ret, idx; + char *name; + + list_for_each_entry(item, &concat_node_list, head) { + concat = item->concat; + /* + * Check if item->count != concat->num_subdev, it indicates + * that the MTD information for all devices included in the + * concatenation are not handy, concat MTD device can't be + * created hence switch to next concat device. + */ + if (item->count != concat->num_subdev) { + continue; + } else { + /* Calculate the legth of the name of the virtual device */ + for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++) + name_sz += (strlen(concat->subdev[idx]->name) + 1); + name_sz += strlen(CONCAT_POSTFIX); + name = kmalloc(name_sz + 1, GFP_KERNEL); + if (!name) { + mtd_virt_concat_put_mtd_devices(concat); + return -ENOMEM; + } + + ret = 0; + for (idx = 0; idx < concat->num_subdev; idx++) { + ret += sprintf((name + ret), "%s-", + concat->subdev[idx]->name); + } + sprintf((name + ret), CONCAT_POSTFIX); + + if (concat->mtd.name) { + ret = memcmp(concat->mtd.name, name, name_sz); + if (ret == 0) + continue; + } + mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name); + if (!mtd) { + kfree(name); + return -ENXIO; + } + concat->mtd = *mtd; + /* Arbitrary set the first device as parent */ + concat->mtd.dev.parent = concat->subdev[0]->dev.parent; + concat->mtd.dev = concat->subdev[0]->dev; + + /* Add the mtd device */ + ret = add_mtd_device(&concat->mtd); + if (ret) + goto destroy_concat; + } + } + + return 0; + +destroy_concat: + mtd_concat_destroy(mtd); + + return ret; +} diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 64808493b4f5..576537774628 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -34,6 +34,7 @@ #include #include +#include #include "mtdcore.h" @@ -1120,6 +1121,12 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, goto out; } + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + ret = mtd_virt_concat_node_create(); + if (ret < 0) + goto out; + } + /* Prefer parsed partitions over driver-provided fallback */ ret = parse_mtd_partitions(mtd, types, parser_data); if (ret == -EPROBE_DEFER) @@ -1137,6 +1144,11 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, if (ret) goto out; + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + ret = mtd_virt_concat_create_join(); + if (ret < 0) + goto out; + } /* * FIXME: some drivers unfortunately call this function more than once. * So we have to check if we've already assigned the reboot notifier. @@ -1186,6 +1198,11 @@ int mtd_device_unregister(struct mtd_info *master) nvmem_unregister(master->otp_user_nvmem); nvmem_unregister(master->otp_factory_nvmem); + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + err = mtd_virt_concat_destroy(master); + if (err) + return err; + } err = del_mtd_partitions(master); if (err) return err; @@ -2621,6 +2638,10 @@ err_reg: static void __exit cleanup_mtd(void) { + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + mtd_virt_concat_destroy_joins(); + mtd_virt_concat_destroy_items(); + } debugfs_remove_recursive(dfs_dir_mtd); cleanup_mtdchar(); if (proc_mtd) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index e016cfbc7224..795a94e6b482 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "mtdcore.h" @@ -409,6 +410,11 @@ int add_mtd_partitions(struct mtd_info *parent, goto err_del_partitions; } + if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) { + if (mtd_virt_concat_add(child)) + continue; + } + mutex_lock(&master->master.partitions_lock); list_add_tail(&child->part.node, &parent->partitions); mutex_unlock(&master->master.partitions_lock); diff --git a/include/linux/mtd/concat.h b/include/linux/mtd/concat.h index b42d9af87c4e..2cd9d48958a8 100644 --- a/include/linux/mtd/concat.h +++ b/include/linux/mtd/concat.h @@ -28,5 +28,54 @@ struct mtd_info *mtd_concat_create( void mtd_concat_destroy(struct mtd_info *mtd); -#endif +/** + * mtd_virt_concat_node_create - Create a component for concatenation + * + * Returns a positive number representing the no. of devices found for + * concatenation, or a negative error code. + * + * List all the devices for concatenations found in DT and create a + * component for concatenation. + */ +int mtd_virt_concat_node_create(void); +/** + * mtd_virt_concat_add - add mtd_info object to the list of subdevices for concatenation + * @mtd: pointer to new MTD device info structure + * + * Returns true if the mtd_info object is added successfully else returns false. + * + * The mtd_info object is added to the list of subdevices for concatenation. + * It returns true if a match is found, and false if all subdevices have + * already been added or if the mtd_info object does not match any of the + * intended MTD devices. + */ +bool mtd_virt_concat_add(struct mtd_info *mtd); + +/** + * mtd_virt_concat_create_join - Create and register the concatenated MTD device + * + * Returns 0 on succes, or a negative error code. + * + * Creates and registers the concatenated MTD device + */ +int mtd_virt_concat_create_join(void); + +/** + * mtd_virt_concat_destroy - Remove the concat that includes a specific mtd device + * as one of its components. + * @mtd: pointer to MTD device info structure. + * + * Returns 0 on succes, or a negative error code. + * + * If the mtd_info object is part of a concatenated device, all other MTD devices + * within that concat are registered individually. The concatenated device is then + * removed, along with its concatenation component. + * + */ +int mtd_virt_concat_destroy(struct mtd_info *mtd); + +void mtd_virt_concat_destroy_joins(void); +void mtd_virt_concat_destroy_items(void); + +#endif From 43479bb3703f17da6cdfaa2a7f4b93db9c6908bc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 5 Feb 2026 19:49:15 +0100 Subject: [PATCH 04/44] mtd: spinand: Clean the flags section Mention that we are declaring the main SPI NAND flags with a comment. Align the values with tabs. Signed-off-by: Miquel Raynal --- include/linux/mtd/spinand.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 6a024cf1c53a..58abd306ebe3 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -477,8 +477,9 @@ struct spinand_ecc_info { const struct mtd_ooblayout_ops *ooblayout; }; -#define SPINAND_HAS_QE_BIT BIT(0) -#define SPINAND_HAS_CR_FEAT_BIT BIT(1) +/* SPI NAND flags */ +#define SPINAND_HAS_QE_BIT BIT(0) +#define SPINAND_HAS_CR_FEAT_BIT BIT(1) #define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2) #define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3) #define SPINAND_NO_RAW_ACCESS BIT(4) From 0c741b8b6963e584b41c284cd743c545636edb04 Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sat, 7 Feb 2026 11:02:43 +0400 Subject: [PATCH 05/44] mtd: nand: realtek-ecc: relax OOB size check to minimum The ECC engine strictly validates that flash OOB size equals exactly 64 bytes. However, some NAND chips have a larger physical OOB while vendor firmware only uses the first 64 bytes for the ECC layout. For example the Macronix MX35LF1G24AD found in the Netlink HG323DAC has 128 byte physical OOB but vendor firmware only uses the first 64 bytes (24 bytes free + 40 bytes BCH6 parity), leaving bytes 64-127 unused. Since the engine only operates on the first 64 bytes of OOB regardless of the physical size, change the check from exact match to minimum size. Flash with OOB >= 64 bytes works correctly with the engine's 64-byte layout. Suggested-by: Markus Stockhausen Signed-off-by: Ahmed Naseef Signed-off-by: Miquel Raynal --- drivers/mtd/nand/ecc-realtek.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c index 0046da37ea3e..7d003fd72027 100644 --- a/drivers/mtd/nand/ecc-realtek.c +++ b/drivers/mtd/nand/ecc-realtek.c @@ -17,10 +17,12 @@ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes * * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there - * are only two known devices in the wild that have NAND flash and make use of this ECC engine - * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only - * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte - * blocks and 64 bytes oob. + * are a few known devices in the wild that make use of this ECC engine + * (Linksys LGS328C, LGS352C & Netlink HG323DAC). To keep compatibility with vendor firmware, + * new modes can only be added when new data layouts have been analyzed. For now allow BCH6 on + * flash with 2048 byte blocks and at least 64 bytes oob. Some vendors make use of + * 128 bytes OOB NAND chips (e.g. Macronix MX35LF1G24AD) but only use BCH6 and thus the first + * 64 bytes of the OOB area. In this case the engine leaves any extra bytes unused. * * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the * Realtek naming conventions for the different structures in the OOB area. @@ -39,7 +41,7 @@ */ #define RTL_ECC_ALLOWED_PAGE_SIZE 2048 -#define RTL_ECC_ALLOWED_OOB_SIZE 64 +#define RTL_ECC_ALLOWED_MIN_OOB_SIZE 64 #define RTL_ECC_ALLOWED_STRENGTH 6 #define RTL_ECC_BLOCK_SIZE 512 @@ -310,10 +312,10 @@ static int rtl_ecc_check_support(struct nand_device *nand) struct mtd_info *mtd = nanddev_to_mtd(nand); struct device *dev = nand->ecc.engine->dev; - if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE || + if (mtd->oobsize < RTL_ECC_ALLOWED_MIN_OOB_SIZE || mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) { - dev_err(dev, "only flash geometry data=%d, oob=%d supported\n", - RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE); + dev_err(dev, "only flash geometry data=%d, oob>=%d supported\n", + RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_MIN_OOB_SIZE); return -EINVAL; } From d86e70e9ca995942e848515b089e9be7430c862e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 13 Feb 2026 12:08:25 -0500 Subject: [PATCH 06/44] dt-bindings: mtd: mxc-nand: add i.MX25 and i.MX27 nand support Add compatible string fsl,imx25-nand and fsl,imx27-nand (over 15 years chips). Add one optional clocks for it because i.MX25 and i.MX27 upstream DTS defines them. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- Documentation/devicetree/bindings/mtd/mxc-nand.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mtd/mxc-nand.yaml b/Documentation/devicetree/bindings/mtd/mxc-nand.yaml index bd8f7b683953..433ae5727ad8 100644 --- a/Documentation/devicetree/bindings/mtd/mxc-nand.yaml +++ b/Documentation/devicetree/bindings/mtd/mxc-nand.yaml @@ -15,7 +15,9 @@ allOf: properties: compatible: oneOf: - - const: fsl,imx27-nand + - enum: + - fsl,imx25-nand + - fsl,imx27-nand - items: - enum: - fsl,imx31-nand @@ -26,6 +28,9 @@ properties: interrupts: maxItems: 1 + clocks: + maxItems: 1 + required: - compatible - reg From d9a2a92b4209838c513f31eecc6c8bef4a107ab2 Mon Sep 17 00:00:00 2001 From: Vaibhav Gupta Date: Sat, 21 Feb 2026 08:11:57 +0000 Subject: [PATCH 07/44] mtd: rawnand: cafe: Use generic power management Switch from PCI power management to the generic power management framework so the pci_driver hooks can eventually be retired. Signed-off-by: Vaibhav Gupta Reviewed-by: Bjorn Helgaas Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/cafe_nand.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c index 65a36d5de742..c4018bc59670 100644 --- a/drivers/mtd/nand/raw/cafe_nand.c +++ b/drivers/mtd/nand/raw/cafe_nand.c @@ -837,9 +837,10 @@ static const struct pci_device_id cafe_nand_tbl[] = { MODULE_DEVICE_TABLE(pci, cafe_nand_tbl); -static int cafe_nand_resume(struct pci_dev *pdev) +static int cafe_nand_resume(struct device *dev) { uint32_t ctrl; + struct pci_dev *pdev = to_pci_dev(dev); struct mtd_info *mtd = pci_get_drvdata(pdev); struct nand_chip *chip = mtd_to_nand(mtd); struct cafe_priv *cafe = nand_get_controller_data(chip); @@ -877,12 +878,14 @@ static int cafe_nand_resume(struct pci_dev *pdev) return 0; } +static DEFINE_SIMPLE_DEV_PM_OPS(cafe_nand_ops, NULL, cafe_nand_resume); + static struct pci_driver cafe_nand_pci_driver = { .name = "CAFÉ NAND", .id_table = cafe_nand_tbl, .probe = cafe_nand_probe, .remove = cafe_nand_remove, - .resume = cafe_nand_resume, + .driver.pm = &cafe_nand_ops, }; module_pci_driver(cafe_nand_pci_driver); From 16d68d10f5b934db7ee02a1e28cbc539905c64b8 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 25 Feb 2026 17:23:34 +0100 Subject: [PATCH 08/44] mtd: physmap: physmap-bt1-rom: Remove not-going-to-be-supported code for Baikal SoC As noticed in the discussion [1] the Baikal SoC and platforms are not going to be finalized, hence remove stale code. Link: https://lore.kernel.org/lkml/22b92ddf-6321-41b5-8073-f9c7064d3432@infradead.org/ [1] Reviewed-by: Randy Dunlap Signed-off-by: Andy Shevchenko Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 11 --- drivers/mtd/maps/Makefile | 1 - drivers/mtd/maps/physmap-bt1-rom.c | 125 ----------------------------- drivers/mtd/maps/physmap-bt1-rom.h | 17 ---- drivers/mtd/maps/physmap-core.c | 1 - 5 files changed, 155 deletions(-) delete mode 100644 drivers/mtd/maps/physmap-bt1-rom.c delete mode 100644 drivers/mtd/maps/physmap-bt1-rom.h diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 8a8b19874e23..99d5ff9a1fbe 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -75,17 +75,6 @@ config MTD_PHYSMAP_OF physically into the CPU's memory. The mapping description here is taken from OF device tree. -config MTD_PHYSMAP_BT1_ROM - bool "Baikal-T1 Boot ROMs OF-based physical memory map handling" - depends on MTD_PHYSMAP_OF - depends on MIPS_BAIKAL_T1 || COMPILE_TEST - select MTD_COMPLEX_MAPPINGS - select MULTIPLEXER - select MUX_MMIO - help - This provides some extra DT physmap parsing for the Baikal-T1 - platforms, some detection and setting up ROMs-specific accessors. - config MTD_PHYSMAP_VERSATILE bool "ARM Versatile OF-based physical memory map handling" depends on MTD_PHYSMAP_OF diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 019f1e92cc41..51af1d2ebd52 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o obj-$(CONFIG_MTD_PHYSMAP) += physmap.o physmap-y := physmap-core.o -physmap-$(CONFIG_MTD_PHYSMAP_BT1_ROM) += physmap-bt1-rom.o physmap-$(CONFIG_MTD_PHYSMAP_VERSATILE) += physmap-versatile.o physmap-$(CONFIG_MTD_PHYSMAP_GEMINI) += physmap-gemini.o physmap-$(CONFIG_MTD_PHYSMAP_IXP4XX) += physmap-ixp4xx.o diff --git a/drivers/mtd/maps/physmap-bt1-rom.c b/drivers/mtd/maps/physmap-bt1-rom.c deleted file mode 100644 index 60dccc48f99e..000000000000 --- a/drivers/mtd/maps/physmap-bt1-rom.c +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * - * Baikal-T1 Physically Mapped Internal ROM driver - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "physmap-bt1-rom.h" - -/* - * Baikal-T1 SoC ROMs are only accessible by the dword-aligned instructions. - * We have to take this into account when implementing the data read-methods. - * Note there is no need in bothering with endianness, since both Baikal-T1 - * CPU and MMIO are LE. - */ -static map_word __xipram bt1_rom_map_read(struct map_info *map, - unsigned long ofs) -{ - void __iomem *src = map->virt + ofs; - unsigned int shift; - map_word ret; - u32 data; - - /* Read data within offset dword. */ - shift = (uintptr_t)src & 0x3; - data = readl_relaxed(src - shift); - if (!shift) { - ret.x[0] = data; - return ret; - } - ret.x[0] = data >> (shift * BITS_PER_BYTE); - - /* Read data from the next dword. */ - shift = 4 - shift; - if (ofs + shift >= map->size) - return ret; - - data = readl_relaxed(src + shift); - ret.x[0] |= data << (shift * BITS_PER_BYTE); - - return ret; -} - -static void __xipram bt1_rom_map_copy_from(struct map_info *map, - void *to, unsigned long from, - ssize_t len) -{ - void __iomem *src = map->virt + from; - unsigned int shift, chunk; - u32 data; - - if (len <= 0 || from >= map->size) - return; - - /* Make sure we don't go over the map limit. */ - len = min_t(ssize_t, map->size - from, len); - - /* - * Since requested data size can be pretty big we have to implement - * the copy procedure as optimal as possible. That's why it's split - * up into the next three stages: unaligned head, aligned body, - * unaligned tail. - */ - shift = (uintptr_t)src & 0x3; - if (shift) { - chunk = min_t(ssize_t, 4 - shift, len); - data = readl_relaxed(src - shift); - memcpy(to, (char *)&data + shift, chunk); - src += chunk; - to += chunk; - len -= chunk; - } - - while (len >= 4) { - data = readl_relaxed(src); - memcpy(to, &data, 4); - src += 4; - to += 4; - len -= 4; - } - - if (len) { - data = readl_relaxed(src); - memcpy(to, &data, len); - } -} - -int of_flash_probe_bt1_rom(struct platform_device *pdev, - struct device_node *np, - struct map_info *map) -{ - struct device *dev = &pdev->dev; - - /* It's supposed to be read-only MTD. */ - if (!of_device_is_compatible(np, "mtd-rom")) { - dev_info(dev, "No mtd-rom compatible string\n"); - return 0; - } - - /* Multiplatform guard. */ - if (!of_device_is_compatible(np, "baikal,bt1-int-rom")) - return 0; - - /* Sanity check the device parameters retrieved from DTB. */ - if (map->bankwidth != 4) - dev_warn(dev, "Bank width is supposed to be 32 bits wide\n"); - - map->read = bt1_rom_map_read; - map->copy_from = bt1_rom_map_copy_from; - - return 0; -} diff --git a/drivers/mtd/maps/physmap-bt1-rom.h b/drivers/mtd/maps/physmap-bt1-rom.h deleted file mode 100644 index 6782899598a4..000000000000 --- a/drivers/mtd/maps/physmap-bt1-rom.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#include -#include - -#ifdef CONFIG_MTD_PHYSMAP_BT1_ROM -int of_flash_probe_bt1_rom(struct platform_device *pdev, - struct device_node *np, - struct map_info *map); -#else -static inline -int of_flash_probe_bt1_rom(struct platform_device *pdev, - struct device_node *np, - struct map_info *map) -{ - return 0; -} -#endif diff --git a/drivers/mtd/maps/physmap-core.c b/drivers/mtd/maps/physmap-core.c index 0dcc25b7ff98..2e31c30d266c 100644 --- a/drivers/mtd/maps/physmap-core.c +++ b/drivers/mtd/maps/physmap-core.c @@ -42,7 +42,6 @@ #include #include -#include "physmap-bt1-rom.h" #include "physmap-gemini.h" #include "physmap-ixp4xx.h" #include "physmap-versatile.h" From b7c0982184b0661f5b1b805f3a56f1bd3757b63e Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Fri, 27 Feb 2026 09:43:36 +0800 Subject: [PATCH 09/44] mtd: physmap_of_gemini: Fix disabled pinctrl state check The condition for checking the disabled pinctrl state incorrectly checks gf->enabled_state instead of gf->disabled_state. This causes misleading error messages and could lead to incorrect behavior when only one of the pinctrl states is defined. Fix the condition to properly check gf->disabled_state. Fixes: 9d3b5086f6d4 ("mtd: physmap_of_gemini: Handle pin control") Signed-off-by: Chen Ni Reviewed-by: Linus Walleij Signed-off-by: Miquel Raynal --- drivers/mtd/maps/physmap-gemini.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/maps/physmap-gemini.c b/drivers/mtd/maps/physmap-gemini.c index 9d3b4bf84a1a..1c34b4ef77ea 100644 --- a/drivers/mtd/maps/physmap-gemini.c +++ b/drivers/mtd/maps/physmap-gemini.c @@ -181,7 +181,7 @@ int of_flash_probe_gemini(struct platform_device *pdev, dev_err(dev, "no enabled pin control state\n"); gf->disabled_state = pinctrl_lookup_state(gf->p, "disabled"); - if (IS_ERR(gf->enabled_state)) { + if (IS_ERR(gf->disabled_state)) { dev_err(dev, "no disabled pin control state\n"); } else { ret = pinctrl_select_state(gf->p, gf->disabled_state); From 87d8f1285470b3c8367880993113ea604d365e33 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 9 Mar 2026 22:17:48 +0100 Subject: [PATCH 10/44] mtd: virt_concat: fix kdoc text The function name in the kdoc comment is different from the name of the function being documented, fix it. Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202603041232.fNDHNtUa-lkp@intel.com/ Signed-off-by: Luca Ceresoli Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index aea88d1c9bc5..42c3bf189faa 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -222,7 +222,7 @@ void mtd_virt_concat_destroy_items(void) } /** - * mtd_virt_concat_create_add - Add a mtd device to the concat list + * mtd_virt_concat_add - Add a mtd device to the concat list * @mtd: pointer to 'mtd_info' * * Return: true on success, false otherwise. From c685e6e8d88d544e8c4429b06c3e6795cbba32dd Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 5 Mar 2026 14:44:09 -0800 Subject: [PATCH 11/44] mtd: virt_concat: use single allocation for node Simpler to reason about and avoids having to free nodes separately. Also add __counted_by attribute for extra runtime analysis. Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index 42c3bf189faa..72689545e48e 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -31,8 +31,8 @@ static LIST_HEAD(concat_node_list); struct mtd_virt_concat_node { struct list_head head; unsigned int count; - struct device_node **nodes; struct mtd_concat *concat; + struct device_node *nodes[] __counted_by(count); }; /** @@ -133,7 +133,6 @@ int mtd_virt_concat_destroy(struct mtd_info *mtd) for (idx = 0; idx < item->count; idx++) of_node_put(item->nodes[idx]); - kfree(item->nodes); kfree(item); } return 0; @@ -167,16 +166,11 @@ static int mtd_virt_concat_create_item(struct device_node *parts, return 0; } - item = kzalloc(sizeof(*item), GFP_KERNEL); + item = kzalloc_flex(*item, nodes, count, GFP_KERNEL); if (!item) return -ENOMEM; item->count = count; - item->nodes = kcalloc(count, sizeof(*item->nodes), GFP_KERNEL); - if (!item->nodes) { - kfree(item); - return -ENOMEM; - } /* * The partition in which "part-concat-next" property @@ -216,7 +210,6 @@ void mtd_virt_concat_destroy_items(void) for (i = 0; i < item->count; i++) of_node_put(item->nodes[i]); - kfree(item->nodes); kfree(item); } } From e19eaffc5213fdd6179e849d3032929fae0d8c2c Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 5 Mar 2026 14:44:10 -0800 Subject: [PATCH 12/44] mtd: concat: replace alloc + calloc with 1 alloc A flex array can be used to reduce the allocation to 1. And actually mtdconcat was using the pointer + 1 trick to point to the overallocated area. Better alternatives exist. Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 8 +------- drivers/mtd/mtdconcat.c | 5 +---- include/linux/mtd/concat.h | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index 72689545e48e..37075ead0f33 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -182,18 +182,12 @@ static int mtd_virt_concat_create_item(struct device_node *parts, for (i = 1; i < count; i++) item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1)); - concat = kzalloc(sizeof(*concat), GFP_KERNEL); + concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL); if (!concat) { kfree(item); return -ENOMEM; } - concat->subdev = kcalloc(count, sizeof(*concat->subdev), GFP_KERNEL); - if (!concat->subdev) { - kfree(item); - kfree(concat); - return -ENOMEM; - } item->concat = concat; list_add_tail(&item->head, &concat_node_list); diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 241d15235d01..c97167d51fe2 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -627,7 +627,6 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c const char *name) { /* name for the new device */ int i; - size_t size; struct mtd_concat *concat; struct mtd_info *subdev_master = NULL; uint32_t max_erasesize, curr_erasesize; @@ -640,15 +639,13 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c printk(KERN_NOTICE "into device \"%s\"\n", name); /* allocate the device structure */ - size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); - concat = kzalloc(size, GFP_KERNEL); + concat = kzalloc_flex(*concat, subdev, num_devs, GFP_KERNEL); if (!concat) { printk ("memory allocation error while creating concatenated device \"%s\"\n", name); return NULL; } - concat->subdev = (struct mtd_info **) (concat + 1); /* * Set up the new "super" device's MTD object structure, check for diff --git a/include/linux/mtd/concat.h b/include/linux/mtd/concat.h index 2cd9d48958a8..f8d4d6ac1fc1 100644 --- a/include/linux/mtd/concat.h +++ b/include/linux/mtd/concat.h @@ -18,7 +18,7 @@ struct mtd_concat { struct mtd_info mtd; int num_subdev; - struct mtd_info **subdev; + struct mtd_info *subdev[]; }; struct mtd_info *mtd_concat_create( From ca19808bc6fac7e29420d8508df569b346b3e339 Mon Sep 17 00:00:00 2001 From: James Kim Date: Mon, 9 Mar 2026 15:05:12 +0900 Subject: [PATCH 13/44] mtd: docg3: fix use-after-free in docg3_release() In docg3_release(), the docg3 pointer is obtained from cascade->floors[0]->priv before the loop that calls doc_release_device() on each floor. doc_release_device() frees the docg3 struct via kfree(docg3) at line 1881. After the loop, docg3->cascade->bch dereferences the already-freed pointer. Fix this by accessing cascade->bch directly, which is equivalent since docg3->cascade points back to the same cascade struct, and is already available as a local variable. This also removes the now-unused docg3 local variable. Fixes: c8ae3f744ddc ("lib/bch: Rework a little bit the exported function names") Cc: stable@vger.kernel.org Signed-off-by: James Kim Signed-off-by: Miquel Raynal --- drivers/mtd/devices/docg3.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c index 33050a2a80f7..603fd0efc2ea 100644 --- a/drivers/mtd/devices/docg3.c +++ b/drivers/mtd/devices/docg3.c @@ -2049,7 +2049,6 @@ err_probe: static void docg3_release(struct platform_device *pdev) { struct docg3_cascade *cascade = platform_get_drvdata(pdev); - struct docg3 *docg3 = cascade->floors[0]->priv; int floor; doc_unregister_sysfs(pdev, cascade); @@ -2057,7 +2056,7 @@ static void docg3_release(struct platform_device *pdev) if (cascade->floors[floor]) doc_release_device(cascade->floors[floor]); - bch_free(docg3->cascade->bch); + bch_free(cascade->bch); } #ifdef CONFIG_OF From 520886a1a6ca16862a0437b4a8fae133a895a9b8 Mon Sep 17 00:00:00 2001 From: Richard Lyu Date: Wed, 11 Mar 2026 00:30:43 +0800 Subject: [PATCH 14/44] mtd: nand: Use scoped_guard for mutex in nand_resume Refactor nand_resume() to use scoped_guard() instead of explicit mutex_lock/unlock. This improves code safety by ensuring the mutex is always released through the RAII-based cleanup infrastructure. The behavior is functionally equivalent. The mutex is released at the end of the scoped block, after which wake_up_all() is called to preserve the original locking semantics. Signed-off-by: Richard Lyu Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/nand_base.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 38429363251c..5c8951741855 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "internals.h" @@ -4704,16 +4705,16 @@ static void nand_resume(struct mtd_info *mtd) { struct nand_chip *chip = mtd_to_nand(mtd); - mutex_lock(&chip->lock); - if (chip->suspended) { - if (chip->ops.resume) - chip->ops.resume(chip); - chip->suspended = 0; - } else { - pr_err("%s called for a chip which is not in suspended state\n", - __func__); + scoped_guard(mutex, &chip->lock) { + if (chip->suspended) { + if (chip->ops.resume) + chip->ops.resume(chip); + chip->suspended = 0; + } else { + pr_err("%s called for a chip which is not in suspended state\n", + __func__); + } } - mutex_unlock(&chip->lock); wake_up_all(&chip->resume_wq); } From 3a6e21ea57c8118d3095f073aeaf9362dc2c3865 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 10 Mar 2026 15:07:37 -0400 Subject: [PATCH 15/44] mtd: rawnand: gpmi: set chip->of_node to nand@0 child node if present The nand-controller.yaml binding requires a child node (e.g. nand@0) under the NAND controller. However, the driver currently assigns the controller's of_node directly to the NAND chip. Search for the first child node with the "nand" prefix and assign it to chip->of_node. This filters out properties such as "partition" that may be placed under the controller node in some older DTS files. Fall back to using the controller's of_node if no suitable child node is found to maintain backward compatibility. This issue went unnoticed because the default behavior works for most NAND chips. Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index 51f595fbc834..c1f766cb225a 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -5,6 +5,7 @@ * Copyright (C) 2010-2015 Freescale Semiconductor, Inc. * Copyright (C) 2008 Embedded Alley Solutions, Inc. */ +#include #include #include #include @@ -2688,7 +2689,15 @@ static int gpmi_nand_init(struct gpmi_nand_data *this) /* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */ nand_set_controller_data(chip, this); - nand_set_flash_node(chip, this->pdev->dev.of_node); + + struct device_node *np __free(device_node) = + of_get_next_child_with_prefix(this->pdev->dev.of_node, NULL, "nand"); + + if (np) + nand_set_flash_node(chip, np); + else + nand_set_flash_node(chip, this->pdev->dev.of_node); + chip->legacy.block_markbad = gpmi_block_markbad; chip->badblock_pattern = &gpmi_bbt_descr; chip->options |= NAND_NO_SUBPAGE_WRITE; From f7bd1948a5461df9e1027d5bd9a511e754146689 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 10 Mar 2026 15:07:38 -0400 Subject: [PATCH 16/44] mtd: rawnand: mxc: set chip->of_node to nand@0 child node if present The nand-controller.yaml binding requires a child node (e.g. nand@0) under the NAND controller. However, the driver currently assigns the controller's of_node directly to the NAND chip. Search for the first child node with the "nand" prefix and assign it to chip->of_node. This filters out properties such as "partition" that may be placed under the controller node in some older DTS files. Fall back to using the controller's of_node if no suitable child node is found to maintain backward compatibility. This issue went unnoticed because the default behavior works for most NAND chips. Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/mxc_nand.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c index 8c56b685bf91..4d8b92e7e672 100644 --- a/drivers/mtd/nand/raw/mxc_nand.c +++ b/drivers/mtd/nand/raw/mxc_nand.c @@ -4,6 +4,7 @@ * Copyright 2008 Sascha Hauer, kernel@pengutronix.de */ +#include #include #include #include @@ -1714,7 +1715,14 @@ static int mxcnd_probe(struct platform_device *pdev) this->legacy.chip_delay = 5; nand_set_controller_data(this, host); - nand_set_flash_node(this, pdev->dev.of_node); + + struct device_node *np __free(device_node) = + of_get_next_child_with_prefix(pdev->dev.of_node, NULL, "nand"); + + if (np) + nand_set_flash_node(this, np); + else + nand_set_flash_node(this, pdev->dev.of_node); host->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(host->clk)) From ee78d466db8a001d137d6f9c97010b343aee456b Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 10 Mar 2026 15:07:39 -0400 Subject: [PATCH 17/44] mtd: rawnand: ifc: set chip->of_node to nand@0 child node if present The nand-controller.yaml binding requires a child node (e.g. nand@0) under the NAND controller. However, the driver currently assigns the controller's of_node directly to the NAND chip. Search for the first child node with the "nand" prefix and assign it to chip->of_node. This filters out properties such as "partition" that may be placed under the controller node in some older DTS files. Fall back to using the controller's of_node if no suitable child node is found to maintain backward compatibility. This issue went unnoticed because the default behavior works for most NAND chips. Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/fsl_ifc_nand.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c index dd88b22a91bd..fad0334f759d 100644 --- a/drivers/mtd/nand/raw/fsl_ifc_nand.c +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -7,6 +7,7 @@ * Author: Dipen Dudhat */ +#include #include #include #include @@ -863,7 +864,14 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) /* Fill in fsl_ifc_mtd structure */ mtd->dev.parent = priv->dev; - nand_set_flash_node(chip, priv->dev->of_node); + + struct device_node *np __free(device_node) = + of_get_next_child_with_prefix(priv->dev->of_node, NULL, "nand"); + + if (np) + nand_set_flash_node(chip, np); + else + nand_set_flash_node(chip, priv->dev->of_node); /* fill in nand_chip structure */ /* set up function call table */ From 756564a536ecd8c9d33edd89f0647a91a0b03587 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Mon, 8 Dec 2025 17:14:14 +0800 Subject: [PATCH 18/44] mtd: spi-nor: core: correct the op.dummy.nbytes when check read operations When check read operation, need to setting the op.dummy.nbytes based on current read operation rather than the nor->read_proto. Fixes: 0e30f47232ab ("mtd: spi-nor: add support for DTR protocol") Signed-off-by: Haibo Chen Reviewed-by: Pratyush Yadav Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 8ffeb41c3e08..e6c1fda61f57 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2393,7 +2393,7 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor, /* convert the dummy cycles to the number of bytes */ op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) * op.dummy.buswidth / 8; - if (spi_nor_protocol_is_dtr(nor->read_proto)) + if (spi_nor_protocol_is_dtr(read->proto)) op.dummy.nbytes *= 2; return spi_nor_spimem_check_op(nor, &op); From 3620d67b48493c6252bbc873dc88dde81641d56b Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Thu, 18 Dec 2025 10:54:30 +0100 Subject: [PATCH 19/44] mtd: spi-nor: update spi_nor_fixups::post_sfdp() documentation After commit 5273cc6df984 ("mtd: spi-nor: core: Call spi_nor_post_sfdp_fixups() only when SFDP is defined") spi_nor_post_sfdp_fixups() isn't called anymore if no SFDP is detected. Update the documentation accordingly. Fixes: 5273cc6df984 ("mtd: spi-nor: core: Call spi_nor_post_sfdp_fixups() only when SFDP is defined") Signed-off-by: Jonas Gorski Reviewed-by: Pratyush Yadav Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 16b382d4f04f..e838c40a2589 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -413,7 +413,7 @@ struct spi_nor_flash_parameter { * number of dummy cycles in read register ops. * @smpt_map_id: called after map ID in SMPT table has been determined for the * case the map ID is wrong and needs to be fixed. - * @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs + * @post_sfdp: called after SFDP has been parsed (is not called for SPI NORs * that do not support RDSFDP). Typically used to tweak various * parameters that could not be extracted by other means (i.e. * when information provided by the SFDP/flash_info tables are From 6d660fba6a32a34ad7d746d7f65317831daaf033 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Tue, 23 Dec 2025 11:01:02 +0800 Subject: [PATCH 20/44] mtd: spi-nor: micron-st: add SNOR_CMD_PP_8_8_8_DTR sfdp fixup for mt35xu512aba Find two batches mt35xu512aba has different SFDP but with same jedec ID. The batch which use the new version of SFDP contain all the necessary information to support OCT DTR mode. The batch with old version do not contain the OCT DTR command information, but in fact it did support OCT DTR mode. Current mt35xu512aba_post_sfdp_fixup() add some setting including SNOR_CMD_READ_8_8_8_DTR, but still lack SNOR_CMD_PP_8_8_8_DTR. Meet issue on the batch mt35xu512aba with old SFDP version. Because no SNOR_CMD_PP_8_8_8_DTR, micron_st_nor_octal_dtr_en() will not be called, then use SNOR_CMD_READ_8_8_8_DTR will meet issue. Fixes: 44dd635cd632 ("mtd: spi-nor: micron-st: use SFDP of mt35xu512aba") Reviewed-by: Pratyush Yadav Signed-off-by: Haibo Chen Reviewed-by: Michael Walle [pratyush@kernel.org: touch up the comment a bit] Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/micron-st.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 88033384a71e..b2b473501d02 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -167,6 +167,16 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor) 0, 20, SPINOR_OP_MT_DTR_RD, SNOR_PROTO_8_8_8_DTR); + /* + * Some batches of mt35xu512aba do not contain the OCT DTR command + * information, but do support OCT DTR mode. Add the settings for + * SNOR_CMD_PP_8_8_8_DTR here. This also makes sure the flash can switch + * to OCT DTR mode. + */ + nor->params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR; + spi_nor_set_pp_settings(&nor->params->page_programs[SNOR_CMD_PP_8_8_8_DTR], + SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR); + nor->cmd_ext_type = SPI_NOR_EXT_REPEAT; nor->params->rdsr_dummy = 8; nor->params->rdsr_addr_nbytes = 0; From 94645aa41bf9ecb87c2ce78b1c3405bfb6074a37 Mon Sep 17 00:00:00 2001 From: Shiji Yang Date: Wed, 28 Jan 2026 20:42:56 +0800 Subject: [PATCH 21/44] mtd: spi-nor: swp: check SR_TB flag when getting tb_mask When the chip does not support top/bottom block protect, the tb_mask must be set to 0, otherwise SR1 bit5 will be unexpectedly modified. Signed-off-by: Shiji Yang Fixes: 3dd8012a8eeb ("mtd: spi-nor: add TB (Top/Bottom) protect support") Reviewed-by: Michael Walle Reviewed-by: Miquel Raynal Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/swp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index 9b07f83aeac7..e67a81dbb6bf 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -28,8 +28,10 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor) { if (nor->flags & SNOR_F_HAS_SR_TB_BIT6) return SR_TB_BIT6; - else + else if (nor->flags & SNOR_F_HAS_SR_TB) return SR_TB_BIT5; + else + return 0; } static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor) From 54f955ba09367eb9abad6c4afd3bb81c2b7e0b54 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 13 Mar 2026 12:34:09 +0100 Subject: [PATCH 22/44] mtd: physmap: Drop leftovers of removed code for Baikal SoC The previous clean up killed the driver along with dropping some calls but missed one place to drop. Do it here. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202603121229.PPSg4X8q-lkp@intel.com/ Reported-by: Mark Brown Closes: https://lore.kernel.org/r/abK8KXC70RC2K_fW@sirena.org.uk Fixes: 16d68d10f5b93 "(mtd: physmap: physmap-bt1-rom: Remove not-going-to-be-supported code for Baikal SoC)" Signed-off-by: Andy Shevchenko Signed-off-by: Miquel Raynal --- drivers/mtd/maps/physmap-core.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/mtd/maps/physmap-core.c b/drivers/mtd/maps/physmap-core.c index 2e31c30d266c..dcda7685fc99 100644 --- a/drivers/mtd/maps/physmap-core.c +++ b/drivers/mtd/maps/physmap-core.c @@ -364,10 +364,6 @@ static int physmap_flash_of_init(struct platform_device *dev) info->maps[i].bankwidth = bankwidth; info->maps[i].device_node = dp; - err = of_flash_probe_bt1_rom(dev, dp, &info->maps[i]); - if (err) - return err; - err = of_flash_probe_gemini(dev, dp, &info->maps[i]); if (err) return err; From 0c87dea1aab86116211cb37387c404c9e9231c39 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 11 Mar 2026 17:39:56 +0200 Subject: [PATCH 23/44] mtd: parsers: ofpart: call of_node_put() only in ofpart_fail path ofpart_none can only be reached after the for_each_child_of_node() loop finishes. for_each_child_of_node() correctly calls of_node_put() for all device nodes it iterates over as long as we don't break or jump out of the loop. Calling of_node_put() inside the ofpart_none path will wrongly decrement the ref count of the last node in the for_each_child_of_node() loop. Move the call to of_node_put() under the ofpart_fail label to fix this. Fixes: ebd5a74db74e ("mtd: ofpart: Check availability of reg property instead of name property") Signed-off-by: Cosmin Tanislav Tested-by: Tommaso Merciai Signed-off-by: Miquel Raynal --- drivers/mtd/parsers/ofpart_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c index 0029bda165bd..181ae9616b2e 100644 --- a/drivers/mtd/parsers/ofpart_core.c +++ b/drivers/mtd/parsers/ofpart_core.c @@ -195,11 +195,11 @@ static int parse_fixed_partitions(struct mtd_info *master, ofpart_fail: pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n", master->name, pp, mtd_node); + of_node_put(pp); ret = -EINVAL; ofpart_none: if (dedicated) of_node_put(ofpart_node); - of_node_put(pp); kfree(parts); return ret; } From e882626c1747653f1f01ea9d12e278e613b11d0f Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 11 Mar 2026 17:39:57 +0200 Subject: [PATCH 24/44] mtd: parsers: ofpart: call of_node_get() for dedicated subpartitions In order to parse sub-partitions, add_mtd_partitions() calls parse_mtd_partitions() for all previously found partitions. Each partition will end up being passed to parse_fixed_partitions(), and its of_node will be treated as the ofpart_node. Commit 7cce81df7d26 ("mtd: parsers: ofpart: fix OF node refcount leak in parse_fixed_partitions()") added of_node_put() calls for ofpart_node on all exit paths. In the case where the partition passed to parse_fixed_partitions() has a parent, it is treated as a dedicated partitions node, and of_node_put() is wrongly called for it, even if of_node_get() was not called explicitly. On repeated bind / unbinds of the MTD, the extra of_node_put() ends up decrementing the refcount down to 0, which should never happen, resulting in the following error: OF: ERROR: of_node_release() detected bad of_node_put() on /soc/spi@80007000/flash@0/partitions/partition@0 Call of_node_get() to balance the call to of_node_put() done for dedicated partitions nodes. Fixes: 7cce81df7d26 ("mtd: parsers: ofpart: fix OF node refcount leak in parse_fixed_partitions()") Signed-off-by: Cosmin Tanislav Tested-by: Tommaso Merciai Signed-off-by: Miquel Raynal --- drivers/mtd/parsers/ofpart_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c index 181ae9616b2e..262c4221d23f 100644 --- a/drivers/mtd/parsers/ofpart_core.c +++ b/drivers/mtd/parsers/ofpart_core.c @@ -75,7 +75,7 @@ static int parse_fixed_partitions(struct mtd_info *master, dedicated = false; } } else { /* Partition */ - ofpart_node = mtd_node; + ofpart_node = of_node_get(mtd_node); } of_id = of_match_node(parse_ofpart_match_table, ofpart_node); From b800359a4dfacae983cd01f8c3f1cbb6f4c9f816 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 16 Mar 2026 17:35:17 -0700 Subject: [PATCH 25/44] mtd: cmdlinepart: use a flexible array member This is already allocated properly. It's just using an extra pointer. Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/parsers/cmdlinepart.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mtd/parsers/cmdlinepart.c b/drivers/mtd/parsers/cmdlinepart.c index 504e5fa2b45b..4caf1b3804f2 100644 --- a/drivers/mtd/parsers/cmdlinepart.c +++ b/drivers/mtd/parsers/cmdlinepart.c @@ -50,9 +50,9 @@ struct cmdline_mtd_partition { struct cmdline_mtd_partition *next; - char *mtd_id; int num_parts; struct mtd_partition *parts; + char mtd_id[]; }; /* mtdpart_setup() parses into here */ @@ -289,7 +289,6 @@ static int mtdpart_setup_real(char *s) /* enter results */ this_mtd->parts = parts; this_mtd->num_parts = num_parts; - this_mtd->mtd_id = (char*)(this_mtd + 1); strscpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1); /* link into chain */ From 87f9c59f896fd46d45be76d8a8286a6916ce1f1a Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:29 +0100 Subject: [PATCH 26/44] mtd: rawnand: sunxi: sunxi_nand_ooblayout_free code clarification The available length is really USER_DATA_LEN - 2 instead of just 2 (the user data length minus the BBM length) USER_DATA_LEN being 4, that doesn't change anything now, but if USER_DATA_LEN changes, it will. Signed-off-by: Richard Genoud Reviewed-by: Chen-Yu Tsai Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index e66adfcca7cd..915f1240546f 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1755,12 +1755,12 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, /* * The first 2 bytes are used for BB markers, hence we - * only have 2 bytes available in the first user data + * only have USER_DATA_SZ - 2 bytes available in the first user data * section. */ if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { oobregion->offset = 2; - oobregion->length = 2; + oobregion->length = USER_DATA_SZ - 2; return 0; } From 848c13996c55fe4ea6bf5acc3ce6c8c5c944b5f6 Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:30 +0100 Subject: [PATCH 27/44] mtd: rawnand: sunxi: fix sunxi_nfc_hw_ecc_read_extra_oob When dumping the OOB, the bytes at the end where actually copied from the beginning of the OOB instead of current_offset. That leads to something like: OOB: ff ff ff ff ff ff ff ff ea 19 00 3a 83 db aa 8d OOB: 99 09 c8 9a 90 36 35 7d aa 15 13 07 3d 97 b2 a4 OOB: a8 bb 19 b3 07 e9 f6 25 52 d7 1a 23 e2 7e 0a e4 OOB: 52 8a 09 d2 1a 86 3d cf b4 99 43 13 d3 90 33 0b OOB: ff ff ff ff ff ff ff ff ea 19 00 3a 83 db aa 8d OOB: 99 09 c8 9a 90 36 35 7d aa 15 13 07 3d 97 b2 a4 OOB: a8 bb 19 b3 07 e9 f6 25 52 d7 1a 23 e2 7e 0a e4 OOB: 52 8a 09 d2 1a 86 3d cf b4 99 43 13 d3 90 33 0b instead of: OOB: ff ff ff ff ff ff ff ff ea 19 00 3a 83 db aa 8d OOB: 99 09 c8 9a 90 36 35 7d aa 15 13 07 3d 97 b2 a4 OOB: a8 bb 19 b3 07 e9 f6 25 52 d7 1a 23 e2 7e 0a e4 OOB: 52 8a 09 d2 1a 86 3d cf b4 99 43 13 d3 90 33 0b OOB: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff OOB: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff OOB: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff OOB: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff (example with BCH16, user data [8,0], no scrambling) *cur_off (offset from the beginning of the page) was compared to offset (offset from the beginning of the OOB), and then, the nand_change_read_column_op() sets the current position to the beginning of the OOB instead of OOB+offset Fixes: 15d6f118285f ("mtd: rawnand: sunxi: Stop supporting ECC_HW_SYNDROME mode") Reviewed-by: Jernej Skrabec Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 915f1240546f..8af449e548d4 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1048,9 +1048,9 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand, if (len <= 0) return; - if (!cur_off || *cur_off != offset) - nand_change_read_column_op(nand, mtd->writesize, NULL, 0, - false); + if (!cur_off || *cur_off != (offset + mtd->writesize)) + nand_change_read_column_op(nand, mtd->writesize + offset, + NULL, 0, false); if (!randomize) sunxi_nfc_read_buf(nand, oob + offset, len); From 8fa72836be11ea70cbfa43f7f2253fa57ccc6ecd Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:31 +0100 Subject: [PATCH 28/44] mtd: rawnand: sunxi: do not count BBM bytes twice BBM is already part of USER_DATA section, so we should not remove it twice This was working ok because we are on the safe size, advertising that there was 2 bytes less available than in reality. But we can't change old platforms, since it may lead to a different ECC strength, so, introduce a legacy flag for old platforms, and switch the new platforms to the correct count. Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 8af449e548d4..d126dc18ef27 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -275,6 +275,8 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand) * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chuncks * @has_ecc_clk: If the controller needs an ECC clock. * @has_mbus_clk: If the controller needs a mbus clock. + * @legacy_max_strength:If the maximize strength function was off by 2 bytes + * NB: this should not be used in new controllers * @reg_io_data: I/O data register * @reg_ecc_err_cnt: ECC error counter register * @reg_user_data: User data register @@ -304,6 +306,7 @@ struct sunxi_nfc_caps { bool has_ecc_block_512; bool has_ecc_clk; bool has_mbus_clk; + bool legacy_max_strength; unsigned int reg_io_data; unsigned int reg_ecc_err_cnt; unsigned int reg_user_data; @@ -1805,10 +1808,22 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, ecc->size = 1024; nsectors = mtd->writesize / ecc->size; - /* Reserve 2 bytes for the BBM */ - bytes = (mtd->oobsize - 2) / nsectors; + /* + * The 2 BBM bytes should not be removed from the grand total, + * because they are part of the USER_DATA_SZ. + * But we can't modify that for older platform since it may + * result in a stronger ECC at the end, and break the + * compatibility. + */ + if (nfc->caps->legacy_max_strength) + bytes = (mtd->oobsize - 2) / nsectors; + else + bytes = mtd->oobsize / nsectors; - /* 4 non-ECC bytes are added before each ECC bytes section */ + /* + * USER_DATA_SZ non-ECC bytes are added before each ECC bytes + * section, they contain the 2 BBM bytes + */ bytes -= USER_DATA_SZ; /* and bytes has to be even. */ @@ -2373,6 +2388,7 @@ static const u8 sunxi_user_data_len_h6[] = { static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = { .has_ecc_block_512 = true, + .legacy_max_strength = true, .reg_io_data = NFC_REG_A10_IO_DATA, .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT, .reg_user_data = NFC_REG_A10_USER_DATA, @@ -2394,6 +2410,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = { static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = { .has_mdma = true, .has_ecc_block_512 = true, + .legacy_max_strength = true, .reg_io_data = NFC_REG_A23_IO_DATA, .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT, .reg_user_data = NFC_REG_A10_USER_DATA, From e3fd963da4c7469757d4f7741157fc5e23da47ed Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:32 +0100 Subject: [PATCH 29/44] mtd: rawnand: sunxi: replace hard coded value by a define - take2 The user data length (4) has been replaced almost all over the file, but 2 places were forgotten. The user data is placed before the ECC, for each step. So, in sunxi_nfc_hw_ecc_read_extra_oob(), the offset of the user data in OOB is indeed ((ecc->bytes + USER_DATA_SZ) * ecc->steps); And in sunxi_nand_ooblayout_ecc(), the offset of the ECC chunk in OOB is the same offset plus the current user data size: section * (ecc->bytes + USER_DATA_SZ) + USER_DATA_SZ; Reviewed-by: Jernej Skrabec Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index d126dc18ef27..0b0b5349f446 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1045,7 +1045,7 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand, { struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; - int offset = ((ecc->bytes + 4) * ecc->steps); + int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps); int len = mtd->oobsize - offset; if (len <= 0) @@ -1741,7 +1741,7 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section, if (section >= ecc->steps) return -ERANGE; - oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + 4; + oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + USER_DATA_SZ; oobregion->length = ecc->bytes; return 0; From 548f87ed47479e08203bc576cb5020f537e49bce Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:33 +0100 Subject: [PATCH 30/44] mtd: rawnand: sunxi: make the code more self-explanatory In sunxi_nfc_hw_ecc_{read,write}_chunk(), the ECC step was forced to 0, the reason is not trivial to get when reading the code. The explanation is that, from the NAND flash controller perspective, we are indeed at step 0 for user data length and ECC errors. Just add a const value with an explanation to clarify things. Acked-by: Jernej Skrabec Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 0b0b5349f446..ca701c75cec5 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -963,6 +963,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, u32 pattern_found; bool erased; int ret; + /* From the controller point of view, we are at step 0 */ + const int nfc_step = 0; if (*cur_off != data_off) nand_change_read_column_op(nand, data_off, NULL, 0, false); @@ -977,7 +979,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, return ret; sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); + sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP, @@ -993,10 +995,9 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found); pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found); - ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, 0, - readl(nfc->regs + NFC_REG_ECC_ST), - pattern_found, - &erased); + ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, + nfc_step, readl(nfc->regs + NFC_REG_ECC_ST), + pattern_found, &erased); if (erased) return 1; @@ -1029,7 +1030,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + USER_DATA_SZ, true, page); - sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, 0, + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step, bbm, page); } } @@ -1207,6 +1208,8 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret; + /* From the controller point of view, we are at step 0 */ + const int nfc_step = 0; if (data_off != *cur_off) nand_change_write_column_op(nand, data_off, NULL, 0, false); @@ -1223,8 +1226,8 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); - sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0, bbm, page); + sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step); + sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ACCESS_DIR | NFC_ECC_OP, From 2781542caf681ce52f213152104fb7669263651c Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:34 +0100 Subject: [PATCH 31/44] mtd: rawnand: sunxi: remove dead code sunxi_nand_ooblayout_free() is only used in a code path where engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST So the other cases can be removed. Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index ca701c75cec5..68e22ce451db 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1756,7 +1756,11 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, struct nand_chip *nand = mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc = &nand->ecc; - if (section > ecc->steps) + /* + * The controller does not provide access to OOB bytes + * past the end of the ECC data. + */ + if (section >= ecc->steps) return -ERANGE; /* @@ -1764,26 +1768,15 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, * only have USER_DATA_SZ - 2 bytes available in the first user data * section. */ - if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + if (section == 0) { oobregion->offset = 2; oobregion->length = USER_DATA_SZ - 2; return 0; } - /* - * The controller does not provide access to OOB bytes - * past the end of the ECC data. - */ - if (section == ecc->steps && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) - return -ERANGE; - oobregion->offset = section * (ecc->bytes + USER_DATA_SZ); - - if (section < ecc->steps) - oobregion->length = USER_DATA_SZ; - else - oobregion->length = mtd->oobsize - oobregion->offset; + oobregion->length = USER_DATA_SZ; return 0; } From a1c967f5d6a568dd24583917774e0178b8e39221 Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:35 +0100 Subject: [PATCH 32/44] mtd: rawnand: sunxi: change error prone variable name In sunxi_nand_hw_ecc_ctrl_init(), i is used as a loop index variable and at the same time as the value used to set ECC mode in ECC control register. To prevent it from being re-used as a loop variable, let's change the naming to ecc_mode. No functional change. Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 68e22ce451db..81e491be3563 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1796,6 +1796,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, struct mtd_info *mtd = nand_to_mtd(nand); struct nand_device *nanddev = mtd_to_nanddev(mtd); int nsectors; + int ecc_mode; int i; if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { @@ -1849,18 +1850,18 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, } /* Add ECC info retrieval from DT */ - for (i = 0; i < nfc->caps->nstrengths; i++) { - if (ecc->strength <= strengths[i]) { + for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) { + if (ecc->strength <= strengths[ecc_mode]) { /* * Update ecc->strength value with the actual strength * that will be used by the ECC engine. */ - ecc->strength = strengths[i]; + ecc->strength = strengths[ecc_mode]; break; } } - if (i >= nfc->caps->nstrengths) { + if (ecc_mode >= nfc->caps->nstrengths) { dev_err(nfc->dev, "unsupported strength\n"); return -ENOTSUPP; } @@ -1896,7 +1897,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, ecc->read_oob_raw = nand_read_oob_std; ecc->write_oob_raw = nand_write_oob_std; - sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, i) | NFC_ECC_EXCEPTION | + sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION | NFC_ECC_PIPELINE | NFC_ECC_EN; if (ecc->size == 512) { From a22f40d9eb1ef587a8201fde3f004173fd8b5e8e Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:36 +0100 Subject: [PATCH 33/44] mtd: rawnand: sunxi: fix typos in comments Fix lenghts -> lengths and chuncks -> chunks Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 81e491be3563..3d2580d39e70 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -272,7 +272,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand) * * @has_mdma: Use mbus dma mode, otherwise general dma * through MBUS on A23/A33 needs extra configuration. - * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chuncks + * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks * @has_ecc_clk: If the controller needs an ECC clock. * @has_mbus_clk: If the controller needs a mbus clock. * @legacy_max_strength:If the maximize strength function was off by 2 bytes @@ -294,7 +294,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand) * @nstrengths: Size of @ecc_strengths * @max_ecc_steps: Maximum supported steps for ECC, this is also the * number of user data registers - * @user_data_len_tab: Table of lenghts supported by USER_DATA_LEN register + * @user_data_len_tab: Table of lengths supported by USER_DATA_LEN register * The table index is the value to set in NFC_USER_DATA_LEN * registers, and the corresponding value is the number of * bytes to write From 54dcd6aa69db541529a083b31f106ef7d147fea1 Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:37 +0100 Subject: [PATCH 34/44] mtd: rawnand: sunxi: introduce maximize variable user data length In Allwinner SoCs, user data can be added in OOB before each ECC data. For older SoCs like A10, the user data size was the size of a register (4 bytes) and was mandatory before each ECC step. So, the A10 OOB Layout is: [4Bytes USER_DATA_STEP0] [ECC_STEP0 bytes] [4bytes USER_DATA_STEP1] [ECC_STEP1 bytes] ... NB: the BBM is stored at the beginning of the USER_DATA_STEP0. Now, for H6/H616 NAND flash controller, this user data can have a different size for each step. So, we are maximizing the user data length to use as many OOB bytes as possible. Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand controller") Signed-off-by: Richard Genoud Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/sunxi_nand.c | 321 ++++++++++++++++++++++++------ 1 file changed, 257 insertions(+), 64 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 3d2580d39e70..02647565c8ba 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -209,9 +209,8 @@ /* * On A10/A23, this is the size of the NDFC User Data Register, containing the - * mandatory user data bytes following the ECC for each ECC step. + * mandatory user data bytes preceding the ECC for each ECC step. * Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ. - * Those bits are currently unsused, and kept as default value 0xffffffff. * * On H6/H616, this size became configurable, from 0 bytes to 32, via the * USER_DATA_LEN registers. @@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc { * @timing_ctl: TIMING_CTL register value for this NAND chip * @nsels: number of CS lines required by the NAND chip * @sels: array of CS lines descriptions + * @user_data_bytes: array of user data lengths for all ECC steps */ struct sunxi_nand_chip { struct list_head node; @@ -257,6 +257,7 @@ struct sunxi_nand_chip { unsigned long clk_rate; u32 timing_cfg; u32 timing_ctl; + u8 *user_data_bytes; int nsels; struct sunxi_nand_chip_sel sels[] __counted_by(nsels); }; @@ -823,12 +824,50 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf) return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); } -static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob, - int step, bool bbm, int page) +static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step) { - struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + if (!sunxi_nand->user_data_bytes) + return USER_DATA_SZ; - sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)), oob); + return sunxi_nand->user_data_bytes[step]; +} + +static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob, + int step, bool bbm, int page, + unsigned int user_data_sz) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + u32 user_data; + + if (!nfc->caps->reg_user_data_len) { + /* + * For A10, the user data for step n is in the nth + * REG_USER_DATA + */ + user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)); + sunxi_nfc_user_data_to_buf(user_data, oob); + } else { + /* + * For H6 NAND controller, the user data for all steps is + * contained in 32 user data registers, but not at a specific + * offset for each step, they are just concatenated. + */ + unsigned int user_data_off = 0; + unsigned int reg_off; + u8 *ptr = oob; + unsigned int i; + + for (i = 0; i < step; i++) + user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i); + + user_data_off /= 4; + for (i = 0; i < user_data_sz / 4; i++, ptr += 4) { + reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i); + user_data = readl(nfc->regs + reg_off); + sunxi_nfc_user_data_to_buf(user_data, ptr); + } + } /* De-randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) @@ -887,17 +926,46 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand, bool bbm, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); - u8 user_data[USER_DATA_SZ]; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); + u8 *user_data = NULL; /* Randomize the Bad Block Marker. */ if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) { - memcpy(user_data, oob, sizeof(user_data)); + user_data = kmalloc(user_data_sz, GFP_KERNEL); + memcpy(user_data, oob, user_data_sz); sunxi_nfc_randomize_bbm(nand, page, user_data); oob = user_data; } - writel(sunxi_nfc_buf_to_user_data(oob), - nfc->regs + NFC_REG_USER_DATA(nfc, step)); + if (!nfc->caps->reg_user_data_len) { + /* + * For A10, the user data for step n is in the nth + * REG_USER_DATA + */ + writel(sunxi_nfc_buf_to_user_data(oob), + nfc->regs + NFC_REG_USER_DATA(nfc, step)); + } else { + /* + * For H6 NAND controller, the user data for all steps is + * contained in 32 user data registers, but not at a specific + * offset for each step, they are just concatenated. + */ + unsigned int user_data_off = 0; + const u8 *ptr = oob; + unsigned int i; + + for (i = 0; i < step; i++) + user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i); + + user_data_off /= 4; + for (i = 0; i < user_data_sz / 4; i++, ptr += 4) { + writel(sunxi_nfc_buf_to_user_data(ptr), + nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i)); + } + } + + kfree(user_data); } static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand, @@ -918,6 +986,8 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob, bool *erased) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc = &nand->ecc; u32 tmp; @@ -940,7 +1010,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob, memset(data, pattern, ecc->size); if (oob) - memset(oob, pattern, ecc->bytes + USER_DATA_SZ); + memset(oob, pattern, ecc->bytes + user_data_sz); return 0; } @@ -955,12 +1025,15 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, u8 *oob, int oob_off, int *cur_off, unsigned int *max_bitflips, - bool bbm, bool oob_required, int page) + int step, bool oob_required, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc = &nand->ecc; int raw_mode = 0; u32 pattern_found; + bool bbm = !step; bool erased; int ret; /* From the controller point of view, we are at step 0 */ @@ -978,8 +1051,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, if (ret) return ret; - sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP, @@ -990,7 +1062,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, if (ret) return ret; - *cur_off = oob_off + ecc->bytes + USER_DATA_SZ; + *cur_off = oob_off + ecc->bytes + user_data_sz; pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found); pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found); @@ -1014,10 +1086,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, ecc->size); nand_change_read_column_op(nand, oob_off, oob, - ecc->bytes + USER_DATA_SZ, false); + ecc->bytes + user_data_sz, false); ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, - ecc->bytes + USER_DATA_SZ, + ecc->bytes + user_data_sz, NULL, 0, ecc->strength); if (ret >= 0) raw_mode = 1; @@ -1027,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, if (oob_required) { nand_change_read_column_op(nand, oob_off, NULL, 0, false); - sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + USER_DATA_SZ, + sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz, true, page); sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step, - bbm, page); + bbm, page, user_data_sz); } } @@ -1040,13 +1112,42 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, return raw_mode; } +/* + * Returns the offset of the OOB for each step. + * (it includes the user data before the ECC data.) + */ +static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand, + struct nand_ecc_ctrl *ecc, int step) +{ + int ecc_off = step * ecc->bytes; + int i; + + for (i = 0; i < step; i++) + ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i); + + return ecc_off; +} + +/* + * Returns the offset of the ECC for each step. + * So, it's the same as sunxi_get_oob_offset(), + * but it skips the next user data. + */ +static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand, + struct nand_ecc_ctrl *ecc, int step) +{ + return sunxi_get_oob_offset(sunxi_nand, ecc, step) + + sunxi_nfc_user_data_sz(sunxi_nand, step); +} + static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand, u8 *oob, int *cur_off, bool randomize, int page) { + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; - int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps); + int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); int len = mtd->oobsize - offset; if (len <= 0) @@ -1071,6 +1172,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf int nchunks) { bool randomized = nand->options & NAND_NEED_SCRAMBLING; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; @@ -1090,7 +1192,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf sunxi_nfc_hw_ecc_enable(nand); sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0); + for (i = 0; i < nchunks; i++) + sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i); sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); @@ -1125,7 +1228,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf for (i = 0; i < nchunks; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = buf + data_off; u8 *oob = nand->oob_poi + oob_off; bool erased; @@ -1143,10 +1247,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf /* TODO: use DMA to retrieve OOB */ nand_change_read_column_op(nand, mtd->writesize + oob_off, - oob, ecc->bytes + USER_DATA_SZ, false); + oob, ecc->bytes + user_data_sz, false); - sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, - !i, page); + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i, + page, user_data_sz); } if (erased) @@ -1158,7 +1262,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf if (status & NFC_ECC_ERR_MSK(nfc)) { for (i = 0; i < nchunks; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = buf + data_off; u8 *oob = nand->oob_poi + oob_off; @@ -1178,10 +1283,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf /* TODO: use DMA to retrieve OOB */ nand_change_read_column_op(nand, mtd->writesize + oob_off, - oob, ecc->bytes + USER_DATA_SZ, false); + oob, ecc->bytes + user_data_sz, false); ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, - ecc->bytes + USER_DATA_SZ, + ecc->bytes + user_data_sz, NULL, 0, ecc->strength); if (ret >= 0) @@ -1202,11 +1307,14 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, const u8 *data, int data_off, const u8 *oob, int oob_off, - int *cur_off, bool bbm, + int *cur_off, int step, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step); struct nand_ecc_ctrl *ecc = &nand->ecc; + bool bbm = !step; int ret; /* From the controller point of view, we are at step 0 */ const int nfc_step = 0; @@ -1225,8 +1333,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, sunxi_nfc_randomizer_config(nand, page, false); sunxi_nfc_randomizer_enable(nand); - sunxi_nfc_reset_user_data_len(nfc); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step); sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page); writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | @@ -1238,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand, if (ret) return ret; - *cur_off = oob_off + ecc->bytes + USER_DATA_SZ; + *cur_off = oob_off + ecc->bytes + user_data_sz; return 0; } @@ -1248,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand, int page) { struct mtd_info *mtd = nand_to_mtd(nand); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; - int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps); + int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); int len = mtd->oobsize - offset; if (len <= 0) @@ -1268,6 +1376,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand, static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf, int oob_required, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; unsigned int max_bitflips = 0; @@ -1280,16 +1390,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = 0; i < ecc->steps; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = buf + data_off; u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, &cur_off, &max_bitflips, - !i, oob_required, page); + i, oob_required, page); if (ret < 0) return ret; else if (ret) @@ -1327,6 +1438,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand, u32 data_offs, u32 readlen, u8 *bufpoi, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret, i, cur_off = 0; @@ -1338,17 +1451,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = data_offs / ecc->size; i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); u8 *data = bufpoi + data_off; u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, &max_bitflips, !i, + &cur_off, &max_bitflips, i, false, page); if (ret < 0) return ret; @@ -1383,6 +1497,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand, const uint8_t *buf, int oob_required, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret, i, cur_off = 0; @@ -1393,15 +1509,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = 0; i < ecc->steps; i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); const u8 *data = buf + data_off; const u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, !i, page); + &cur_off, i, page); if (ret) return ret; } @@ -1420,6 +1537,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand, const u8 *buf, int oob_required, int page) { + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct mtd_info *mtd = nand_to_mtd(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; int ret, i, cur_off = 0; @@ -1430,16 +1549,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand, sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_reset_user_data_len(nfc); for (i = data_offs / ecc->size; i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) { int data_off = i * ecc->size; - int oob_off = i * (ecc->bytes + USER_DATA_SZ); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); const u8 *data = buf + data_off; const u8 *oob = nand->oob_poi + oob_off; ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob, oob_off + mtd->writesize, - &cur_off, !i, page); + &cur_off, i, page); if (ret) return ret; } @@ -1455,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand, int page) { struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); struct nand_ecc_ctrl *ecc = &nand->ecc; struct scatterlist sg; u32 wait; @@ -1473,10 +1594,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand, sunxi_nfc_reset_user_data_len(nfc); for (i = 0; i < ecc->steps; i++) { - const u8 *oob = nand->oob_poi + (i * (ecc->bytes + USER_DATA_SZ)); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i); + int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i); + const u8 *oob = nand->oob_poi + oob_off; sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page); - sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, i); + sunxi_nfc_set_user_data_len(nfc, user_data_sz, i); } nand_prog_page_begin_op(nand, page, 0, NULL, 0); @@ -1740,11 +1863,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section, { struct nand_chip *nand = mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc = &nand->ecc; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); if (section >= ecc->steps) return -ERANGE; - oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + USER_DATA_SZ; + oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section); oobregion->length = ecc->bytes; return 0; @@ -1755,6 +1879,8 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, { struct nand_chip *nand = mtd_to_nand(mtd); struct nand_ecc_ctrl *ecc = &nand->ecc; + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section); /* * The controller does not provide access to OOB bytes @@ -1765,18 +1891,18 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, /* * The first 2 bytes are used for BB markers, hence we - * only have USER_DATA_SZ - 2 bytes available in the first user data + * only have user_data_sz - 2 bytes available in the first user data * section. */ if (section == 0) { oobregion->offset = 2; - oobregion->length = USER_DATA_SZ - 2; + oobregion->length = user_data_sz - 2; return 0; } - oobregion->offset = section * (ecc->bytes + USER_DATA_SZ); - oobregion->length = USER_DATA_SZ; + oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section); + oobregion->length = user_data_sz; return 0; } @@ -1786,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = { .free = sunxi_nand_ooblayout_free, }; +static void sunxi_nand_detach_chip(struct nand_chip *nand) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + devm_kfree(nfc->dev, sunxi_nand->user_data_bytes); + sunxi_nand->user_data_bytes = NULL; +} + +static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize, + int ecc_bytes, int nsectors) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + const struct sunxi_nfc_caps *c = nfc->caps; + int remaining_bytes = oobsize - (ecc_bytes * nsectors); + int i, step; + + sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors, + GFP_KERNEL); + if (!sunxi_nand->user_data_bytes) + return -ENOMEM; + + for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) { + for (i = 0; i < c->nuser_data_tab; i++) { + if (c->user_data_len_tab[i] > remaining_bytes) + break; + sunxi_nand->user_data_bytes[step] = c->user_data_len_tab[i]; + } + remaining_bytes -= sunxi_nand->user_data_bytes[step]; + if (sunxi_nand->user_data_bytes[step] == 0) + break; + } + + return 0; +} + static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, struct nand_ecc_ctrl *ecc, struct device_node *np) @@ -1795,33 +1958,50 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, const u8 *strengths = nfc->caps->ecc_strengths; struct mtd_info *mtd = nand_to_mtd(nand); struct nand_device *nanddev = mtd_to_nanddev(mtd); + int total_user_data_sz = 0; int nsectors; int ecc_mode; int i; if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { - int bytes; + int bytes = mtd->oobsize; ecc->size = 1024; nsectors = mtd->writesize / ecc->size; - /* - * The 2 BBM bytes should not be removed from the grand total, - * because they are part of the USER_DATA_SZ. - * But we can't modify that for older platform since it may - * result in a stronger ECC at the end, and break the - * compatibility. - */ - if (nfc->caps->legacy_max_strength) - bytes = (mtd->oobsize - 2) / nsectors; - else - bytes = mtd->oobsize / nsectors; + if (!nfc->caps->reg_user_data_len) { + /* + * If there's a fixed user data length, subtract it before + * computing the max ECC strength + */ + + for (i = 0; i < nsectors; i++) + total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i); + + /* + * The 2 BBM bytes should not be removed from the grand total, + * because they are part of the USER_DATA_SZ. + * But we can't modify that for older platform since it may + * result in a stronger ECC at the end, and break the + * compatibility. + */ + if (nfc->caps->legacy_max_strength) + bytes -= 2; + + bytes -= total_user_data_sz; + } else { + /* + * remove at least the BBM size before computing the + * max ECC + */ + bytes -= 2; + } /* - * USER_DATA_SZ non-ECC bytes are added before each ECC bytes - * section, they contain the 2 BBM bytes + * Once all user data has been subtracted, the rest can be used + * for ECC bytes */ - bytes -= USER_DATA_SZ; + bytes /= nsectors; /* and bytes has to be even. */ if (bytes % 2) @@ -1874,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand, nsectors = mtd->writesize / ecc->size; - if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors)) + /* + * The rationale for variable data length is to prioritize maximum ECC + * strength, and then use the remaining space for user data. + */ + if (nfc->caps->reg_user_data_len) + sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes, + nsectors); + + if (total_user_data_sz == 0) + for (i = 0; i < nsectors; i++) + total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i); + + if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz)) return -EINVAL; ecc->read_oob = sunxi_nfc_hw_ecc_read_oob; @@ -2104,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand, static const struct nand_controller_ops sunxi_nand_controller_ops = { .attach_chip = sunxi_nand_attach_chip, + .detach_chip = sunxi_nand_detach_chip, .setup_interface = sunxi_nfc_setup_interface, .exec_op = sunxi_nfc_exec_op, }; From 25a915fad503c2678902075565d47ddc2aa45db9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 18 Mar 2026 11:47:50 +0100 Subject: [PATCH 35/44] mtd: spinand: winbond: Clarify when to enable the HS bit Above 104MHz when in fast dual or quad I/O reads, the delay between address and data cycles is too short. It is possible to reach higher frequencies, up to 166MHz, by adding a few more dummy cycles through the setting of the HS bit. Improve the condition for enabling this bit, and also make sure we set it at soon as we go over 104MHz. Fixes: f1a91175faaa ("mtd: spinand: winbond: Enable high-speed modes on w25n0xjw") Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 6dfd0dcc8ee7..e0138785e280 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -337,16 +337,19 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand, 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 && - op->dummy.buswidth == 1 && op->data.buswidth == 1) + 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 if (!op->max_freq) - hs = true; else - hs = false; + hs = true; ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4); if (ret) From 0ba8da2f318efc006ae5c080a4abfbabb5d110e2 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 24 Mar 2026 18:16:18 -0400 Subject: [PATCH 36/44] dt-bindings: mtd: refactor NAND bindings and add nand-controller-legacy.yaml The modern NAND controller binding requires NAND chips to be described as child nodes of the controller, for example: nand-controller { ... nand@0 { /* raw NAND chip properties */ }; }; However, many existing device trees place NAND chip properties directly within the controller node because those controllers support only a single chip. This layout is still widely used by older platforms and by other DT consumers such as U-Boot. Migrating all existing users to the new layout will take time. Several kernel drivers, such as ams-delta.c, davinci_nand.c and fsmc_nand.c, still expect the legacy layout where raw NAND properties are defined in the controller node. To support both layouts during the transition: - Extract NAND chip-related properties into separate schemas (nand-property.yaml and raw-nand-property.yaml) from nand-chip.yaml and raw-nand-chip.yaml. - Introduce nand-controller-legacy.yaml to allow both the legacy and modern layouts. - Add a select condition in nand-controller.yaml to prevent node name pattern matching for fsl,* NAND controllers. Keep compatibility with existing device trees while allowing gradual migration to the modern binding structure. Signed-off-by: Frank Li Reviewed-by: Rob Herring (Arm) Signed-off-by: Miquel Raynal --- .../devicetree/bindings/mtd/nand-chip.yaml | 46 +-------- .../bindings/mtd/nand-controller-legacy.yaml | 65 ++++++++++++ .../bindings/mtd/nand-controller.yaml | 2 + .../bindings/mtd/nand-property.yaml | 64 ++++++++++++ .../bindings/mtd/raw-nand-chip.yaml | 74 +------------- .../bindings/mtd/raw-nand-property.yaml | 98 +++++++++++++++++++ 6 files changed, 231 insertions(+), 118 deletions(-) create mode 100644 Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml create mode 100644 Documentation/devicetree/bindings/mtd/nand-property.yaml create mode 100644 Documentation/devicetree/bindings/mtd/raw-nand-property.yaml diff --git a/Documentation/devicetree/bindings/mtd/nand-chip.yaml b/Documentation/devicetree/bindings/mtd/nand-chip.yaml index 609d4a4ddd80..8800d1d07266 100644 --- a/Documentation/devicetree/bindings/mtd/nand-chip.yaml +++ b/Documentation/devicetree/bindings/mtd/nand-chip.yaml @@ -11,6 +11,7 @@ maintainers: allOf: - $ref: mtd.yaml# + - $ref: nand-property.yaml description: | This file covers the generic description of a NAND chip. It implies that the @@ -22,51 +23,6 @@ properties: description: Contains the chip-select IDs. - nand-ecc-engine: - description: | - A phandle on the hardware ECC engine if any. There are - basically three possibilities: - 1/ The ECC engine is part of the NAND controller, in this - case the phandle should reference the parent node. - 2/ The ECC engine is part of the NAND part (on-die), in this - case the phandle should reference the node itself. - 3/ The ECC engine is external, in this case the phandle should - reference the specific ECC engine node. - $ref: /schemas/types.yaml#/definitions/phandle - - nand-use-soft-ecc-engine: - description: Use a software ECC engine. - type: boolean - - nand-no-ecc-engine: - description: Do not use any ECC correction. - type: boolean - - nand-ecc-algo: - description: - Desired ECC algorithm. - $ref: /schemas/types.yaml#/definitions/string - enum: [hamming, bch, rs] - - nand-ecc-strength: - description: - Maximum number of bits that can be corrected per ECC step. - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 1 - - nand-ecc-step-size: - description: - Number of data bytes covered by a single ECC step. - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 1 - - secure-regions: - description: - Regions in the NAND chip which are protected using a secure element - like Trustzone. This property contains the start address and size of - the secure regions present. - $ref: /schemas/types.yaml#/definitions/uint64-matrix - required: - reg diff --git a/Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml b/Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml new file mode 100644 index 000000000000..d6e612413df1 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/nand-controller-legacy.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/nand-controller-legacy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NAND Controller Common Properties + +maintainers: + - Miquel Raynal + - Richard Weinberger + +description: > + The NAND controller should be represented with its own DT node, and + all NAND chips attached to this controller should be defined as + children nodes of the NAND controller. This representation should be + enforced even for simple controllers supporting only one chip. + + This is only for legacy nand controller, new controller should use + nand-controller.yaml + +properties: + + "#address-cells": + const: 1 + + "#size-cells": + enum: [0, 1] + + ranges: true + + cs-gpios: + description: + Array of chip-select available to the controller. The first + entries are a 1:1 mapping of the available chip-select on the + NAND controller (even if they are not used). As many additional + chip-select as needed may follow and should be phandles of GPIO + lines. 'reg' entries of the NAND chip subnodes become indexes of + this array when this property is present. + minItems: 1 + maxItems: 8 + + partitions: + type: object + + required: + - compatible + +patternProperties: + "^nand@[a-f0-9]$": + type: object + $ref: raw-nand-chip.yaml# + + "^partition@[0-9a-f]+$": + type: object + $ref: /schemas/mtd/partitions/partition.yaml#/$defs/partition-node + deprecated: true + +allOf: + - $ref: raw-nand-property.yaml# + - $ref: nand-property.yaml# + +# This is a generic file other binding inherit from and extend +additionalProperties: true + diff --git a/Documentation/devicetree/bindings/mtd/nand-controller.yaml b/Documentation/devicetree/bindings/mtd/nand-controller.yaml index 28167c0cf271..0aa61d5fa50b 100644 --- a/Documentation/devicetree/bindings/mtd/nand-controller.yaml +++ b/Documentation/devicetree/bindings/mtd/nand-controller.yaml @@ -16,6 +16,8 @@ description: | children nodes of the NAND controller. This representation should be enforced even for simple controllers supporting only one chip. +select: false + properties: $nodename: pattern: "^nand-controller(@.*)?" diff --git a/Documentation/devicetree/bindings/mtd/nand-property.yaml b/Documentation/devicetree/bindings/mtd/nand-property.yaml new file mode 100644 index 000000000000..55488a4b1548 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/nand-property.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/nand-property.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NAND Chip Common Properties + +maintainers: + - Miquel Raynal + +description: | + This file covers the generic properties of a NAND chip. It implies that the + bus interface should not be taken into account: both raw NAND devices and + SPI-NAND devices are concerned by this description. + +properties: + nand-ecc-engine: + description: | + A phandle on the hardware ECC engine if any. There are + basically three possibilities: + 1/ The ECC engine is part of the NAND controller, in this + case the phandle should reference the parent node. + 2/ The ECC engine is part of the NAND part (on-die), in this + case the phandle should reference the node itself. + 3/ The ECC engine is external, in this case the phandle should + reference the specific ECC engine node. + $ref: /schemas/types.yaml#/definitions/phandle + + nand-use-soft-ecc-engine: + description: Use a software ECC engine. + type: boolean + + nand-no-ecc-engine: + description: Do not use any ECC correction. + type: boolean + + nand-ecc-algo: + description: + Desired ECC algorithm. + $ref: /schemas/types.yaml#/definitions/string + enum: [hamming, bch, rs] + + nand-ecc-strength: + description: + Maximum number of bits that can be corrected per ECC step. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + + nand-ecc-step-size: + description: + Number of data bytes covered by a single ECC step. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + + secure-regions: + description: + Regions in the NAND chip which are protected using a secure element + like Trustzone. This property contains the start address and size of + the secure regions present. + $ref: /schemas/types.yaml#/definitions/uint64-matrix + +# This file can be referenced by more specific devices (like spi-nands) +additionalProperties: true diff --git a/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml b/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml index 092448d7bfc5..792de3e3c6ee 100644 --- a/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml +++ b/Documentation/devicetree/bindings/mtd/raw-nand-chip.yaml @@ -11,6 +11,7 @@ maintainers: allOf: - $ref: nand-chip.yaml# + - $ref: raw-nand-property.yaml# description: | The ECC strength and ECC step size properties define the user @@ -31,79 +32,6 @@ properties: description: Contains the chip-select IDs. - nand-ecc-placement: - description: - Location of the ECC bytes. This location is unknown by default - but can be explicitly set to "oob", if all ECC bytes are - known to be stored in the OOB area, or "interleaved" if ECC - bytes will be interleaved with regular data in the main area. - $ref: /schemas/types.yaml#/definitions/string - enum: [ oob, interleaved ] - deprecated: true - - nand-ecc-mode: - description: - Legacy ECC configuration mixing the ECC engine choice and - configuration. - $ref: /schemas/types.yaml#/definitions/string - enum: [none, soft, soft_bch, hw, hw_syndrome, on-die] - deprecated: true - - nand-bus-width: - description: - Bus width to the NAND chip - $ref: /schemas/types.yaml#/definitions/uint32 - enum: [8, 16] - default: 8 - - nand-on-flash-bbt: - description: - With this property, the OS will search the device for a Bad - Block Table (BBT). If not found, it will create one, reserve - a few blocks at the end of the device to store it and update - it as the device ages. Otherwise, the out-of-band area of a - few pages of all the blocks will be scanned at boot time to - find Bad Block Markers (BBM). These markers will help to - build a volatile BBT in RAM. - $ref: /schemas/types.yaml#/definitions/flag - - nand-ecc-maximize: - description: - Whether or not the ECC strength should be maximized. The - maximum ECC strength is both controller and chip - dependent. The ECC engine has to select the ECC config - providing the best strength and taking the OOB area size - constraint into account. This is particularly useful when - only the in-band area is used by the upper layers, and you - want to make your NAND as reliable as possible. - $ref: /schemas/types.yaml#/definitions/flag - - nand-is-boot-medium: - description: - Whether or not the NAND chip is a boot medium. Drivers might - use this information to select ECC algorithms supported by - the boot ROM or similar restrictions. - $ref: /schemas/types.yaml#/definitions/flag - - nand-rb: - description: - Contains the native Ready/Busy IDs. - $ref: /schemas/types.yaml#/definitions/uint32-array - - rb-gpios: - description: - Contains one or more GPIO descriptor (the numper of descriptor - depends on the number of R/B pins exposed by the flash) for the - Ready/Busy pins. Active state refers to the NAND ready state and - should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted. - - wp-gpios: - description: - Contains one GPIO descriptor for the Write Protect pin. - Active state refers to the NAND Write Protect state and should be - set to GPIOD_ACTIVE_LOW unless the signal is inverted. - maxItems: 1 - required: - reg diff --git a/Documentation/devicetree/bindings/mtd/raw-nand-property.yaml b/Documentation/devicetree/bindings/mtd/raw-nand-property.yaml new file mode 100644 index 000000000000..f853b72426c4 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/raw-nand-property.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/raw-nand-property.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Raw NAND Chip Common Properties + +maintainers: + - Miquel Raynal + +description: | + The ECC strength and ECC step size properties define the user + desires in terms of correction capability of a controller. Together, + they request the ECC engine to correct {strength} bit errors per + {size} bytes for a particular raw NAND chip. + + The interpretation of these parameters is implementation-defined, so + not all implementations must support all possible + combinations. However, implementations are encouraged to further + specify the value(s) they support. + +properties: + nand-ecc-placement: + description: + Location of the ECC bytes. This location is unknown by default + but can be explicitly set to "oob", if all ECC bytes are + known to be stored in the OOB area, or "interleaved" if ECC + bytes will be interleaved with regular data in the main area. + $ref: /schemas/types.yaml#/definitions/string + enum: [ oob, interleaved ] + deprecated: true + + nand-ecc-mode: + description: + Legacy ECC configuration mixing the ECC engine choice and + configuration. + $ref: /schemas/types.yaml#/definitions/string + enum: [none, soft, soft_bch, hw, hw_syndrome, on-die] + deprecated: true + + nand-bus-width: + description: + Bus width to the NAND chip + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [8, 16] + default: 8 + + nand-on-flash-bbt: + description: + With this property, the OS will search the device for a Bad + Block Table (BBT). If not found, it will create one, reserve + a few blocks at the end of the device to store it and update + it as the device ages. Otherwise, the out-of-band area of a + few pages of all the blocks will be scanned at boot time to + find Bad Block Markers (BBM). These markers will help to + build a volatile BBT in RAM. + $ref: /schemas/types.yaml#/definitions/flag + + nand-ecc-maximize: + description: + Whether or not the ECC strength should be maximized. The + maximum ECC strength is both controller and chip + dependent. The ECC engine has to select the ECC config + providing the best strength and taking the OOB area size + constraint into account. This is particularly useful when + only the in-band area is used by the upper layers, and you + want to make your NAND as reliable as possible. + $ref: /schemas/types.yaml#/definitions/flag + + nand-is-boot-medium: + description: + Whether or not the NAND chip is a boot medium. Drivers might + use this information to select ECC algorithms supported by + the boot ROM or similar restrictions. + $ref: /schemas/types.yaml#/definitions/flag + + nand-rb: + description: + Contains the native Ready/Busy IDs. + $ref: /schemas/types.yaml#/definitions/uint32-array + + rb-gpios: + description: + Contains one or more GPIO descriptor (the numper of descriptor + depends on the number of R/B pins exposed by the flash) for the + Ready/Busy pins. Active state refers to the NAND ready state and + should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted. + + wp-gpios: + description: + Contains one GPIO descriptor for the Write Protect pin. + Active state refers to the NAND Write Protect state and should be + set to GPIOD_ACTIVE_LOW unless the signal is inverted. + maxItems: 1 + +# This is a generic file other binding inherit from and extend +additionalProperties: true From 17de8a68ac9807adf551f7d2d980e26d1196e41a Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 24 Mar 2026 18:16:19 -0400 Subject: [PATCH 37/44] dt-bindings: mtd: gpmi-nand: ref to nand-controller-legacy.yaml Ref to nand-controller-legacy.yaml instead nand-controller.yaml to allow legacy DT layout. Reviewed-by: Rob Herring (Arm) Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- Documentation/devicetree/bindings/mtd/gpmi-nand.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml b/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml index 0badb2e978c7..adb684e3207c 100644 --- a/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml +++ b/Documentation/devicetree/bindings/mtd/gpmi-nand.yaml @@ -101,7 +101,7 @@ required: unevaluatedProperties: false allOf: - - $ref: nand-controller.yaml + - $ref: nand-controller-legacy.yaml - if: properties: From 3b2a422e23cf1998b85ccbcb90cabff01d17422c Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 24 Mar 2026 18:16:20 -0400 Subject: [PATCH 38/44] dt-bindings: mtd: mxc-nand: add missing compatible string and ref to nand-controller-legacy.yaml Add compatible string fsl,imx51-nand, fsl,imx53-nand and fsl,imx35-nand. Add missinge properties dmas and dma-names. Change reg's maxItems to 2 because i.MX53 have addition NAND flash internal buffer space. Change ref to nand-controller-legacy.yaml allow legacy DT layout. Reviewed-by: Rob Herring (Arm) Signed-off-by: Frank Li Signed-off-by: Miquel Raynal --- .../devicetree/bindings/mtd/mxc-nand.yaml | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mtd/mxc-nand.yaml b/Documentation/devicetree/bindings/mtd/mxc-nand.yaml index 433ae5727ad8..fbaff7d3eda8 100644 --- a/Documentation/devicetree/bindings/mtd/mxc-nand.yaml +++ b/Documentation/devicetree/bindings/mtd/mxc-nand.yaml @@ -10,7 +10,7 @@ maintainers: - Uwe Kleine-König allOf: - - $ref: nand-controller.yaml + - $ref: nand-controller-legacy.yaml properties: compatible: @@ -18,12 +18,21 @@ properties: - enum: - fsl,imx25-nand - fsl,imx27-nand + - fsl,imx51-nand + - fsl,imx53-nand + - items: + - enum: + - fsl,imx35-nand + - const: fsl,imx25-nand - items: - enum: - fsl,imx31-nand - const: fsl,imx27-nand reg: - maxItems: 1 + minItems: 1 + items: + - description: IP register space + - description: Nand flash internal buffer space interrupts: maxItems: 1 @@ -31,6 +40,13 @@ properties: clocks: maxItems: 1 + dmas: + maxItems: 1 + + dma-names: + items: + - const: rx-tx + required: - compatible - reg From 0f0b444be36c098d34a155bbe9e5a2f714a462fb Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Wed, 18 Feb 2026 14:35:21 +0000 Subject: [PATCH 39/44] mtd: spi-nor: winbond: Fix locking support for w25q256jwm The Winbond w25q256jwm device supports four Block Protect (BP) bits and uses Status Register bit 6 as the Top/Bottom (TB) protect bit. Update the flash parameters by enabling SPI_NOR_4BIT_BP and SPI_NOR_TB_SR_BIT6. Without these flags, the locking configuration is incorrect. Reference: https://www.winbond.com/hq/support/documentation/levelOne.jsp?__locale=en&DocNo=DA00-W25Q256JW.1 Signed-off-by: Eliav Farber Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/winbond.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index fb855fe44733..55f1209936d5 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -337,7 +337,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x19), .name = "w25q256jwm", .size = SZ_32M, - .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { .id = SNOR_ID(0xef, 0x80, 0x20), From 5eb130177693c3470cafaf721af41c60dd891cc7 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Wed, 18 Feb 2026 14:35:23 +0000 Subject: [PATCH 40/44] mtd: spi-nor: winbond: Fix locking support for w25q64jvm The Winbond w25q64jvm supports block protection through the Status Register (SR) and provides a Top/Bottom (TB) protection bit. Enable SPI_NOR_HAS_LOCK and SPI_NOR_HAS_TB for this device to properly describe its locking capabilities. The device uses Status Register bit 5 as the TB bit and supports only three Block Protect (BP) bits. Therefore, do not set SPI_NOR_TB_SR_BIT6 or SPI_NOR_4BIT_BP. Reference: https://www.winbond.com/hq/support/documentation/levelOne.jsp?__locale=en&DocNo=DA00-W25Q64JV.1 Signed-off-by: Eliav Farber Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/winbond.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 55f1209936d5..5a3e3ee96edb 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -295,6 +295,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x17), .name = "w25q64jvm", .size = SZ_8M, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K, }, { .id = SNOR_ID(0xef, 0x70, 0x18), From a0f64241d3566a49c0a9b33ba7ae458ae22003a9 Mon Sep 17 00:00:00 2001 From: Sanjaikumar V S Date: Wed, 11 Mar 2026 10:30:56 +0000 Subject: [PATCH 41/44] mtd: spi-nor: sst: Fix write enable before AAI sequence When writing to SST flash starting at an odd address, a single byte is first programmed using the byte program (BP) command. After this operation completes, the flash hardware automatically clears the Write Enable Latch (WEL) bit. If an AAI (Auto Address Increment) word program sequence follows, it requires WEL to be set. Without re-enabling writes, the AAI sequence fails. Add spi_nor_write_enable() after the odd-address byte program when more data needs to be written. Use a local boolean for clarity. Fixes: b199489d37b2 ("mtd: spi-nor: add the framework for SPI NOR") Cc: stable@vger.kernel.org Signed-off-by: Sanjaikumar V S Tested-by: Hendrik Donner Reviewed-by: Hendrik Donner Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/sst.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index 175211fe6a5e..db02c14ba16f 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -203,6 +203,8 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, /* Start write from odd address. */ if (to % 2) { + bool needs_write_enable = (len > 1); + /* write one byte. */ ret = sst_nor_write_data(nor, to, 1, buf); if (ret < 0) @@ -210,6 +212,17 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, to++; actual++; + + /* + * Byte program clears the write enable latch. If more + * data needs to be written using the AAI sequence, + * re-enable writes. + */ + if (needs_write_enable) { + ret = spi_nor_write_enable(nor); + if (ret) + goto out; + } } /* Write out most of the data here. */ From 760e8c382c2de149b84e69fb60cccc32f6725a3f Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Wed, 18 Feb 2026 14:35:22 +0000 Subject: [PATCH 42/44] mtd: spi-nor: winbond: Fix locking support for w25q256jw The Winbond w25q256jw device: - Supports lock/unlock via SR. - Has Top/Bottom (TB) protect bit. - Uses Status Register bit 6 as the Top/Bottom (TB) protect bit. - Supports four Block Protect (BP) bits. Update the flash parameters by enabling SPI_NOR_HAS_LOCK, SPI_NOR_HAS_TB, SPI_NOR_TB_SR_BIT6 and SPI_NOR_4BIT_BP. Without these flags, the locking configuration is incorrect. Reference: https://www.winbond.com/hq/support/documentation/levelOne.jsp?__locale=en&DocNo=DA00-W25Q256JW.1 Signed-off-by: Eliav Farber Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/winbond.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 5a3e3ee96edb..eaa547d36aad 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -274,6 +274,7 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x60, 0x19), .name = "w25q256jw", .size = SZ_32M, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { .id = SNOR_ID(0xef, 0x60, 0x20), From cf6788aed0cd911c2e7dded6f28214996dfabc30 Mon Sep 17 00:00:00 2001 From: Haoyu Lu Date: Tue, 31 Mar 2026 17:53:51 +0800 Subject: [PATCH 43/44] mtd: spi-nor: micron-st: Enable die erase support for MT35XU02GCBA The MT35XU02GCBA flash device does not support chip erase according to its datasheet, but supports die erase. The existing code had a TODO comment noting that the SPI_NOR_IO_MODE_EN_VOLATILE flag probably needs to be enabled and the driver implementation needs to be converted to use die erase. This patch enables the SPI_NOR_IO_MODE_EN_VOLATILE flag and adds the mt35_two_die_fixups to the MT35XU02GCBA entry, which includes the micron_st_nor_two_die_late_init() function that sets up die erase support. With these changes, the flash device can properly use die erase operations instead of chip erase. Signed-off-by: Haoyu Lu Reviewed-by: Pratyush Yadav (Google) [pratyush@kernel.org: drop the whole comment instead of just the TODO line] Signed-off-by: Pratyush Yadav (Google) --- drivers/mtd/spi-nor/micron-st.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index b2b473501d02..c75b0a1cd567 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -195,7 +195,7 @@ static const struct spi_nor_fixups mt35xu512aba_fixups = { .post_sfdp = mt35xu512aba_post_sfdp_fixup, }; -static const struct spi_nor_fixups mt35xu01gbba_fixups = { +static const struct spi_nor_fixups mt35_two_die_fixups = { .post_sfdp = mt35xu512aba_post_sfdp_fixup, .late_init = micron_st_nor_two_die_late_init, }; @@ -212,25 +212,16 @@ static const struct flash_info micron_nor_parts[] = { .id = SNOR_ID(0x2c, 0x5b, 0x1b), .mfr_flags = USE_FSR, .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE, - .fixups = &mt35xu01gbba_fixups, + .fixups = &mt35_two_die_fixups, }, { - /* - * The MT35XU02GCBA flash device does not support chip erase, - * according to its datasheet. It supports die erase, which - * means the current driver implementation will likely need to - * be converted to use die erase. Furthermore, similar to the - * MT35XU01GBBA, the SPI_NOR_IO_MODE_EN_VOLATILE flag probably - * needs to be enabled. - * - * TODO: Fix these and test on real hardware. - */ .id = SNOR_ID(0x2c, 0x5b, 0x1c), .name = "mt35xu02g", .sector_size = SZ_128K, .size = SZ_256M, .no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ, .mfr_flags = USE_FSR, - .fixup_flags = SPI_NOR_4B_OPCODES, + .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE, + .fixups = &mt35_two_die_fixups, }, }; From 7866ce992cf0d3c3b50fe8bf4acb1dbb173a2304 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 25 Mar 2026 18:04:50 +0100 Subject: [PATCH 44/44] mtd: spinand: winbond: Declare the QE bit on W25NxxJW Factory default for this bit is "set" (at least on the chips I have), but we must make sure it is actually set by Linux explicitly, as the bit is writable by an earlier stage. Fixes: 6a804fb72de5 ("mtd: spinand: winbond: add support for serial NAND flash") Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/winbond.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index e0138785e280..ad22774096e6 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -488,7 +488,7 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants, &write_cache_variants, &update_cache_variants), - 0, + SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&w25n01jw_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)), SPINAND_INFO("W25N01KV", /* 3.3V */ @@ -552,7 +552,7 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants, &write_cache_variants, &update_cache_variants), - 0, + SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)), SPINAND_INFO("W25N02KV", /* 3.3V */