Commit 96489c1c authored by Miquel Raynal's avatar Miquel Raynal
Browse files

mtd: nand: ecc: Add infrastructure to support hardware engines



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 of the in use ECC engine in order to
prevent modules to be unloaded. Put the reference when the engine gets
retired.

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

Signed-off-by: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20211216111654.238086-13-miquel.raynal@bootlin.com
parent 1d46963d
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -235,7 +235,9 @@ static int nanddev_get_ecc_engine(struct nand_device *nand)
		nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
		break;
	case NAND_ECC_ENGINE_TYPE_ON_HOST:
		pr_err("On-host hardware ECC engines not supported yet\n");
		nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
		if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
			return -EPROBE_DEFER;
		break;
	default:
		pr_err("Missing ECC engine type\n");
@@ -255,7 +257,7 @@ static int nanddev_put_ecc_engine(struct nand_device *nand)
{
	switch (nand->ecc.ctx.conf.engine_type) {
	case NAND_ECC_ENGINE_TYPE_ON_HOST:
		pr_err("On-host hardware ECC engines not supported yet\n");
		nand_ecc_put_on_host_hw_engine(nand);
		break;
	case NAND_ECC_ENGINE_TYPE_NONE:
	case NAND_ECC_ENGINE_TYPE_SOFT:
@@ -300,7 +302,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) {
		if (ret != -EPROBE_DEFER)
			pr_err("No ECC engine found\n");

		return ret;
	}

+88 −0
Original line number Diff line number Diff line
@@ -96,6 +96,12 @@
#include <linux/module.h>
#include <linux/mtd/nand.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>

static LIST_HEAD(on_host_hw_engines);
static DEFINE_MUTEX(on_host_hw_engines_mutex);

/**
 * nand_ecc_init_ctx - Init the ECC engine context
@@ -611,6 +617,88 @@ struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand)
}
EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine);

int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine)
{
	struct nand_ecc_engine *item;

	if (!engine)
		return -EINVAL;

	/* Prevent multiple registrations of one engine */
	list_for_each_entry(item, &on_host_hw_engines, node)
		if (item == engine)
			return 0;

	mutex_lock(&on_host_hw_engines_mutex);
	list_add_tail(&engine->node, &on_host_hw_engines);
	mutex_unlock(&on_host_hw_engines_mutex);

	return 0;
}
EXPORT_SYMBOL(nand_ecc_register_on_host_hw_engine);

int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine)
{
	if (!engine)
		return -EINVAL;

	mutex_lock(&on_host_hw_engines_mutex);
	list_del(&engine->node);
	mutex_unlock(&on_host_hw_engines_mutex);

	return 0;
}
EXPORT_SYMBOL(nand_ecc_unregister_on_host_hw_engine);

static struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *dev)
{
	struct nand_ecc_engine *item;

	list_for_each_entry(item, &on_host_hw_engines, node)
		if (item->dev == dev)
			return item;

	return NULL;
}

struct nand_ecc_engine *nand_ecc_get_on_host_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(&on_host_hw_engines))
		return NULL;

	/* Check for an explicit nand-ecc-engine property */
	np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0);
	if (np) {
		pdev = of_find_device_by_node(np);
		if (!pdev)
			return ERR_PTR(-EPROBE_DEFER);

		engine = nand_ecc_match_on_host_hw_engine(&pdev->dev);
		platform_device_put(pdev);
		of_node_put(np);

		if (!engine)
			return ERR_PTR(-EPROBE_DEFER);
	}

	if (engine)
		get_device(engine->dev);

	return engine;
}
EXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine);

void nand_ecc_put_on_host_hw_engine(struct nand_device *nand)
{
	put_device(nand->ecc.engine->dev);
}
EXPORT_SYMBOL(nand_ecc_put_on_host_hw_engine);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
MODULE_DESCRIPTION("Generic ECC engine");
+28 −0
Original line number Diff line number Diff line
@@ -263,12 +263,36 @@ struct nand_ecc_engine_ops {
			     struct nand_page_io_req *req);
};

/**
 * enum nand_ecc_engine_integration - How the NAND ECC engine is integrated
 * @NAND_ECC_ENGINE_INTEGRATION_INVALID: Invalid value
 * @NAND_ECC_ENGINE_INTEGRATION_PIPELINED: Pipelined engine, performs on-the-fly
 *                                         correction, does not need to copy
 *                                         data around
 * @NAND_ECC_ENGINE_INTEGRATION_EXTERNAL: External engine, needs to bring the
 *                                        data into its own area before use
 */
enum nand_ecc_engine_integration {
	NAND_ECC_ENGINE_INTEGRATION_INVALID,
	NAND_ECC_ENGINE_INTEGRATION_PIPELINED,
	NAND_ECC_ENGINE_INTEGRATION_EXTERNAL,
};

/**
 * struct nand_ecc_engine - ECC engine abstraction for NAND devices
 * @dev: Host device
 * @node: Private field for registration time
 * @ops: ECC engine operations
 * @integration: How the engine is integrated with the host
 *               (only relevant on %NAND_ECC_ENGINE_TYPE_ON_HOST engines)
 * @priv: Private data
 */
struct nand_ecc_engine {
	struct device *dev;
	struct list_head node;
	struct nand_ecc_engine_ops *ops;
	enum nand_ecc_engine_integration integration;
	void *priv;
};

void of_get_nand_ecc_user_config(struct nand_device *nand);
@@ -279,8 +303,12 @@ 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);
bool nand_ecc_is_strong_enough(struct nand_device *nand);
int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine);
int nand_ecc_unregister_on_host_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_on_die_hw_engine(struct nand_device *nand);
struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand);
void nand_ecc_put_on_host_hw_engine(struct nand_device *nand);

#if IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING)
struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void);