From: Paul Cercueil <paul@crapouillou.net> To: David Woodhouse <dwmw2@infradead.org>, Brian Norris <computersforpeace@gmail.com>, Boris Brezillon <bbrezillon@kernel.org>, Marek Vasut <marek.vasut@gmail.com>, Richard Weinberger <richard@nod.at>, Rob Herring <robh+dt@kernel.org>, Mark Rutland <mark.rutland@arm.com>, Miquel Raynal <miquel.raynal@bootlin.com>, Harvey Hunt <harveyhuntnexus@gmail.com> Cc: od@zcrc.me, Mathieu Malaterre <malat@debian.org>, linux-mtd@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Paul Cercueil <paul@crapouillou.net> Subject: [PATCH v5 10/12] mtd: rawnand: ingenic: Add support for the JZ4740 Date: Wed, 13 Mar 2019 23:22:57 +0100 [thread overview] Message-ID: <20190313222259.28704-10-paul@crapouillou.net> (raw) In-Reply-To: <20190313222259.28704-1-paul@crapouillou.net> Add support for probing the ingenic-nand driver on the JZ4740 SoC from Ingenic, and the jz4740-ecc driver to support the JZ4740-specific ECC hardware. Signed-off-by: Paul Cercueil <paul@crapouillou.net> --- Notes: v2: New patch v3: Also add support for the hardware ECC of the JZ4740 in this patch v4: - Fix formatting issues - Add MODULE_* macros v5: - Rename jz4740_ecc_init to jz4740_ecc_reset - Fix comments blocks not following the kernel style drivers/mtd/nand/raw/ingenic/Kconfig | 10 ++ drivers/mtd/nand/raw/ingenic/Makefile | 1 + drivers/mtd/nand/raw/ingenic/ingenic_nand.c | 48 +++++-- drivers/mtd/nand/raw/ingenic/jz4740_ecc.c | 197 ++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 11 deletions(-) create mode 100644 drivers/mtd/nand/raw/ingenic/jz4740_ecc.c diff --git a/drivers/mtd/nand/raw/ingenic/Kconfig b/drivers/mtd/nand/raw/ingenic/Kconfig index 4bf7d7af3b0a..cc663cc15119 100644 --- a/drivers/mtd/nand/raw/ingenic/Kconfig +++ b/drivers/mtd/nand/raw/ingenic/Kconfig @@ -17,6 +17,16 @@ if MTD_NAND_JZ4780 config MTD_NAND_INGENIC_ECC tristate +config MTD_NAND_JZ4740_ECC + tristate "Hardware BCH support for JZ4740 SoC" + select MTD_NAND_INGENIC_ECC + help + Enable this driver to support the Reed-Solomon error-correction + hardware present on the JZ4740 SoC from Ingenic. + + This driver can also be built as a module. If so, the module + will be called jz4740-ecc. + config MTD_NAND_JZ4780_BCH tristate "Hardware BCH support for JZ4780 SoC" select MTD_NAND_INGENIC_ECC diff --git a/drivers/mtd/nand/raw/ingenic/Makefile b/drivers/mtd/nand/raw/ingenic/Makefile index f3c3c0f230b0..563b7effcf59 100644 --- a/drivers/mtd/nand/raw/ingenic/Makefile +++ b/drivers/mtd/nand/raw/ingenic/Makefile @@ -2,4 +2,5 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o obj-$(CONFIG_MTD_NAND_JZ4780) += ingenic_nand.o obj-$(CONFIG_MTD_NAND_INGENIC_ECC) += ingenic_ecc.o +obj-$(CONFIG_MTD_NAND_JZ4740_ECC) += jz4740_ecc.o obj-$(CONFIG_MTD_NAND_JZ4780_BCH) += jz4780_bch.o diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand.c index a5f2f21c5198..1bd1f18d4532 100644 --- a/drivers/mtd/nand/raw/ingenic/ingenic_nand.c +++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_device.h> #include <linux/gpio/consumer.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -26,13 +27,15 @@ #define DRV_NAME "ingenic-nand" -#define OFFSET_DATA 0x00000000 -#define OFFSET_CMD 0x00400000 -#define OFFSET_ADDR 0x00800000 - /* Command delay when there is no R/B pin. */ #define RB_DELAY_US 100 +struct jz_soc_info { + unsigned long data_offset; + unsigned long addr_offset; + unsigned long cmd_offset; +}; + struct ingenic_nand_cs { unsigned int bank; void __iomem *base; @@ -41,6 +44,7 @@ struct ingenic_nand_cs { struct ingenic_nfc { struct device *dev; struct ingenic_ecc *ecc; + const struct jz_soc_info *soc_info; struct nand_controller controller; unsigned int num_banks; struct list_head chips; @@ -100,9 +104,9 @@ static void ingenic_nand_cmd_ctrl(struct nand_chip *chip, int cmd, return; if (ctrl & NAND_ALE) - writeb(cmd, cs->base + OFFSET_ADDR); + writeb(cmd, cs->base + nfc->soc_info->addr_offset); else if (ctrl & NAND_CLE) - writeb(cmd, cs->base + OFFSET_CMD); + writeb(cmd, cs->base + nfc->soc_info->cmd_offset); } static int ingenic_nand_dev_ready(struct nand_chip *chip) @@ -160,8 +164,13 @@ static int ingenic_nand_attach_chip(struct nand_chip *chip) struct ingenic_nfc *nfc = to_ingenic_nfc(chip->controller); int eccbytes; - chip->ecc.bytes = fls((1 + 8) * chip->ecc.size) * - (chip->ecc.strength / 8); + if (chip->ecc.strength == 4) { + /* JZ4740 uses 9 bytes of ECC to correct maximum 4 errors */ + chip->ecc.bytes = 9; + } else { + chip->ecc.bytes = fls((1 + 8) * chip->ecc.size) * + (chip->ecc.strength / 8); + } switch (chip->ecc.mode) { case NAND_ECC_HW: @@ -270,8 +279,8 @@ static int ingenic_nand_init_chip(struct platform_device *pdev, return -ENOMEM; mtd->dev.parent = dev; - chip->legacy.IO_ADDR_R = cs->base + OFFSET_DATA; - chip->legacy.IO_ADDR_W = cs->base + OFFSET_DATA; + chip->legacy.IO_ADDR_R = cs->base + nfc->soc_info->data_offset; + chip->legacy.IO_ADDR_W = cs->base + nfc->soc_info->data_offset; chip->legacy.chip_delay = RB_DELAY_US; chip->options = NAND_NO_SUBPAGE_WRITE; chip->legacy.select_chip = ingenic_nand_select_chip; @@ -353,6 +362,10 @@ static int ingenic_nand_probe(struct platform_device *pdev) if (!nfc) return -ENOMEM; + nfc->soc_info = device_get_match_data(dev); + if (!nfc->soc_info) + return -EINVAL; + /* * Check for ECC HW before we call nand_scan_ident, to prevent us from * having to call it again if the ECC driver returns -EPROBE_DEFER. @@ -390,8 +403,21 @@ static int ingenic_nand_remove(struct platform_device *pdev) return 0; } +static const struct jz_soc_info jz4740_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00008000, + .addr_offset = 0x00010000, +}; + +static const struct jz_soc_info jz4780_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00400000, + .addr_offset = 0x00800000, +}; + static const struct of_device_id ingenic_nand_dt_match[] = { - { .compatible = "ingenic,jz4780-nand" }, + { .compatible = "ingenic,jz4740-nand", .data = &jz4740_soc_info }, + { .compatible = "ingenic,jz4780-nand", .data = &jz4780_soc_info }, {}, }; MODULE_DEVICE_TABLE(of, ingenic_nand_dt_match); diff --git a/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c b/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c new file mode 100644 index 000000000000..13fea645c7f0 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ4740 ECC controller driver + * + * Copyright (c) 2019 Paul Cercueil <paul@crapouillou.net> + * + * based on jz4740-nand.c + */ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#include "ingenic_ecc.h" + +#define JZ_REG_NAND_ECC_CTRL 0x00 +#define JZ_REG_NAND_DATA 0x04 +#define JZ_REG_NAND_PAR0 0x08 +#define JZ_REG_NAND_PAR1 0x0C +#define JZ_REG_NAND_PAR2 0x10 +#define JZ_REG_NAND_IRQ_STAT 0x14 +#define JZ_REG_NAND_IRQ_CTRL 0x18 +#define JZ_REG_NAND_ERR(x) (0x1C + ((x) << 2)) + +#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4) +#define JZ_NAND_ECC_CTRL_ENCODING BIT(3) +#define JZ_NAND_ECC_CTRL_RS BIT(2) +#define JZ_NAND_ECC_CTRL_RESET BIT(1) +#define JZ_NAND_ECC_CTRL_ENABLE BIT(0) + +#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29)) +#define JZ_NAND_STATUS_PAD_FINISH BIT(4) +#define JZ_NAND_STATUS_DEC_FINISH BIT(3) +#define JZ_NAND_STATUS_ENC_FINISH BIT(2) +#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1) +#define JZ_NAND_STATUS_ERROR BIT(0) + +static const uint8_t empty_block_ecc[] = { + 0xcd, 0x9d, 0x90, 0x58, 0xf4, 0x8b, 0xff, 0xb7, 0x6f +}; + +static void jz4740_ecc_reset(struct ingenic_ecc *ecc, bool calc_ecc) +{ + uint32_t reg; + + /* Clear interrupt status */ + writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); + + /* Initialize and enable ECC hardware */ + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_RESET; + reg |= JZ_NAND_ECC_CTRL_ENABLE; + reg |= JZ_NAND_ECC_CTRL_RS; + if (calc_ecc) /* calculate ECC from data */ + reg |= JZ_NAND_ECC_CTRL_ENCODING; + else /* correct data from ECC */ + reg &= ~JZ_NAND_ECC_CTRL_ENCODING; + + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); +} + +static int jz4740_ecc_calculate(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + uint32_t reg, status; + unsigned int timeout = 1000; + int i; + + jz4740_ecc_reset(ecc, true); + + do { + status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout); + + if (timeout == 0) + return -ETIMEDOUT; + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + for (i = 0; i < params->bytes; ++i) + ecc_code[i] = readb(ecc->base + JZ_REG_NAND_PAR0 + i); + + /* + * If the written data is completely 0xff, we also want to write 0xff as + * ECC, otherwise we will get in trouble when doing subpage writes. + */ + if (memcmp(ecc_code, empty_block_ecc, ARRAY_SIZE(empty_block_ecc)) == 0) + memset(ecc_code, 0xff, ARRAY_SIZE(empty_block_ecc)); + + return 0; +} + +static void jz_nand_correct_data(uint8_t *buf, int index, int mask) +{ + int offset = index & 0x7; + uint16_t data; + + index += (index >> 3); + + data = buf[index]; + data |= buf[index + 1] << 8; + + mask ^= (data >> offset) & 0x1ff; + data &= ~(0x1ff << offset); + data |= (mask << offset); + + buf[index] = data & 0xff; + buf[index + 1] = (data >> 8) & 0xff; +} + +static int jz4740_ecc_correct(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code) +{ + int i, error_count, index; + uint32_t reg, status, error; + unsigned int timeout = 1000; + + jz4740_ecc_reset(ecc, false); + + for (i = 0; i < params->bytes; ++i) + writeb(ecc_code[i], ecc->base + JZ_REG_NAND_PAR0 + i); + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_PAR_READY; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + do { + status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout); + + if (timeout == 0) + return -ETIMEDOUT; + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + if (status & JZ_NAND_STATUS_ERROR) { + if (status & JZ_NAND_STATUS_UNCOR_ERROR) + return -EBADMSG; + + error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29; + + for (i = 0; i < error_count; ++i) { + error = readl(ecc->base + JZ_REG_NAND_ERR(i)); + index = ((error >> 16) & 0x1ff) - 1; + if (index >= 0 && index < params->size) + jz_nand_correct_data(buf, index, error & 0x1ff); + } + + return error_count; + } + + return 0; +} + +static void jz4740_ecc_disable(struct ingenic_ecc *ecc) +{ + u32 reg; + + writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); +} + +static const struct ingenic_ecc_ops jz4740_ecc_ops = { + .disable = jz4740_ecc_disable, + .calculate = jz4740_ecc_calculate, + .correct = jz4740_ecc_correct, +}; + +static const struct of_device_id jz4740_ecc_dt_match[] = { + { .compatible = "ingenic,jz4740-ecc", .data = &jz4740_ecc_ops }, + {}, +}; +MODULE_DEVICE_TABLE(of, jz4740_ecc_dt_match); + +static struct platform_driver jz4740_ecc_driver = { + .probe = ingenic_ecc_probe, + .driver = { + .name = "jz4740-ecc", + .of_match_table = jz4740_ecc_dt_match, + }, +}; +module_platform_driver(jz4740_ecc_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_DESCRIPTION("Ingenic JZ4740 ECC controller driver"); +MODULE_LICENSE("GPL v2"); -- 2.11.0
WARNING: multiple messages have this Message-ID (diff)
From: Paul Cercueil <paul@crapouillou.net> To: David Woodhouse <dwmw2@infradead.org>, Brian Norris <computersforpeace@gmail.com>, Boris Brezillon <bbrezillon@kernel.org>, Marek Vasut <marek.vasut@gmail.com>, Richard Weinberger <richard@nod.at>, Rob Herring <robh+dt@kernel.org>, Mark Rutland <mark.rutland@arm.com>, Miquel Raynal <miquel.raynal@bootlin.com>, Harvey Hunt <harveyhuntnexus@gmail.com> Cc: devicetree@vger.kernel.org, Mathieu Malaterre <malat@debian.org>, linux-kernel@vger.kernel.org, Paul Cercueil <paul@crapouillou.net>, od@zcrc.me, linux-mtd@lists.infradead.org Subject: [PATCH v5 10/12] mtd: rawnand: ingenic: Add support for the JZ4740 Date: Wed, 13 Mar 2019 23:22:57 +0100 [thread overview] Message-ID: <20190313222259.28704-10-paul@crapouillou.net> (raw) In-Reply-To: <20190313222259.28704-1-paul@crapouillou.net> Add support for probing the ingenic-nand driver on the JZ4740 SoC from Ingenic, and the jz4740-ecc driver to support the JZ4740-specific ECC hardware. Signed-off-by: Paul Cercueil <paul@crapouillou.net> --- Notes: v2: New patch v3: Also add support for the hardware ECC of the JZ4740 in this patch v4: - Fix formatting issues - Add MODULE_* macros v5: - Rename jz4740_ecc_init to jz4740_ecc_reset - Fix comments blocks not following the kernel style drivers/mtd/nand/raw/ingenic/Kconfig | 10 ++ drivers/mtd/nand/raw/ingenic/Makefile | 1 + drivers/mtd/nand/raw/ingenic/ingenic_nand.c | 48 +++++-- drivers/mtd/nand/raw/ingenic/jz4740_ecc.c | 197 ++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 11 deletions(-) create mode 100644 drivers/mtd/nand/raw/ingenic/jz4740_ecc.c diff --git a/drivers/mtd/nand/raw/ingenic/Kconfig b/drivers/mtd/nand/raw/ingenic/Kconfig index 4bf7d7af3b0a..cc663cc15119 100644 --- a/drivers/mtd/nand/raw/ingenic/Kconfig +++ b/drivers/mtd/nand/raw/ingenic/Kconfig @@ -17,6 +17,16 @@ if MTD_NAND_JZ4780 config MTD_NAND_INGENIC_ECC tristate +config MTD_NAND_JZ4740_ECC + tristate "Hardware BCH support for JZ4740 SoC" + select MTD_NAND_INGENIC_ECC + help + Enable this driver to support the Reed-Solomon error-correction + hardware present on the JZ4740 SoC from Ingenic. + + This driver can also be built as a module. If so, the module + will be called jz4740-ecc. + config MTD_NAND_JZ4780_BCH tristate "Hardware BCH support for JZ4780 SoC" select MTD_NAND_INGENIC_ECC diff --git a/drivers/mtd/nand/raw/ingenic/Makefile b/drivers/mtd/nand/raw/ingenic/Makefile index f3c3c0f230b0..563b7effcf59 100644 --- a/drivers/mtd/nand/raw/ingenic/Makefile +++ b/drivers/mtd/nand/raw/ingenic/Makefile @@ -2,4 +2,5 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o obj-$(CONFIG_MTD_NAND_JZ4780) += ingenic_nand.o obj-$(CONFIG_MTD_NAND_INGENIC_ECC) += ingenic_ecc.o +obj-$(CONFIG_MTD_NAND_JZ4740_ECC) += jz4740_ecc.o obj-$(CONFIG_MTD_NAND_JZ4780_BCH) += jz4780_bch.o diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand.c index a5f2f21c5198..1bd1f18d4532 100644 --- a/drivers/mtd/nand/raw/ingenic/ingenic_nand.c +++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_device.h> #include <linux/gpio/consumer.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -26,13 +27,15 @@ #define DRV_NAME "ingenic-nand" -#define OFFSET_DATA 0x00000000 -#define OFFSET_CMD 0x00400000 -#define OFFSET_ADDR 0x00800000 - /* Command delay when there is no R/B pin. */ #define RB_DELAY_US 100 +struct jz_soc_info { + unsigned long data_offset; + unsigned long addr_offset; + unsigned long cmd_offset; +}; + struct ingenic_nand_cs { unsigned int bank; void __iomem *base; @@ -41,6 +44,7 @@ struct ingenic_nand_cs { struct ingenic_nfc { struct device *dev; struct ingenic_ecc *ecc; + const struct jz_soc_info *soc_info; struct nand_controller controller; unsigned int num_banks; struct list_head chips; @@ -100,9 +104,9 @@ static void ingenic_nand_cmd_ctrl(struct nand_chip *chip, int cmd, return; if (ctrl & NAND_ALE) - writeb(cmd, cs->base + OFFSET_ADDR); + writeb(cmd, cs->base + nfc->soc_info->addr_offset); else if (ctrl & NAND_CLE) - writeb(cmd, cs->base + OFFSET_CMD); + writeb(cmd, cs->base + nfc->soc_info->cmd_offset); } static int ingenic_nand_dev_ready(struct nand_chip *chip) @@ -160,8 +164,13 @@ static int ingenic_nand_attach_chip(struct nand_chip *chip) struct ingenic_nfc *nfc = to_ingenic_nfc(chip->controller); int eccbytes; - chip->ecc.bytes = fls((1 + 8) * chip->ecc.size) * - (chip->ecc.strength / 8); + if (chip->ecc.strength == 4) { + /* JZ4740 uses 9 bytes of ECC to correct maximum 4 errors */ + chip->ecc.bytes = 9; + } else { + chip->ecc.bytes = fls((1 + 8) * chip->ecc.size) * + (chip->ecc.strength / 8); + } switch (chip->ecc.mode) { case NAND_ECC_HW: @@ -270,8 +279,8 @@ static int ingenic_nand_init_chip(struct platform_device *pdev, return -ENOMEM; mtd->dev.parent = dev; - chip->legacy.IO_ADDR_R = cs->base + OFFSET_DATA; - chip->legacy.IO_ADDR_W = cs->base + OFFSET_DATA; + chip->legacy.IO_ADDR_R = cs->base + nfc->soc_info->data_offset; + chip->legacy.IO_ADDR_W = cs->base + nfc->soc_info->data_offset; chip->legacy.chip_delay = RB_DELAY_US; chip->options = NAND_NO_SUBPAGE_WRITE; chip->legacy.select_chip = ingenic_nand_select_chip; @@ -353,6 +362,10 @@ static int ingenic_nand_probe(struct platform_device *pdev) if (!nfc) return -ENOMEM; + nfc->soc_info = device_get_match_data(dev); + if (!nfc->soc_info) + return -EINVAL; + /* * Check for ECC HW before we call nand_scan_ident, to prevent us from * having to call it again if the ECC driver returns -EPROBE_DEFER. @@ -390,8 +403,21 @@ static int ingenic_nand_remove(struct platform_device *pdev) return 0; } +static const struct jz_soc_info jz4740_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00008000, + .addr_offset = 0x00010000, +}; + +static const struct jz_soc_info jz4780_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00400000, + .addr_offset = 0x00800000, +}; + static const struct of_device_id ingenic_nand_dt_match[] = { - { .compatible = "ingenic,jz4780-nand" }, + { .compatible = "ingenic,jz4740-nand", .data = &jz4740_soc_info }, + { .compatible = "ingenic,jz4780-nand", .data = &jz4780_soc_info }, {}, }; MODULE_DEVICE_TABLE(of, ingenic_nand_dt_match); diff --git a/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c b/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c new file mode 100644 index 000000000000..13fea645c7f0 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ4740 ECC controller driver + * + * Copyright (c) 2019 Paul Cercueil <paul@crapouillou.net> + * + * based on jz4740-nand.c + */ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#include "ingenic_ecc.h" + +#define JZ_REG_NAND_ECC_CTRL 0x00 +#define JZ_REG_NAND_DATA 0x04 +#define JZ_REG_NAND_PAR0 0x08 +#define JZ_REG_NAND_PAR1 0x0C +#define JZ_REG_NAND_PAR2 0x10 +#define JZ_REG_NAND_IRQ_STAT 0x14 +#define JZ_REG_NAND_IRQ_CTRL 0x18 +#define JZ_REG_NAND_ERR(x) (0x1C + ((x) << 2)) + +#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4) +#define JZ_NAND_ECC_CTRL_ENCODING BIT(3) +#define JZ_NAND_ECC_CTRL_RS BIT(2) +#define JZ_NAND_ECC_CTRL_RESET BIT(1) +#define JZ_NAND_ECC_CTRL_ENABLE BIT(0) + +#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29)) +#define JZ_NAND_STATUS_PAD_FINISH BIT(4) +#define JZ_NAND_STATUS_DEC_FINISH BIT(3) +#define JZ_NAND_STATUS_ENC_FINISH BIT(2) +#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1) +#define JZ_NAND_STATUS_ERROR BIT(0) + +static const uint8_t empty_block_ecc[] = { + 0xcd, 0x9d, 0x90, 0x58, 0xf4, 0x8b, 0xff, 0xb7, 0x6f +}; + +static void jz4740_ecc_reset(struct ingenic_ecc *ecc, bool calc_ecc) +{ + uint32_t reg; + + /* Clear interrupt status */ + writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); + + /* Initialize and enable ECC hardware */ + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_RESET; + reg |= JZ_NAND_ECC_CTRL_ENABLE; + reg |= JZ_NAND_ECC_CTRL_RS; + if (calc_ecc) /* calculate ECC from data */ + reg |= JZ_NAND_ECC_CTRL_ENCODING; + else /* correct data from ECC */ + reg &= ~JZ_NAND_ECC_CTRL_ENCODING; + + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); +} + +static int jz4740_ecc_calculate(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + uint32_t reg, status; + unsigned int timeout = 1000; + int i; + + jz4740_ecc_reset(ecc, true); + + do { + status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout); + + if (timeout == 0) + return -ETIMEDOUT; + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + for (i = 0; i < params->bytes; ++i) + ecc_code[i] = readb(ecc->base + JZ_REG_NAND_PAR0 + i); + + /* + * If the written data is completely 0xff, we also want to write 0xff as + * ECC, otherwise we will get in trouble when doing subpage writes. + */ + if (memcmp(ecc_code, empty_block_ecc, ARRAY_SIZE(empty_block_ecc)) == 0) + memset(ecc_code, 0xff, ARRAY_SIZE(empty_block_ecc)); + + return 0; +} + +static void jz_nand_correct_data(uint8_t *buf, int index, int mask) +{ + int offset = index & 0x7; + uint16_t data; + + index += (index >> 3); + + data = buf[index]; + data |= buf[index + 1] << 8; + + mask ^= (data >> offset) & 0x1ff; + data &= ~(0x1ff << offset); + data |= (mask << offset); + + buf[index] = data & 0xff; + buf[index + 1] = (data >> 8) & 0xff; +} + +static int jz4740_ecc_correct(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code) +{ + int i, error_count, index; + uint32_t reg, status, error; + unsigned int timeout = 1000; + + jz4740_ecc_reset(ecc, false); + + for (i = 0; i < params->bytes; ++i) + writeb(ecc_code[i], ecc->base + JZ_REG_NAND_PAR0 + i); + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_PAR_READY; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + do { + status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout); + + if (timeout == 0) + return -ETIMEDOUT; + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + if (status & JZ_NAND_STATUS_ERROR) { + if (status & JZ_NAND_STATUS_UNCOR_ERROR) + return -EBADMSG; + + error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29; + + for (i = 0; i < error_count; ++i) { + error = readl(ecc->base + JZ_REG_NAND_ERR(i)); + index = ((error >> 16) & 0x1ff) - 1; + if (index >= 0 && index < params->size) + jz_nand_correct_data(buf, index, error & 0x1ff); + } + + return error_count; + } + + return 0; +} + +static void jz4740_ecc_disable(struct ingenic_ecc *ecc) +{ + u32 reg; + + writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); +} + +static const struct ingenic_ecc_ops jz4740_ecc_ops = { + .disable = jz4740_ecc_disable, + .calculate = jz4740_ecc_calculate, + .correct = jz4740_ecc_correct, +}; + +static const struct of_device_id jz4740_ecc_dt_match[] = { + { .compatible = "ingenic,jz4740-ecc", .data = &jz4740_ecc_ops }, + {}, +}; +MODULE_DEVICE_TABLE(of, jz4740_ecc_dt_match); + +static struct platform_driver jz4740_ecc_driver = { + .probe = ingenic_ecc_probe, + .driver = { + .name = "jz4740-ecc", + .of_match_table = jz4740_ecc_dt_match, + }, +}; +module_platform_driver(jz4740_ecc_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_DESCRIPTION("Ingenic JZ4740 ECC controller driver"); +MODULE_LICENSE("GPL v2"); -- 2.11.0 ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/
next prev parent reply other threads:[~2019-03-13 22:23 UTC|newest] Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-03-13 22:22 [PATCH v5 01/12] dt-bindings: mtd: ingenic: Add compatible strings for JZ4740 and JZ4725B Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 02/12] dt-bindings: mtd: ingenic: Change 'BCH' to 'ECC' in documentation Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 03/12] dt-bindings: mtd: ingenic: Use standard ecc-engine property Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-15 23:25 ` Rob Herring 2019-03-15 23:25 ` Rob Herring 2019-03-15 23:25 ` Rob Herring 2019-03-13 22:22 ` [PATCH v5 04/12] mtd: rawnand: Move drivers for Ingenic SoCs to subfolder Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 05/12] mtd: rawnand: ingenic: Use SPDX license notifiers Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 06/12] mtd: rawnand: ingenic: Rename jz4780_nand driver to ingenic_nand Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 07/12] mtd: rawnand: ingenic: Rename jz4780_bch_init to jz4780_bch_reset Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 08/12] mtd: rawnand: ingenic: Separate top-level and SoC specific code Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 09/12] mtd: rawnand: ingenic: Make use of ecc-engine property Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-15 8:40 ` Miquel Raynal 2019-03-15 8:40 ` Miquel Raynal 2019-03-15 14:37 ` Paul Cercueil 2019-03-15 14:37 ` Paul Cercueil 2019-03-18 20:37 ` Paul Cercueil 2019-03-18 20:37 ` Paul Cercueil 2019-03-18 21:39 ` Miquel Raynal 2019-03-18 21:39 ` Miquel Raynal 2019-03-13 22:22 ` Paul Cercueil [this message] 2019-03-13 22:22 ` [PATCH v5 10/12] mtd: rawnand: ingenic: Add support for the JZ4740 Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 11/12] mtd: rawnand: ingenic: Add support for the JZ4725B Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil 2019-03-13 22:22 ` [PATCH v5 12/12] mtd: rawnand: ingenic: Add ooblayout for the Qi Ben Nanonote Paul Cercueil 2019-03-13 22:22 ` Paul Cercueil
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20190313222259.28704-10-paul@crapouillou.net \ --to=paul@crapouillou.net \ --cc=bbrezillon@kernel.org \ --cc=computersforpeace@gmail.com \ --cc=devicetree@vger.kernel.org \ --cc=dwmw2@infradead.org \ --cc=harveyhuntnexus@gmail.com \ --cc=linux-kernel@vger.kernel.org \ --cc=linux-mtd@lists.infradead.org \ --cc=malat@debian.org \ --cc=marek.vasut@gmail.com \ --cc=mark.rutland@arm.com \ --cc=miquel.raynal@bootlin.com \ --cc=od@zcrc.me \ --cc=richard@nod.at \ --cc=robh+dt@kernel.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.