@@ -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;
}
@@ -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");
@@ -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
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(-)