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@xxxxxxxxxxx> --- 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(-) 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@xxxxxxxxxxx>"); 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 -- 2.20.1 ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/