--- /dev/null
+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);
+
--- /dev/null
+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");