[RFC PATCH 1/5] mtd: nand: ecc-qcom: Add support for ECC Engine Driver

From: Md Sadre Alam
Date: Tue Oct 31 2023 - 08:04:10 EST


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