[openwrt/openwrt] realtek: backport ECC driver

LEDE Commits lede-commits at lists.infradead.org
Tue Sep 30 02:15:30 PDT 2025


robimarko pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/b49f9d980429b890e59a8f310f72c36e1b976a84

commit b49f9d980429b890e59a8f310f72c36e1b976a84
Author: Markus Stockhausen <markus.stockhausen at gmx.de>
AuthorDate: Mon Sep 29 14:33:08 2025 -0400

    realtek: backport ECC driver
    
    Upstream will get support for the Realtek ECC engine with 6.18.
    To make use of this in Openwrt
    
    - backport upstream patches
    - change config so that ECC will be built for nand subtargets
    - define ECC engine in RTL93xx DTS.
    
    Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
    Link: https://github.com/openwrt/openwrt/pull/19746
    Signed-off-by: Robert Marko <robimarko at gmail.com>
---
 .../402-v6.18-mtd-nand-move-chunk-to-core.patch    | 329 +++++++++++++
 target/linux/realtek/dts/rtl930x.dtsi              |   7 +
 target/linux/realtek/dts/rtl931x.dtsi              |   7 +
 ...021-v6.18-mtd-nand-add-realtek-ecc-engine.patch | 521 +++++++++++++++++++++
 target/linux/realtek/rtl838x/config-6.12           |   2 +
 target/linux/realtek/rtl839x/config-6.12           |   2 +
 target/linux/realtek/rtl930x/config-6.12           |   1 +
 target/linux/realtek/rtl930x_nand/config-6.12      |   1 +
 target/linux/realtek/rtl931x/config-6.12           |   1 +
 target/linux/realtek/rtl931x_nand/config-6.12      |   1 +
 10 files changed, 872 insertions(+)

diff --git a/target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch b/target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch
new file mode 100644
index 0000000000..7933874d01
--- /dev/null
+++ b/target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch
@@ -0,0 +1,329 @@
+From 6b88293aae7fb78872e5cc1ec36e2f750ae12e38 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen at gmx.de>
+Date: Wed, 10 Sep 2025 14:32:58 -0400
+Subject: mtd: nand: move nand_check_erased_ecc_chunk() to nand/core
+
+The check function for bitflips in erased blocks will be needed
+by the Realtek ECC engine driver (which is currently under
+development). Right now it is located in raw/nand_base.c.
+While this is sufficient for the current usecases, there is
+no real dependency for an ECC engine on the raw nand library.
+
+Move the function over to a more generic place in core library.
+
+Suggested-by: Miquel Raynal <miquel.raynal at bootlin.com>
+Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
+---
+ drivers/mtd/nand/core.c          | 131 +++++++++++++++++++++++++++++++++++++++
+ drivers/mtd/nand/raw/nand_base.c | 131 ---------------------------------------
+ include/linux/mtd/nand.h         |   5 ++
+ include/linux/mtd/rawnand.h      |   5 --
+ 4 files changed, 136 insertions(+), 136 deletions(-)
+
+--- a/drivers/mtd/nand/core.c
++++ b/drivers/mtd/nand/core.c
+@@ -13,6 +13,137 @@
+ #include <linux/mtd/nand.h>
+ 
+ /**
++ * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
++ * @buf: buffer to test
++ * @len: buffer length
++ * @bitflips_threshold: maximum number of bitflips
++ *
++ * Check if a buffer contains only 0xff, which means the underlying region
++ * has been erased and is ready to be programmed.
++ * The bitflips_threshold specify the maximum number of bitflips before
++ * considering the region is not erased.
++ * Note: The logic of this function has been extracted from the memweight
++ * implementation, except that nand_check_erased_buf function exit before
++ * testing the whole buffer if the number of bitflips exceed the
++ * bitflips_threshold value.
++ *
++ * Returns a positive number of bitflips less than or equal to
++ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
++ * threshold.
++ */
++static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
++{
++	const unsigned char *bitmap = buf;
++	int bitflips = 0;
++	int weight;
++
++	for (; len && ((uintptr_t)bitmap) % sizeof(long);
++	     len--, bitmap++) {
++		weight = hweight8(*bitmap);
++		bitflips += BITS_PER_BYTE - weight;
++		if (unlikely(bitflips > bitflips_threshold))
++			return -EBADMSG;
++	}
++
++	for (; len >= sizeof(long);
++	     len -= sizeof(long), bitmap += sizeof(long)) {
++		unsigned long d = *((unsigned long *)bitmap);
++		if (d == ~0UL)
++			continue;
++		weight = hweight_long(d);
++		bitflips += BITS_PER_LONG - weight;
++		if (unlikely(bitflips > bitflips_threshold))
++			return -EBADMSG;
++	}
++
++	for (; len > 0; len--, bitmap++) {
++		weight = hweight8(*bitmap);
++		bitflips += BITS_PER_BYTE - weight;
++		if (unlikely(bitflips > bitflips_threshold))
++			return -EBADMSG;
++	}
++
++	return bitflips;
++}
++
++/**
++ * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
++ *				 0xff data
++ * @data: data buffer to test
++ * @datalen: data length
++ * @ecc: ECC buffer
++ * @ecclen: ECC length
++ * @extraoob: extra OOB buffer
++ * @extraooblen: extra OOB length
++ * @bitflips_threshold: maximum number of bitflips
++ *
++ * Check if a data buffer and its associated ECC and OOB data contains only
++ * 0xff pattern, which means the underlying region has been erased and is
++ * ready to be programmed.
++ * The bitflips_threshold specify the maximum number of bitflips before
++ * considering the region as not erased.
++ *
++ * Note:
++ * 1/ ECC algorithms are working on pre-defined block sizes which are usually
++ *    different from the NAND page size. When fixing bitflips, ECC engines will
++ *    report the number of errors per chunk, and the NAND core infrastructure
++ *    expect you to return the maximum number of bitflips for the whole page.
++ *    This is why you should always use this function on a single chunk and
++ *    not on the whole page. After checking each chunk you should update your
++ *    max_bitflips value accordingly.
++ * 2/ When checking for bitflips in erased pages you should not only check
++ *    the payload data but also their associated ECC data, because a user might
++ *    have programmed almost all bits to 1 but a few. In this case, we
++ *    shouldn't consider the chunk as erased, and checking ECC bytes prevent
++ *    this case.
++ * 3/ The extraoob argument is optional, and should be used if some of your OOB
++ *    data are protected by the ECC engine.
++ *    It could also be used if you support subpages and want to attach some
++ *    extra OOB data to an ECC chunk.
++ *
++ * Returns a positive number of bitflips less than or equal to
++ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
++ * threshold. In case of success, the passed buffers are filled with 0xff.
++ */
++int nand_check_erased_ecc_chunk(void *data, int datalen,
++				void *ecc, int ecclen,
++				void *extraoob, int extraooblen,
++				int bitflips_threshold)
++{
++	int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
++
++	data_bitflips = nand_check_erased_buf(data, datalen,
++					      bitflips_threshold);
++	if (data_bitflips < 0)
++		return data_bitflips;
++
++	bitflips_threshold -= data_bitflips;
++
++	ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
++	if (ecc_bitflips < 0)
++		return ecc_bitflips;
++
++	bitflips_threshold -= ecc_bitflips;
++
++	extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
++						  bitflips_threshold);
++	if (extraoob_bitflips < 0)
++		return extraoob_bitflips;
++
++	if (data_bitflips)
++		memset(data, 0xff, datalen);
++
++	if (ecc_bitflips)
++		memset(ecc, 0xff, ecclen);
++
++	if (extraoob_bitflips)
++		memset(extraoob, 0xff, extraooblen);
++
++	return data_bitflips + ecc_bitflips + extraoob_bitflips;
++}
++EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
++
++/**
+  * nanddev_isbad() - Check if a block is bad
+  * @nand: NAND device
+  * @pos: position pointing to the block we want to check
+--- a/drivers/mtd/nand/raw/nand_base.c
++++ b/drivers/mtd/nand/raw/nand_base.c
+@@ -2784,137 +2784,6 @@ int nand_set_features(struct nand_chip *
+ }
+ 
+ /**
+- * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
+- * @buf: buffer to test
+- * @len: buffer length
+- * @bitflips_threshold: maximum number of bitflips
+- *
+- * Check if a buffer contains only 0xff, which means the underlying region
+- * has been erased and is ready to be programmed.
+- * The bitflips_threshold specify the maximum number of bitflips before
+- * considering the region is not erased.
+- * Note: The logic of this function has been extracted from the memweight
+- * implementation, except that nand_check_erased_buf function exit before
+- * testing the whole buffer if the number of bitflips exceed the
+- * bitflips_threshold value.
+- *
+- * Returns a positive number of bitflips less than or equal to
+- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+- * threshold.
+- */
+-static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
+-{
+-	const unsigned char *bitmap = buf;
+-	int bitflips = 0;
+-	int weight;
+-
+-	for (; len && ((uintptr_t)bitmap) % sizeof(long);
+-	     len--, bitmap++) {
+-		weight = hweight8(*bitmap);
+-		bitflips += BITS_PER_BYTE - weight;
+-		if (unlikely(bitflips > bitflips_threshold))
+-			return -EBADMSG;
+-	}
+-
+-	for (; len >= sizeof(long);
+-	     len -= sizeof(long), bitmap += sizeof(long)) {
+-		unsigned long d = *((unsigned long *)bitmap);
+-		if (d == ~0UL)
+-			continue;
+-		weight = hweight_long(d);
+-		bitflips += BITS_PER_LONG - weight;
+-		if (unlikely(bitflips > bitflips_threshold))
+-			return -EBADMSG;
+-	}
+-
+-	for (; len > 0; len--, bitmap++) {
+-		weight = hweight8(*bitmap);
+-		bitflips += BITS_PER_BYTE - weight;
+-		if (unlikely(bitflips > bitflips_threshold))
+-			return -EBADMSG;
+-	}
+-
+-	return bitflips;
+-}
+-
+-/**
+- * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
+- *				 0xff data
+- * @data: data buffer to test
+- * @datalen: data length
+- * @ecc: ECC buffer
+- * @ecclen: ECC length
+- * @extraoob: extra OOB buffer
+- * @extraooblen: extra OOB length
+- * @bitflips_threshold: maximum number of bitflips
+- *
+- * Check if a data buffer and its associated ECC and OOB data contains only
+- * 0xff pattern, which means the underlying region has been erased and is
+- * ready to be programmed.
+- * The bitflips_threshold specify the maximum number of bitflips before
+- * considering the region as not erased.
+- *
+- * Note:
+- * 1/ ECC algorithms are working on pre-defined block sizes which are usually
+- *    different from the NAND page size. When fixing bitflips, ECC engines will
+- *    report the number of errors per chunk, and the NAND core infrastructure
+- *    expect you to return the maximum number of bitflips for the whole page.
+- *    This is why you should always use this function on a single chunk and
+- *    not on the whole page. After checking each chunk you should update your
+- *    max_bitflips value accordingly.
+- * 2/ When checking for bitflips in erased pages you should not only check
+- *    the payload data but also their associated ECC data, because a user might
+- *    have programmed almost all bits to 1 but a few. In this case, we
+- *    shouldn't consider the chunk as erased, and checking ECC bytes prevent
+- *    this case.
+- * 3/ The extraoob argument is optional, and should be used if some of your OOB
+- *    data are protected by the ECC engine.
+- *    It could also be used if you support subpages and want to attach some
+- *    extra OOB data to an ECC chunk.
+- *
+- * Returns a positive number of bitflips less than or equal to
+- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+- * threshold. In case of success, the passed buffers are filled with 0xff.
+- */
+-int nand_check_erased_ecc_chunk(void *data, int datalen,
+-				void *ecc, int ecclen,
+-				void *extraoob, int extraooblen,
+-				int bitflips_threshold)
+-{
+-	int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
+-
+-	data_bitflips = nand_check_erased_buf(data, datalen,
+-					      bitflips_threshold);
+-	if (data_bitflips < 0)
+-		return data_bitflips;
+-
+-	bitflips_threshold -= data_bitflips;
+-
+-	ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
+-	if (ecc_bitflips < 0)
+-		return ecc_bitflips;
+-
+-	bitflips_threshold -= ecc_bitflips;
+-
+-	extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
+-						  bitflips_threshold);
+-	if (extraoob_bitflips < 0)
+-		return extraoob_bitflips;
+-
+-	if (data_bitflips)
+-		memset(data, 0xff, datalen);
+-
+-	if (ecc_bitflips)
+-		memset(ecc, 0xff, ecclen);
+-
+-	if (extraoob_bitflips)
+-		memset(extraoob, 0xff, extraooblen);
+-
+-	return data_bitflips + ecc_bitflips + extraoob_bitflips;
+-}
+-EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
+-
+-/**
+  * nand_read_page_raw_notsupp - dummy read raw page function
+  * @chip: nand chip info structure
+  * @buf: buffer to store read data
+--- a/include/linux/mtd/nand.h
++++ b/include/linux/mtd/nand.h
+@@ -1136,4 +1136,9 @@ static inline bool nanddev_bbt_is_initia
+ int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo);
+ int nanddev_mtd_max_bad_blocks(struct mtd_info *mtd, loff_t offs, size_t len);
+ 
++int nand_check_erased_ecc_chunk(void *data, int datalen,
++				void *ecc, int ecclen,
++				void *extraoob, int extraooblen,
++				int threshold);
++
+ #endif /* __LINUX_MTD_NAND_H */
+--- a/include/linux/mtd/rawnand.h
++++ b/include/linux/mtd/rawnand.h
+@@ -1519,11 +1519,6 @@ int rawnand_sw_bch_correct(struct nand_c
+ 			   unsigned char *read_ecc, unsigned char *calc_ecc);
+ void rawnand_sw_bch_cleanup(struct nand_chip *chip);
+ 
+-int nand_check_erased_ecc_chunk(void *data, int datalen,
+-				void *ecc, int ecclen,
+-				void *extraoob, int extraooblen,
+-				int threshold);
+-
+ int nand_ecc_choose_conf(struct nand_chip *chip,
+ 			 const struct nand_ecc_caps *caps, int oobavail);
+ 
diff --git a/target/linux/realtek/dts/rtl930x.dtsi b/target/linux/realtek/dts/rtl930x.dtsi
index d3c6a2dd6c..f358f50417 100644
--- a/target/linux/realtek/dts/rtl930x.dtsi
+++ b/target/linux/realtek/dts/rtl930x.dtsi
@@ -55,6 +55,13 @@
 		#size-cells = <1>;
 		ranges = <0x0 0x18000000 0x20000>;
 
+		ecc0: ecc at 1a600 {
+			compatible = "realtek,rtl9301-ecc";
+			reg = <0x1a600 0x54>;
+
+			status = "disabled";
+		};
+
 		intc: interrupt-controller at 3000 {
 			compatible = "realtek,rtl9300-intc", "realtek,rtl-intc";
 			reg = <0x3000 0x18>, <0x3018 0x18>;
diff --git a/target/linux/realtek/dts/rtl931x.dtsi b/target/linux/realtek/dts/rtl931x.dtsi
index 58121bea2e..eeda87ef23 100644
--- a/target/linux/realtek/dts/rtl931x.dtsi
+++ b/target/linux/realtek/dts/rtl931x.dtsi
@@ -90,6 +90,13 @@
 		#size-cells = <1>;
 		ranges = <0x0 0x18000000 0x20000>;
 
+		ecc0: ecc at 1a600 {
+			compatible = "realtek,rtl9301-ecc";
+			reg = <0x1a600 0x54>;
+
+			status = "disabled";
+		};
+
 		spi0: spi at 1200 {
 			status = "okay";
 
diff --git a/target/linux/realtek/patches-6.12/021-v6.18-mtd-nand-add-realtek-ecc-engine.patch b/target/linux/realtek/patches-6.12/021-v6.18-mtd-nand-add-realtek-ecc-engine.patch
new file mode 100644
index 0000000000..17c63fcdcb
--- /dev/null
+++ b/target/linux/realtek/patches-6.12/021-v6.18-mtd-nand-add-realtek-ecc-engine.patch
@@ -0,0 +1,521 @@
+From 3148d0e5b1c5733d69ec51b70c8280e46488750a Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen at gmx.de>
+Date: Fri, 19 Sep 2025 03:52:01 -0400
+Subject: mtd: nand: realtek-ecc: Add Realtek external ECC engine support
+
+The Realtek RTl93xx switch SoC series has a built in ECC controller
+that can provide BCH6 or BCH12 over 512 data and 6 tag bytes. It
+generates 10 (BCH6) or 20 (BCH12) bytes of parity.
+
+This engine will most likely work in conjunction with the Realtek
+spi-mem based NAND controller but can work on its own. Therefore
+the initial implementation will be of type external.
+
+Remark! The engine can support any data blocks that are multiples
+of 512 bytes. For now limit it to data+oob layouts that have been
+analyzed from existing devices. This way it keeps compatibility
+and pre-existing vendor data can be read.
+
+Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
+---
+ drivers/mtd/nand/Kconfig       |   8 +
+ drivers/mtd/nand/Makefile      |   1 +
+ drivers/mtd/nand/ecc-realtek.c | 464 +++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 473 insertions(+)
+ create mode 100644 drivers/mtd/nand/ecc-realtek.c
+
+--- a/drivers/mtd/nand/Kconfig
++++ b/drivers/mtd/nand/Kconfig
+@@ -65,6 +65,14 @@ config MTD_NAND_ECC_MEDIATEK
+ 	help
+ 	  This enables support for the hardware ECC engine from Mediatek.
+ 
++config MTD_NAND_ECC_REALTEK
++        tristate "Realtek RTL93xx hardware ECC engine"
++        depends on HAS_IOMEM
++        depends on MACH_REALTEK_RTL || COMPILE_TEST
++        select MTD_NAND_ECC
++        help
++          This enables support for the hardware ECC engine from Realtek.
++
+ endmenu
+ 
+ endmenu
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -2,6 +2,7 @@
+ 
+ nandcore-objs := core.o bbt.o
+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
++obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o
+ obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
+ obj-$(CONFIG_MTD_NAND_MTK_BMT)	+= mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o
+ ifeq ($(CONFIG_SPI_QPIC_SNAND),y)
+--- /dev/null
++++ b/drivers/mtd/nand/ecc-realtek.c
+@@ -0,0 +1,464 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Support for Realtek hardware ECC engine in RTL93xx SoCs
++ */
++
++#include <linux/bitfield.h>
++#include <linux/dma-mapping.h>
++#include <linux/mtd/nand.h>
++#include <linux/mutex.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++/*
++ * The Realtek ECC engine has two operation modes.
++ *
++ * - BCH6  : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes
++ * - 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.
++ *
++ * 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.
++ *
++ * - BBI      : Bad block indicator. The first two bytes of OOB. Protected by ECC!
++ * - tag      : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC!
++ * - syndrome : ECC/parity bytes
++ *
++ * Altogether this gives currently the following block layout.
++ *
++ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
++ * |  512 |  512 |  512 |  512 |   2 |    4 |    6 |    6 |    6 |  10 |  10 |  10 |  10 |
++ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
++ * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC |
++ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
++ */
++
++#define RTL_ECC_ALLOWED_PAGE_SIZE 	2048
++#define RTL_ECC_ALLOWED_OOB_SIZE	64
++#define RTL_ECC_ALLOWED_STRENGTH	6
++
++#define RTL_ECC_BLOCK_SIZE 		512
++#define RTL_ECC_FREE_SIZE 		6
++#define RTL_ECC_PARITY_SIZE_BCH6	10
++#define RTL_ECC_PARITY_SIZE_BCH12	20
++
++/*
++ * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes
++ * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be
++ * aligned to a multiple of 4.
++ */
++
++#define RTL_ECC_DMA_FREE_PARITY_SIZE	ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4)
++#define RTL_ECC_DMA_SIZE		(RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE)
++
++#define RTL_ECC_CFG			0x00
++#define   RTL_ECC_BCH6			0
++#define   RTL_ECC_BCH12			BIT(28)
++#define   RTL_ECC_DMA_PRECISE		BIT(12)
++#define   RTL_ECC_BURST_128		GENMASK(1, 0)
++#define RTL_ECC_DMA_TRIGGER 		0x08
++#define   RTL_ECC_OP_DECODE		0
++#define   RTL_ECC_OP_ENCODE		BIT(0)
++#define RTL_ECC_DMA_START		0x0c
++#define RTL_ECC_DMA_TAG			0x10
++#define RTL_ECC_STATUS			0x14
++#define   RTL_ECC_CORR_COUNT		GENMASK(19, 12)
++#define   RTL_ECC_RESULT		BIT(8)
++#define   RTL_ECC_ALL_ONE		BIT(4)
++#define   RTL_ECC_OP_STATUS		BIT(0)
++
++struct rtl_ecc_engine {
++	struct device *dev;
++	struct nand_ecc_engine engine;
++	struct mutex lock;
++	char *buf;
++	dma_addr_t buf_dma;
++	struct regmap *regmap;
++};
++
++struct rtl_ecc_ctx {
++	struct rtl_ecc_engine * rtlc;
++	struct nand_ecc_req_tweak_ctx req_ctx;
++	int steps;
++	int bch_mode;
++	int strength;
++	int parity_size;
++};
++
++static const struct regmap_config rtl_ecc_regmap_config = {
++	.reg_bits	= 32,
++	.val_bits	= 32,
++	.reg_stride	= 4,
++};
++
++static inline void *nand_to_ctx(struct nand_device *nand)
++{
++	return nand->ecc.ctx.priv;
++}
++
++static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand)
++{
++	struct nand_ecc_engine *eng = nand->ecc.engine;
++
++	return container_of(eng, struct rtl_ecc_engine, engine);
++}
++
++static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
++				 struct mtd_oob_region *oobregion)
++{
++	struct nand_device *nand = mtd_to_nanddev(mtd);
++	struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
++
++	if (section < 0 || section >= ctx->steps)
++		return -ERANGE;
++
++	oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size;
++	oobregion->length = ctx->parity_size;
++
++	return 0;
++}
++
++static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section,
++				  struct mtd_oob_region *oobregion)
++{
++	struct nand_device *nand = mtd_to_nanddev(mtd);
++	struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
++	int bbm;
++
++	if (section < 0 || section >= ctx->steps)
++		return -ERANGE;
++
++	/* reserve 2 BBM bytes in first block */
++	bbm = section ? 0 : 2;
++	oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm;
++	oobregion->length = RTL_ECC_FREE_SIZE - bbm;
++
++	return 0;
++}
++
++static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = {
++	.ecc = rtl_ecc_ooblayout_ecc,
++	.free = rtl_ecc_ooblayout_free,
++};
++
++static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation)
++{
++	struct rtl_ecc_engine *rtlc = ctx->rtlc;
++
++	regmap_write(rtlc->regmap, RTL_ECC_CFG,
++		     ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE);
++
++	regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma);
++	regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE);
++	regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation);
++}
++
++static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx)
++{
++	struct rtl_ecc_engine *rtlc = ctx->rtlc;
++	int ret, status, bitflips;
++	bool all_one;
++
++	/*
++	 * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data
++	 * and 6 free bytes. In case the NAND area has been erased and all data and oob is
++	 * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger
++	 * interrupts when finished, use active polling for now. 12 us maximum wait time has
++	 * proven to be a good tradeoff between performance and overhead.
++	 */
++
++	ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status,
++				       !(status & RTL_ECC_OP_STATUS), 12, 1000000);
++	if (ret)
++		return ret;
++
++	ret = FIELD_GET(RTL_ECC_RESULT, status);
++	all_one = FIELD_GET(RTL_ECC_ALL_ONE, status);
++	bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status);
++
++	/* For erased blocks (all bits one) error status can be ignored */
++	if (all_one)
++		ret = 0;
++
++	return ret ? -EBADMSG : bitflips;
++}
++
++static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free,
++			      char *parity, int operation)
++{
++	struct rtl_ecc_engine *rtlc = ctx->rtlc;
++	char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE;
++	char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE;
++	char *buf_data = rtlc->buf;
++	int ret;
++
++	mutex_lock(&rtlc->lock);
++
++	memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE);
++	memcpy(buf_free, free, RTL_ECC_FREE_SIZE);
++	memcpy(buf_parity, parity, ctx->parity_size);
++
++	dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE);
++	rtl_ecc_kick_engine(ctx, operation);
++	ret = rtl_ecc_wait_for_engine(ctx);
++	dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE);
++
++	if (ret >= 0) {
++		memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE);
++		memcpy(free, buf_free, RTL_ECC_FREE_SIZE);
++		memcpy(parity, buf_parity, ctx->parity_size);
++	}
++
++	mutex_unlock(&rtlc->lock);
++
++	return ret;
++}
++
++static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req)
++{
++	struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
++	struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
++	char *data, *free, *parity;
++	int ret = 0;
++
++	if (req->mode == MTD_OPS_RAW)
++		return 0;
++
++	nand_ecc_tweak_req(&ctx->req_ctx, req);
++
++	if (req->type == NAND_PAGE_READ)
++		return 0;
++
++	free = req->oobbuf.in;
++	data = req->databuf.in;
++	parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
++
++	for (int i = 0; i < ctx->steps; i++) {
++		ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE);
++
++		free += RTL_ECC_FREE_SIZE;
++		data += RTL_ECC_BLOCK_SIZE;
++		parity += ctx->parity_size;
++	}
++
++	if (unlikely(ret))
++		dev_dbg(rtlc->dev, "ECC calculation failed\n");
++
++	return ret ? -EBADMSG : 0;
++}
++
++static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req)
++{
++	struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
++	struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
++	struct mtd_info *mtd = nanddev_to_mtd(nand);
++	char *data, *free, *parity;
++	bool failure = false;
++	int bitflips = 0;
++
++	if (req->mode == MTD_OPS_RAW)
++		return 0;
++
++	if (req->type == NAND_PAGE_WRITE) {
++		nand_ecc_restore_req(&ctx->req_ctx, req);
++		return 0;
++	}
++
++	free = req->oobbuf.in;
++	data = req->databuf.in;
++	parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
++
++	for (int i = 0 ; i < ctx->steps; i++) {
++		int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE);
++
++		if (unlikely(ret < 0))
++			/* ECC totally fails for bitflips in erased blocks */
++			ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE,
++							  parity, ctx->parity_size,
++							  free, RTL_ECC_FREE_SIZE,
++							  ctx->strength);
++		if (unlikely(ret < 0)) {
++			failure = true;
++			mtd->ecc_stats.failed++;
++		} else {
++			mtd->ecc_stats.corrected += ret;
++			bitflips = max_t(unsigned int, bitflips, ret);
++		}
++
++		free += RTL_ECC_FREE_SIZE;
++		data += RTL_ECC_BLOCK_SIZE;
++		parity += ctx->parity_size;
++	}
++
++	nand_ecc_restore_req(&ctx->req_ctx, req);
++
++	if (unlikely(failure))
++		dev_dbg(rtlc->dev, "ECC correction failed\n");
++	else if (unlikely(bitflips > 2))
++		dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips);
++
++	return failure ? -EBADMSG : bitflips;
++}
++
++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 ||
++	    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);
++		return -EINVAL;
++	}
++
++	if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH ||
++	    nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH ||
++	    nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB ||
++	    nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) {
++		dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n",
++			RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE);
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static int rtl_ecc_init_ctx(struct nand_device *nand)
++{
++	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
++	struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
++	struct mtd_info *mtd = nanddev_to_mtd(nand);
++	int strength = nand->ecc.user_conf.strength;
++	struct device *dev = nand->ecc.engine->dev;
++	struct rtl_ecc_ctx *ctx;
++	int ret;
++
++	ret = rtl_ecc_check_support(nand);
++	if (ret)
++		return ret;
++
++	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
++	if (!ctx)
++		return -ENOMEM;
++
++	nand->ecc.ctx.priv = ctx;
++	mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops);
++
++	conf->algo = NAND_ECC_ALGO_BCH;
++	conf->strength = strength;
++	conf->step_size = RTL_ECC_BLOCK_SIZE;
++	conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
++
++	ctx->rtlc = rtlc;
++	ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE;
++	ctx->strength = strength;
++	ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12;
++	ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12;
++
++	ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
++	if (ret)
++		return ret;
++
++	dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d",
++		conf->strength, ctx->steps, conf->step_size,
++		ctx->steps, ctx->steps, ctx->parity_size);
++
++	return 0;
++}
++
++static void rtl_ecc_cleanup_ctx(struct nand_device *nand)
++{
++	struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
++
++	if (ctx)
++		nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
++}
++
++static struct nand_ecc_engine_ops rtl_ecc_engine_ops = {
++	.init_ctx = rtl_ecc_init_ctx,
++	.cleanup_ctx = rtl_ecc_cleanup_ctx,
++	.prepare_io_req = rtl_ecc_prepare_io_req,
++	.finish_io_req = rtl_ecc_finish_io_req,
++};
++
++static int rtl_ecc_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct rtl_ecc_engine *rtlc;
++	void __iomem *base;
++	int ret;
++
++	rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL);
++	if (!rtlc)
++		return -ENOMEM;
++
++	base = devm_platform_ioremap_resource(pdev, 0);
++	if (IS_ERR(base))
++		return PTR_ERR(base);
++
++	ret = devm_mutex_init(dev, &rtlc->lock);
++	if (ret)
++		return ret;
++
++	rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config);
++	if (IS_ERR(rtlc->regmap))
++		return PTR_ERR(rtlc->regmap);
++
++	/*
++	 * Focus on simplicity and use a preallocated DMA buffer for data exchange with the
++	 * engine. For now make it a noncoherent memory model as invalidating/flushing caches
++	 * is faster than reading/writing uncached memory on the known architectures.
++	 */
++
++	rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma,
++					  DMA_BIDIRECTIONAL, GFP_KERNEL);
++	if (IS_ERR(rtlc->buf))
++		return PTR_ERR(rtlc->buf);
++
++	rtlc->dev = dev;
++	rtlc->engine.dev = dev;
++	rtlc->engine.ops = &rtl_ecc_engine_ops;
++	rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
++
++	nand_ecc_register_on_host_hw_engine(&rtlc->engine);
++
++	platform_set_drvdata(pdev, rtlc);
++
++	return 0;
++}
++
++static void rtl_ecc_remove(struct platform_device *pdev)
++{
++	struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev);
++
++	nand_ecc_unregister_on_host_hw_engine(&rtlc->engine);
++	dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma,
++			     DMA_BIDIRECTIONAL);
++}
++
++static const struct of_device_id rtl_ecc_of_ids[] = {
++	{
++		.compatible = "realtek,rtl9301-ecc",
++	},
++	{ /* sentinel */ },
++};
++
++static struct platform_driver rtl_ecc_driver = {
++	.driver	= {
++		.name = "rtl-nand-ecc-engine",
++		.of_match_table = rtl_ecc_of_ids,
++	},
++	.probe = rtl_ecc_probe,
++	.remove = rtl_ecc_remove,
++};
++module_platform_driver(rtl_ecc_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen at gmx.de>");
++MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");
diff --git a/target/linux/realtek/rtl838x/config-6.12 b/target/linux/realtek/rtl838x/config-6.12
index 8afd239b3b..ef2f183fc3 100644
--- a/target/linux/realtek/rtl838x/config-6.12
+++ b/target/linux/realtek/rtl838x/config-6.12
@@ -158,6 +158,8 @@ CONFIG_MTD_CFI_ADV_OPTIONS=y
 CONFIG_MTD_CFI_GEOMETRY=y
 CONFIG_MTD_CMDLINE_PARTS=y
 CONFIG_MTD_JEDECPROBE=y
+# CONFIG_MTD_NAND_ECC_REALTEK is not set
+# CONFIG_MTD_SPI_NAND is not set
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_BRNIMAGE_FW=y
 CONFIG_MTD_SPLIT_EVA_FW=y
diff --git a/target/linux/realtek/rtl839x/config-6.12 b/target/linux/realtek/rtl839x/config-6.12
index 022cafea46..26e8c2bb57 100644
--- a/target/linux/realtek/rtl839x/config-6.12
+++ b/target/linux/realtek/rtl839x/config-6.12
@@ -161,6 +161,8 @@ CONFIG_MTD_CFI_ADV_OPTIONS=y
 CONFIG_MTD_CFI_GEOMETRY=y
 CONFIG_MTD_CMDLINE_PARTS=y
 CONFIG_MTD_JEDECPROBE=y
+# CONFIG_MTD_NAND_ECC_REALTEK is not set
+# CONFIG_MTD_SPI_NAND is not set
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_BRNIMAGE_FW=y
 CONFIG_MTD_SPLIT_EVA_FW=y
diff --git a/target/linux/realtek/rtl930x/config-6.12 b/target/linux/realtek/rtl930x/config-6.12
index 5b3b426019..4da9fe4bca 100644
--- a/target/linux/realtek/rtl930x/config-6.12
+++ b/target/linux/realtek/rtl930x/config-6.12
@@ -145,6 +145,7 @@ CONFIG_MTD_CFI_ADV_OPTIONS=y
 CONFIG_MTD_CFI_GEOMETRY=y
 CONFIG_MTD_CMDLINE_PARTS=y
 CONFIG_MTD_JEDECPROBE=y
+# CONFIG_MTD_NAND_ECC_REALTEK is not set
 # CONFIG_MTD_SPI_NAND is not set
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_BRNIMAGE_FW=y
diff --git a/target/linux/realtek/rtl930x_nand/config-6.12 b/target/linux/realtek/rtl930x_nand/config-6.12
index c4a2806421..cdf866603b 100644
--- a/target/linux/realtek/rtl930x_nand/config-6.12
+++ b/target/linux/realtek/rtl930x_nand/config-6.12
@@ -145,6 +145,7 @@ CONFIG_MTD_CFI_ADV_OPTIONS=y
 CONFIG_MTD_CFI_GEOMETRY=y
 CONFIG_MTD_CMDLINE_PARTS=y
 CONFIG_MTD_JEDECPROBE=y
+CONFIG_MTD_NAND_ECC_REALTEK=y
 CONFIG_MTD_SPI_NAND=y
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_BRNIMAGE_FW=y
diff --git a/target/linux/realtek/rtl931x/config-6.12 b/target/linux/realtek/rtl931x/config-6.12
index ee51cdb256..7c2702552a 100644
--- a/target/linux/realtek/rtl931x/config-6.12
+++ b/target/linux/realtek/rtl931x/config-6.12
@@ -156,6 +156,7 @@ CONFIG_MTD_CFI_ADV_OPTIONS=y
 CONFIG_MTD_CFI_GEOMETRY=y
 CONFIG_MTD_CMDLINE_PARTS=y
 CONFIG_MTD_JEDECPROBE=y
+# CONFIG_MTD_NAND_ECC_REALTEK is not set
 # CONFIG_MTD_SPI_NAND is not set
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_BRNIMAGE_FW=y
diff --git a/target/linux/realtek/rtl931x_nand/config-6.12 b/target/linux/realtek/rtl931x_nand/config-6.12
index 529739d7ac..4f261fab36 100644
--- a/target/linux/realtek/rtl931x_nand/config-6.12
+++ b/target/linux/realtek/rtl931x_nand/config-6.12
@@ -156,6 +156,7 @@ CONFIG_MTD_CFI_ADV_OPTIONS=y
 CONFIG_MTD_CFI_GEOMETRY=y
 CONFIG_MTD_CMDLINE_PARTS=y
 CONFIG_MTD_JEDECPROBE=y
+CONFIG_MTD_NAND_ECC_REALTEK=y
 CONFIG_MTD_SPI_NAND=y
 CONFIG_MTD_SPI_NOR=y
 CONFIG_MTD_SPLIT_BRNIMAGE_FW=y




More information about the lede-commits mailing list