]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: backport ECC driver 19746/head
authorMarkus Stockhausen <markus.stockhausen@gmx.de>
Mon, 29 Sep 2025 18:33:08 +0000 (14:33 -0400)
committerRobert Marko <robimarko@gmail.com>
Tue, 30 Sep 2025 09:15:26 +0000 (11:15 +0200)
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@gmx.de>
Link: https://github.com/openwrt/openwrt/pull/19746
Signed-off-by: Robert Marko <robimarko@gmail.com>
target/linux/generic/backport-6.12/402-v6.18-mtd-nand-move-chunk-to-core.patch [new file with mode: 0644]
target/linux/realtek/dts/rtl930x.dtsi
target/linux/realtek/dts/rtl931x.dtsi
target/linux/realtek/patches-6.12/021-v6.18-mtd-nand-add-realtek-ecc-engine.patch [new file with mode: 0644]
target/linux/realtek/rtl838x/config-6.12
target/linux/realtek/rtl839x/config-6.12
target/linux/realtek/rtl930x/config-6.12
target/linux/realtek/rtl930x_nand/config-6.12
target/linux/realtek/rtl931x/config-6.12
target/linux/realtek/rtl931x_nand/config-6.12

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 (file)
index 0000000..7933874
--- /dev/null
@@ -0,0 +1,329 @@
+From 6b88293aae7fb78872e5cc1ec36e2f750ae12e38 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@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@bootlin.com>
+Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
+Signed-off-by: Miquel Raynal <miquel.raynal@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);
index d3c6a2dd6c4bd908b40f8038fa338c55665774ef..f358f50417f1de933aca839ee7b6b9af84b361d6 100644 (file)
                #size-cells = <1>;
                ranges = <0x0 0x18000000 0x20000>;
 
+               ecc0: ecc@1a600 {
+                       compatible = "realtek,rtl9301-ecc";
+                       reg = <0x1a600 0x54>;
+
+                       status = "disabled";
+               };
+
                intc: interrupt-controller@3000 {
                        compatible = "realtek,rtl9300-intc", "realtek,rtl-intc";
                        reg = <0x3000 0x18>, <0x3018 0x18>;
index 58121bea2eb54489c074ca6133751e330e10b2c2..eeda87ef2386f37d6e66c78d01f8d8aff3c4ac2d 100644 (file)
                #size-cells = <1>;
                ranges = <0x0 0x18000000 0x20000>;
 
+               ecc0: ecc@1a600 {
+                       compatible = "realtek,rtl9301-ecc";
+                       reg = <0x1a600 0x54>;
+
+                       status = "disabled";
+               };
+
                spi0: spi@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 (file)
index 0000000..17c63fc
--- /dev/null
@@ -0,0 +1,521 @@
+From 3148d0e5b1c5733d69ec51b70c8280e46488750a Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@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@gmx.de>
+Signed-off-by: Miquel Raynal <miquel.raynal@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@gmx.de>");
++MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");
index 8afd239b3b2148c9e52ad16c5df42280d4331cd0..ef2f183fc36ba95926142b78e8eb1f7879acd5fe 100644 (file)
@@ -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
index 022cafea463c69bc8aaf3d5280959b99e458a6b5..26e8c2bb57720ee4b18ddce9d03034fdf5c1d193 100644 (file)
@@ -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
index 5b3b4260190725d19ceda4cfd21245ab31827956..4da9fe4bca1c7c61de802748fc8a454112e5cd42 100644 (file)
@@ -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
index c4a2806421d5ad6f710d8e96c9694c5ccc6b037f..cdf866603b6ebf8bb52c714e100670e0c99b9548 100644 (file)
@@ -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
index ee51cdb2566f505f8b988c263047eb3fb4026a8a..7c2702552aa0acc65fd2af3d5d1663b402c1bdc9 100644 (file)
@@ -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
index 529739d7acb9c0b6efaf1342df5c42cd20608a8d..4f261fab3650eaf4c8a0e76d30c028dc5a78dacb 100644 (file)
@@ -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