Signed-off-by: Md Sadre Alam <quic_mdalam@xxxxxxxxxxx> Signed-off-by: Sricharan R <quic_srichara@xxxxxxxxxxx> --- drivers/mtd/nand/Kconfig | 7 ++ drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/ecc-qcom.c | 198 ++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 drivers/mtd/nand/ecc-qcom.c diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 5b0c2c95f10c..333cec8187c8 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -61,6 +61,13 @@ config MTD_NAND_ECC_MEDIATEK help This enables support for the hardware ECC engine from Mediatek. +config MTD_NAND_ECC_QCOM + tristate "Qualcomm hardware ECC engine" + depends on ARCH_QCOM + select MTD_NAND_ECC + help + This enables support for the hardware ECC engine from Qualcomm. + endmenu endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 19e1291ac4d5..c73b8a3456ec 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -3,6 +3,7 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o +obj-$(CONFIG_MTD_NAND_ECC_QCOM) += ecc-qcom.o qpic_common.o obj-y += onenand/ obj-y += raw/ diff --git a/drivers/mtd/nand/ecc-qcom.c b/drivers/mtd/nand/ecc-qcom.c new file mode 100644 index 000000000000..a85423ed368a --- /dev/null +++ b/drivers/mtd/nand/ecc-qcom.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * QCOM ECC Engine Driver. + * Copyright (C) 2023 Qualcomm Inc. + * Authors: Md sadre Alam <quic_mdalam@xxxxxxxxxxx> + * Sricharan R <quic_srichara@xxxxxxxxxxx> + */ + +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/iopoll.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/mutex.h> +#include <linux/mtd/nand-qpic-common.h> + + + +/* ECC modes supported by the controller */ +#define ECC_NONE BIT(0) +#define ECC_RS_4BIT BIT(1) +#define ECC_BCH_4BIT BIT(2) +#define ECC_BCH_8BIT BIT(3) + +struct qpic_ecc_caps { + u32 err_mask; + u32 err_shift; + const u8 *ecc_strength; + const u32 *ecc_regs; + u8 num_ecc_strength; + u8 ecc_mode_shift; + u32 parity_bits; + int pg_irq_sel; +}; + + +struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip) +{ + return container_of(chip, struct qcom_nand_host, chip); +} +EXPORT_SYMBOL(to_qcom_nand_host); + +struct qcom_nand_controller * +get_qcom_nand_controller(struct nand_chip *chip) +{ + return container_of(chip->controller, struct qcom_nand_controller, + controller); +} +EXPORT_SYMBOL(get_qcom_nand_controller); + +static struct qpic_ecc *qpic_ecc_get(struct device_node *np) +{ + struct platform_device *pdev; + struct qpic_ecc *ecc; + + pdev = of_find_device_by_node(np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + + ecc = platform_get_drvdata(pdev); + if (!ecc) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + return ecc; +} + +struct qpic_ecc *of_qpic_ecc_get(struct device_node *of_node) +{ + struct qpic_ecc *ecc = NULL; + struct device_node *np; + + np = of_parse_phandle(of_node, "nand-ecc-engine", 0); + /* for backward compatibility */ + if (!np) + np = of_parse_phandle(of_node, "ecc-engine", 0); + if (np) { + ecc = qpic_ecc_get(np); + of_node_put(np); + } + + return ecc; +} +EXPORT_SYMBOL(of_qpic_ecc_get); + +int qcom_ecc_config(struct qpic_ecc *ecc, int ecc_strength, + bool wide_bus) +{ + ecc->ecc_modes = (ECC_RS_4BIT | ECC_BCH_8BIT); + + if (ecc_strength >= 8) { + /* 8 bit ECC defaults to BCH ECC on all platforms */ + ecc->bch_enabled = true; + ecc->ecc_mode = 1; + + if (wide_bus) { + ecc->ecc_bytes_hw = 14; + ecc->spare_bytes = 0; + ecc->bbm_size = 2; + } else { + ecc->ecc_bytes_hw = 13; + ecc->spare_bytes = 2; + ecc->bbm_size = 1; + } + } else { + /* + * if the controller supports BCH for 4 bit ECC, the controller + * uses lesser bytes for ECC. If RS is used, the ECC bytes is + * always 10 bytes + */ + if (ecc->ecc_modes & ECC_BCH_4BIT) { + /* BCH */ + ecc->bch_enabled = true; + ecc->ecc_mode = 0; + if (wide_bus) { + ecc->ecc_bytes_hw = 8; + ecc->spare_bytes = 2; + ecc->bbm_size = 2; + } else { + ecc->ecc_bytes_hw = 7; + ecc->spare_bytes = 4; + ecc->bbm_size = 1; + } + } else { + /* RS */ + ecc->ecc_bytes_hw = 10; + if (wide_bus) { + ecc->spare_bytes = 0; + ecc->bbm_size = 2; + } else { + ecc->spare_bytes = 1; + ecc->bbm_size = 1; + } + } + } + + return 0; +} +EXPORT_SYMBOL(qcom_ecc_config); + +void qcom_ecc_enable(struct qcom_ecc *ecc) +{ + ecc->use_ecc = true; +} +EXPORT_SYMBOL(qcom_ecc_enable); + +void qcom_ecc_disable(struct qcom_ecc *ecc) +{ + ecc->use_ecc = false; +} +EXPORT_SYMBOL(qcom_ecc_disable); + +static const struct of_device_id qpic_ecc_dt_match[] = { + { + .compatible = "qcom,ipq9574-ecc", + }, + {}, +}; + +static int qpic_ecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qpic_ecc *ecc; + u32 max_eccdata_size; + + ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); + if (!ecc) + return -ENOMEM; + + ecc->caps = of_device_get_match_data(dev); + + ecc->dev = dev; + platform_set_drvdata(pdev, ecc); + dev_info(dev, "probed\n"); + + return 0; +} + + +MODULE_DEVICE_TABLE(of, qpic_ecc_dt_match); + +static struct platform_driver qpic_ecc_driver = { + .probe = qpic_ecc_probe, + .driver = { + .name = "qpic-ecc", + .of_match_table = qpic_ecc_dt_match, + }, +}; + +module_platform_driver(qpic_ecc_driver); + +MODULE_AUTHOR("Md Sadre Alam <quic_mdalam@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("QPIC Nand ECC Driver"); +MODULE_LICENSE("Dual MIT/GPL"); -- 2.34.1