[v3,40/40] mtd: nand: ecc: Add infrastructure to support hardware engines
diff mbox series

Message ID 20190919193141.7865-41-miquel.raynal@bootlin.com
State New
Delegated to: Miquel Raynal
Headers show
Series
  • Introduce the generic ECC engine abstraction
Related show

Commit Message

Miquel Raynal Sept. 19, 2019, 7:31 p.m. UTC
Add the necessary helpers to register/unregister hardware ECC engines
that will be called from ECC engine drivers.

Also add helpers to get the right engine from the user
perspective. Keep a reference on the engine in use to prevent modules
to be unloaded. Put the reference if the engine is retired.

A static list of hardware (only) ECC engines is setup to keep track of
the registered engines.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
 drivers/mtd/nand/core.c  |  10 ++--
 drivers/mtd/nand/ecc.c   | 103 +++++++++++++++++++++++++++++++++++++++
 include/linux/mtd/nand.h |  12 +++++
 3 files changed, 122 insertions(+), 3 deletions(-)

Patch
diff mbox series

diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
index 062a65e131db..55dc1920deae 100644
--- a/drivers/mtd/nand/core.c
+++ b/drivers/mtd/nand/core.c
@@ -232,7 +232,9 @@  static int nanddev_get_ecc_engine(struct nand_device *nand)
 		nand->ecc.engine = nand_ecc_get_ondie_engine(nand);
 		break;
 	case NAND_HW_ECC_ENGINE:
-		pr_err("Hardware ECC engines not supported yet\n");
+		nand->ecc.engine = nand_ecc_get_hw_engine(nand);
+		if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
 		break;
 	default:
 		pr_err("Missing ECC engine provider\n");
@@ -252,7 +254,7 @@  static int nanddev_put_ecc_engine(struct nand_device *nand)
 {
 	switch (nand->ecc.ctx.conf.provider) {
 	case NAND_HW_ECC_ENGINE:
-		pr_err("Hardware ECC engines not supported yet\n");
+		nand_ecc_put_hw_engine(nand);
 		break;
 	case NAND_NO_ECC_ENGINE:
 	case NAND_SOFT_ECC_ENGINE:
@@ -297,7 +299,9 @@  int nanddev_ecc_engine_init(struct nand_device *nand)
 	/* Look for the ECC engine to use */
 	ret = nanddev_get_ecc_engine(nand);
 	if (ret) {
-		pr_err("No ECC engine found\n");
+		if (ret != -EPROBE_DEFER)
+			pr_err("No ECC engine found\n");
+
 		return ret;
 	}
 
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c
index 4f869d33213d..eabf936c5a50 100644
--- a/drivers/mtd/nand/ecc.c
+++ b/drivers/mtd/nand/ecc.c
@@ -96,6 +96,10 @@ 
 
 #include <linux/module.h>
 #include <linux/mtd/nand.h>
+#include <linux/of_platform.h>
+
+static LIST_HEAD(hw_engines);
+static DEFINE_MUTEX(hw_engines_mutex);
 
 int nand_ecc_init_ctx(struct nand_device *nand)
 {
@@ -480,6 +484,39 @@  bool nand_ecc_correction_is_enough(struct nand_device *nand)
 }
 EXPORT_SYMBOL(nand_ecc_correction_is_enough);
 
+int nand_ecc_register_hw_engine(struct nand_ecc_engine *engine)
+{
+	struct nand_ecc_engine *item;
+
+	if (!engine)
+		return -ENOTSUPP;
+
+	/* Prevent multiple registrations of one engine */
+	list_for_each_entry(item, &hw_engines, node)
+		if (item == engine)
+			return 0;
+
+	mutex_lock(&hw_engines_mutex);
+	list_add_tail(&engine->node, &hw_engines);
+	mutex_unlock(&hw_engines_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(nand_ecc_register_hw_engine);
+
+int nand_ecc_unregister_hw_engine(struct nand_ecc_engine *engine)
+{
+	if (!engine)
+		return -ENOTSUPP;
+
+	mutex_lock(&hw_engines_mutex);
+	list_del(&engine->node);
+	mutex_unlock(&hw_engines_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(nand_ecc_unregister_hw_engine);
+
 struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand)
 {
 	unsigned int algo = nand->ecc.user_conf.algo;
@@ -506,6 +543,72 @@  struct nand_ecc_engine *nand_ecc_get_ondie_engine(struct nand_device *nand)
 }
 EXPORT_SYMBOL(nand_ecc_get_ondie_engine);
 
+struct nand_ecc_engine *nand_ecc_match_hw_engine(struct device *dev)
+{
+	struct nand_ecc_engine *item;
+
+	list_for_each_entry(item, &hw_engines, node)
+		if (item->dev == dev)
+			return item;
+
+	return NULL;
+}
+EXPORT_SYMBOL(nand_ecc_match_hw_engine);
+
+struct nand_ecc_engine *nand_ecc_get_hw_engine(struct nand_device *nand)
+{
+	struct nand_ecc_engine *engine = NULL;
+	struct device *dev = &nand->mtd.dev;
+	struct platform_device *pdev;
+	struct device_node *np;
+
+	if (list_empty(&hw_engines))
+		return NULL;
+
+	/* Check for an explicit ecc-engine property in the parent */
+	np = of_parse_phandle(dev->of_node->parent, "ecc-engine", 0);
+	if (np) {
+
+		pdev = of_find_device_by_node(np);
+		if (!pdev)
+			return ERR_PTR(-EPROBE_DEFER);
+
+		engine = nand_ecc_match_hw_engine(&pdev->dev);
+		of_dev_put(pdev);
+		of_node_put(np);
+	}
+
+	/* Support DTs without ecc-engine property: check the parent node */
+	if (!engine) {
+		pdev = of_find_device_by_node(dev->of_node->parent);
+		if (pdev) {
+			engine = nand_ecc_match_hw_engine(&pdev->dev);
+			of_dev_put(pdev);
+		}
+	}
+
+	/* Support no DT or very old DTs: check the node itself */
+	if (!engine) {
+		pdev = of_find_device_by_node(dev->of_node);
+		if (pdev) {
+			engine = nand_ecc_match_hw_engine(&pdev->dev);
+			of_dev_put(pdev);
+		}
+	}
+
+	if (engine)
+		get_device(engine->dev);
+
+	return engine;
+}
+EXPORT_SYMBOL(nand_ecc_get_hw_engine);
+
+void nand_ecc_put_hw_engine(struct nand_device *nand)
+{
+	put_device(nand->ecc.engine->dev);
+}
+EXPORT_SYMBOL(nand_ecc_put_hw_engine);
+
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
 MODULE_DESCRIPTION("Generic ECC engine");
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 5a745f74eb35..673d6c893f01 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -258,10 +258,16 @@  struct nand_ecc_engine_ops {
 
 /**
  * struct nand_ecc_engine - Generic ECC engine abstraction for NAND devices
+ * @dev: Host device
+ * @node: Private field for registration time
  * @ops: ECC engine operations
+ * @priv: Private data
  */
 struct nand_ecc_engine {
+	struct device *dev;
+	struct list_head node;
 	struct nand_ecc_engine_ops *ops;
+	void *priv;
 };
 
 void nand_ecc_read_user_conf(struct nand_device *nand);
@@ -272,8 +278,14 @@  int nand_ecc_prepare_io_req(struct nand_device *nand,
 int nand_ecc_finish_io_req(struct nand_device *nand,
 			   struct nand_page_io_req *req, void *oobbuf);
 bool nand_ecc_correction_is_enough(struct nand_device *nand);
+int nand_ecc_register_hw_engine(struct nand_ecc_engine *engine);
+int nand_ecc_unregister_hw_engine(struct nand_ecc_engine *engine);
 struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand);
 struct nand_ecc_engine *nand_ecc_get_ondie_engine(struct nand_device *nand);
+struct nand_ecc_engine *nand_ecc_get_hw_engine(struct nand_device *nand);
+struct nand_ecc_engine *nand_ecc_match_hw_engine(struct device *dev);
+void nand_ecc_put_hw_engine(struct nand_device *nand);
+
 
 /**
  * struct nand_ecc - High-level ECC object