From 43479bb3703f17da6cdfaa2a7f4b93db9c6908bc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 5 Feb 2026 19:49:15 +0100 Subject: [PATCH 01/22] 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 02/22] 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 03/22] 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 04/22] 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 520886a1a6ca16862a0437b4a8fae133a895a9b8 Mon Sep 17 00:00:00 2001 From: Richard Lyu Date: Wed, 11 Mar 2026 00:30:43 +0800 Subject: [PATCH 05/22] 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 06/22] 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 07/22] 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 08/22] 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 87f9c59f896fd46d45be76d8a8286a6916ce1f1a Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 17 Mar 2026 15:24:29 +0100 Subject: [PATCH 09/22] 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 10/22] 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 11/22] 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 12/22] 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 13/22] 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 14/22] 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 15/22] 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 16/22] 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 17/22] 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 18/22] 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 19/22] 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 20/22] 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 21/22] 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 7866ce992cf0d3c3b50fe8bf4acb1dbb173a2304 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 25 Mar 2026 18:04:50 +0100 Subject: [PATCH 22/22] 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 */