[RFC,v4,2/5] mtd: nand: ecc: mtk: Convert to the ECC infrastructure

Miquel Raynal miquel.raynal at bootlin.com
Thu Dec 9 02:32:09 PST 2021


Hi Xiangsheng,

xiangsheng.hou at mediatek.com wrote on Tue, 30 Nov 2021 16:31:59 +0800:

> Convert the Mediatek HW ECC engine to the ECC infrastructure with
> pipelined case.
> 
> Signed-off-by: Xiangsheng Hou <xiangsheng.hou at mediatek.com>
> ---
>  drivers/mtd/nand/ecc-mtk.c       | 614 +++++++++++++++++++++++++++++++
>  include/linux/mtd/nand-ecc-mtk.h |  68 ++++
>  2 files changed, 682 insertions(+)
> 
> diff --git a/drivers/mtd/nand/ecc-mtk.c b/drivers/mtd/nand/ecc-mtk.c
> index 31d7c77d5c59..c44499b3d0a5 100644
> --- a/drivers/mtd/nand/ecc-mtk.c
> +++ b/drivers/mtd/nand/ecc-mtk.c
> @@ -16,6 +16,7 @@
>  #include <linux/of_platform.h>
>  #include <linux/mutex.h>
>  
> +#include <linux/mtd/nand.h>
>  #include <linux/mtd/nand-ecc-mtk.h>
>  
>  #define ECC_IDLE_MASK		BIT(0)
> @@ -41,11 +42,17 @@
>  #define ECC_IDLE_REG(op)	((op) == ECC_ENCODE ? ECC_ENCIDLE : ECC_DECIDLE)
>  #define ECC_CTL_REG(op)		((op) == ECC_ENCODE ? ECC_ENCCON : ECC_DECCON)
>  
> +#define OOB_FREE_MAX_SIZE 8
> +#define OOB_FREE_MIN_SIZE 1
> +
>  struct mtk_ecc_caps {
>  	u32 err_mask;
>  	const u8 *ecc_strength;
>  	const u32 *ecc_regs;
>  	u8 num_ecc_strength;
> +	const u8 *spare_size;
> +	u8 num_spare_size;
> +	u32 max_section_size;
>  	u8 ecc_mode_shift;
>  	u32 parity_bits;
>  	int pg_irq_sel;
> @@ -79,6 +86,12 @@ static const u8 ecc_strength_mt7622[] = {
>  	4, 6, 8, 10, 12, 14, 16
>  };
>  
> +/* spare size for each section that each IP supports */
> +static const u8 spare_size_mt7622[] = {
> +	16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51,
> +	52, 62, 61, 63, 64, 67, 74
> +};
> +
>  enum mtk_ecc_regs {
>  	ECC_ENCPAR00,
>  	ECC_ENCIRQ_EN,
> @@ -447,6 +460,604 @@ unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc)
>  }
>  EXPORT_SYMBOL(mtk_ecc_get_parity_bits);
>  
> +static inline int mtk_ecc_data_off(struct nand_device *nand, int i)
> +{
> +	int eccsize = nand->ecc.ctx.conf.step_size;
> +
> +	return i * eccsize;
> +}
> +
> +static inline int mtk_ecc_oob_free_position(struct nand_device *nand, int i)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	int position;
> +
> +	if (i < eng->bbm_ctl.section)
> +		position = (i + 1) * eng->oob_free;
> +	else if (i == eng->bbm_ctl.section)
> +		position = 0;
> +	else
> +		position = i * eng->oob_free;
> +
> +	return position;
> +}
> +
> +static inline int mtk_ecc_data_len(struct nand_device *nand)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	int eccsize = nand->ecc.ctx.conf.step_size;
> +	int eccbytes = eng->oob_ecc;
> +
> +	return eccsize + eng->oob_free + eccbytes;
> +}
> +
> +static inline u8 *mtk_ecc_section_ptr(struct nand_device *nand,  int i)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +
> +	return eng->bounce_page_buf + i * mtk_ecc_data_len(nand);
> +}
> +
> +static inline u8 *mtk_ecc_oob_free_ptr(struct nand_device *nand, int i)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	int eccsize = nand->ecc.ctx.conf.step_size;
> +
> +	return eng->bounce_page_buf + i * mtk_ecc_data_len(nand) + eccsize;
> +}
> +
> +static void mtk_ecc_no_bbm_swap(struct nand_device *a, u8 *b, u8 *c)
> +{
> +	/* nop */

Is this really useful?

> +}
> +
> +static void mtk_ecc_bbm_swap(struct nand_device *nand, u8 *databuf, u8 *oobbuf)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	int step_size = nand->ecc.ctx.conf.step_size;
> +	u32 bbm_pos = eng->bbm_ctl.position;
> +
> +	bbm_pos += eng->bbm_ctl.section * step_size;
> +
> +	swap(oobbuf[0], databuf[bbm_pos]);
> +}
> +
> +static void mtk_ecc_set_bbm_ctl(struct mtk_ecc_bbm_ctl *bbm_ctl,
> +				struct nand_device *nand)
> +{
> +	if (nanddev_page_size(nand) == 512) {
> +		bbm_ctl->bbm_swap = mtk_ecc_no_bbm_swap;
> +	} else {
> +		bbm_ctl->bbm_swap = mtk_ecc_bbm_swap;
> +		bbm_ctl->section = nanddev_page_size(nand) /
> +				   mtk_ecc_data_len(nand);
> +		bbm_ctl->position = nanddev_page_size(nand) %
> +				    mtk_ecc_data_len(nand);
> +	}
> +}
> +
> +static int mtk_ecc_ooblayout_free(struct mtd_info *mtd, int section,
> +				  struct mtd_oob_region *oob_region)
> +{
> +	struct nand_device *nand = mtd_to_nanddev(mtd);
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
> +	u32 eccsteps, bbm_bytes = 0;
> +
> +	eccsteps = mtd->writesize / conf->step_size;
> +
> +	if (section >= eccsteps)
> +		return -ERANGE;
> +
> +	/* Reserve 1 byte for BBM only for section 0 */
> +	if (section == 0)
> +		bbm_bytes = 1;
> +
> +	oob_region->length = eng->oob_free - bbm_bytes;
> +	oob_region->offset = section * eng->oob_free + bbm_bytes;
> +
> +	return 0;
> +}
> +
> +static int mtk_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
> +				 struct mtd_oob_region *oob_region)
> +{
> +	struct nand_device *nand = mtd_to_nanddev(mtd);
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +
> +	if (section)
> +		return -ERANGE;
> +
> +	oob_region->offset = eng->oob_free * eng->nsteps;
> +	oob_region->length = mtd->oobsize - oob_region->offset;
> +
> +	return 0;
> +}
> +
> +static const struct mtd_ooblayout_ops mtk_ecc_ooblayout_ops = {
> +	.free = mtk_ecc_ooblayout_free,
> +	.ecc = mtk_ecc_ooblayout_ecc,
> +};
> +
> +const struct mtd_ooblayout_ops *mtk_ecc_get_ooblayout(void)
> +{
> +	return &mtk_ecc_ooblayout_ops;
> +}
> +
> +static struct device *mtk_ecc_get_engine_dev(struct device *dev)
> +{
> +	struct platform_device *eccpdev;
> +	struct device_node *np;
> +
> +	/*
> +	 * The device node is only the host controller,
> +	 * not the actual ECC engine when pipelined case.
> +	 */
> +	np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0);
> +	if (!np)
> +		return NULL;
> +
> +	eccpdev = of_find_device_by_node(np);
> +	if (!eccpdev) {
> +		of_node_put(np);
> +		return NULL;
> +	}
> +
> +	platform_device_put(eccpdev);
> +	of_node_put(np);
> +
> +	return &eccpdev->dev;
> +}

As this will be the exact same function for all the pipelined engines,
I am tempted to put this in the core. I'll soon send a iteration, stay
tuned.

> +/*
> + * mtk_ecc_data_format() - Convert to/from MTK ECC on-flash data format
> + *
> + * MTK ECC engine organize page data by section, the on-flash format as bellow:
> + * ||          section 0         ||          section 1          || ...
> + * || data | OOB free | OOB ECC || data || OOB free | OOB ECC || ...
> + *
> + * Terefore, it`s necessary to convert data when reading/writing in raw mode.
> + */
> +static void mtk_ecc_data_format(struct nand_device *nand,

mtk_ecc_reorganize_data_layout()?

> +				struct nand_page_io_req *req)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	int step_size = nand->ecc.ctx.conf.step_size;
> +	void *databuf, *oobbuf;
> +	int i;
> +
> +	if (req->type == NAND_PAGE_WRITE) {
> +		databuf = (void *)req->databuf.out;
> +		oobbuf = (void *)req->oobbuf.out;
> +
> +		/*
> +		 * Convert the source databuf and oobbuf to MTK ECC
> +		 * on-flash data format.
> +		 */
> +		for (i = 0; i < eng->nsteps; i++) {
> +			if (i == eng->bbm_ctl.section)
> +				eng->bbm_ctl.bbm_swap(nand,
> +						      databuf, oobbuf);

Do you really need this swap? Isn't the overall move enough to put the
BBM at the right place?

> +			memcpy(mtk_ecc_section_ptr(nand, i),
> +			       databuf + mtk_ecc_data_off(nand, i),
> +			       step_size);
> +
> +			memcpy(mtk_ecc_oob_free_ptr(nand, i),
> +			       oobbuf + mtk_ecc_oob_free_position(nand, i),
> +			       eng->oob_free);
> +
> +			memcpy(mtk_ecc_oob_free_ptr(nand, i) + eng->oob_free,
> +			       oobbuf + eng->oob_free * eng->nsteps +
> +			       i * eng->oob_ecc,
> +			       eng->oob_ecc);
> +		}
> +
> +		req->databuf.out = eng->bounce_page_buf;
> +		req->oobbuf.out = eng->bounce_oob_buf;
> +	} else {
> +		databuf = req->databuf.in;
> +		oobbuf = req->oobbuf.in;
> +
> +		/*
> +		 * Convert the on-flash MTK ECC data format to
> +		 * destination databuf and oobbuf.
> +		 */
> +		memcpy(eng->bounce_page_buf, databuf,
> +		       nanddev_page_size(nand));
> +		memcpy(eng->bounce_oob_buf, oobbuf,
> +		       nanddev_per_page_oobsize(nand));
> +
> +		for (i = 0; i < eng->nsteps; i++) {
> +			memcpy(databuf + mtk_ecc_data_off(nand, i),
> +			       mtk_ecc_section_ptr(nand, i), step_size);
> +
> +			memcpy(oobbuf + mtk_ecc_oob_free_position(nand, i),
> +			       mtk_ecc_section_ptr(nand, i) + step_size,
> +			       eng->oob_free);
> +
> +			memcpy(oobbuf + eng->oob_free * eng->nsteps +
> +			       i * eng->oob_ecc,
> +			       mtk_ecc_section_ptr(nand, i) + step_size
> +			       + eng->oob_free,
> +			       eng->oob_ecc);
> +
> +			if (i == eng->bbm_ctl.section)
> +				eng->bbm_ctl.bbm_swap(nand,
> +						      databuf, oobbuf);
> +		}
> +	}
> +}
> +
> +static void mtk_ecc_oob_free_shift(struct nand_device *nand,
> +				   u8 *dst_buf, u8 *src_buf, bool write)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	u32 position;
> +	int i;
> +
> +	for (i = 0; i < eng->nsteps; i++) {
> +		if (i < eng->bbm_ctl.section)
> +			position = (i + 1) * eng->oob_free;
> +		else if (i == eng->bbm_ctl.section)
> +			position = 0;
> +		else
> +			position = i * eng->oob_free;
> +
> +		if (write)
> +			memcpy(dst_buf + i * eng->oob_free, src_buf + position,
> +			       eng->oob_free);
> +		else
> +			memcpy(dst_buf + position, src_buf + i * eng->oob_free,
> +			       eng->oob_free);
> +	}
> +}
> +
> +static void mtk_ecc_set_section_size_and_strength(struct nand_device *nand)
> +{
> +	struct nand_ecc_props *reqs = &nand->ecc.requirements;
> +	struct nand_ecc_props *user = &nand->ecc.user_conf;
> +	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +
> +	/* Configure the correction depending on the NAND device topology */
> +	if (user->step_size && user->strength) {
> +		conf->step_size = user->step_size;
> +		conf->strength = user->strength;
> +	} else if (reqs->step_size && reqs->strength) {
> +		conf->step_size = reqs->step_size;
> +		conf->strength = reqs->strength;
> +	}
> +
> +	/*
> +	 * Align ECC strength and ECC size.
> +	 * The MTK HW ECC engine only support 512 and 1024 ECC size.
> +	 */
> +	if (conf->step_size < 1024) {

I prefer stronger checks than '<'.

> +		if (nanddev_page_size(nand) > 512 &&
> +		    eng->ecc->caps->max_section_size > 512) {
> +			conf->step_size = 1024;
> +			conf->strength <<= 1;

the operation "<<= 1" is more readable as "* 2" IMHO.

Same below in both directions.

> +		} else {
> +			conf->step_size = 512;
> +		}
> +	} else {
> +		conf->step_size = 1024;
> +	}
> +
> +	eng->section_size = conf->step_size;
> +}
> +
> +static int mtk_ecc_set_spare_per_section(struct nand_device *nand)
> +{
> +	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	const u8 *spare = eng->ecc->caps->spare_size;
> +	u32 i, closest_spare = 0;
> +
> +	eng->nsteps = nanddev_page_size(nand) / conf->step_size;
> +	eng->oob_per_section = nanddev_per_page_oobsize(nand) / eng->nsteps;
> +
> +	if (conf->step_size == 1024)
> +		eng->oob_per_section >>= 1;
> +
> +	if (eng->oob_per_section < spare[0]) {
> +		dev_err(eng->ecc->dev, "OOB size per section too small %d\n",
> +			eng->oob_per_section);
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < eng->ecc->caps->num_spare_size; i++) {
> +		if (eng->oob_per_section >= spare[i] &&
> +		    spare[i] >= spare[closest_spare]) {
> +			closest_spare = i;
> +			if (eng->oob_per_section == spare[i])
> +				break;
> +		}
> +	}
> +
> +	eng->oob_per_section = spare[closest_spare];
> +	eng->oob_per_section_idx = closest_spare;
> +
> +	if (conf->step_size == 1024)
> +		eng->oob_per_section <<= 1;
> +
> +	return 0;
> +}
> +
> +int mtk_ecc_prepare_io_req_pipelined(struct nand_device *nand,
> +				     struct nand_page_io_req *req)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	struct mtd_info *mtd = nanddev_to_mtd(nand);
> +	int ret;
> +
> +	nand_ecc_tweak_req(&eng->req_ctx, req);
> +
> +	/* Store the source buffer data to avoid modify source data */
> +	if (req->type == NAND_PAGE_WRITE) {
> +		if (req->datalen)
> +			memcpy(eng->src_page_buf + req->dataoffs,
> +			       req->databuf.out,
> +			       req->datalen);
> +
> +		if (req->ooblen)
> +			memcpy(eng->src_oob_buf + req->ooboffs,
> +			       req->oobbuf.out,
> +			       req->ooblen);
> +	}
> +
> +	if (req->mode == MTD_OPS_RAW) {
> +		if (req->type == NAND_PAGE_WRITE)
> +			mtk_ecc_data_format(nand, req);
> +
> +		return 0;
> +	}
> +
> +	eng->ecc_cfg.mode = ECC_NFI_MODE;
> +	eng->ecc_cfg.sectors = eng->nsteps;
> +	eng->ecc_cfg.op = ECC_DECODE;
> +
> +	if (req->type == NAND_PAGE_READ)
> +		return mtk_ecc_enable(eng->ecc, &eng->ecc_cfg);
> +
> +	memset(eng->bounce_oob_buf, 0xff, nanddev_per_page_oobsize(nand));
> +	if (req->ooblen) {
> +		if (req->mode == MTD_OPS_AUTO_OOB) {
> +			ret = mtd_ooblayout_set_databytes(mtd,
> +							  req->oobbuf.out,
> +							  eng->bounce_oob_buf,
> +							  req->ooboffs,
> +							  mtd->oobavail);
> +			if (ret)
> +				return ret;
> +		} else {
> +			memcpy(eng->bounce_oob_buf + req->ooboffs,
> +			       req->oobbuf.out,
> +			       req->ooblen);
> +		}
> +	}
> +
> +	eng->bbm_ctl.bbm_swap(nand, (void *)req->databuf.out,
> +			      eng->bounce_oob_buf);
> +	mtk_ecc_oob_free_shift(nand, (void *)req->oobbuf.out,
> +			       eng->bounce_oob_buf, true);
> +
> +	eng->ecc_cfg.op = ECC_ENCODE;
> +
> +	return mtk_ecc_enable(eng->ecc, &eng->ecc_cfg);
> +}
> +
> +int mtk_ecc_finish_io_req_pipelined(struct nand_device *nand,
> +				    struct nand_page_io_req *req)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +	struct mtd_info *mtd = nanddev_to_mtd(nand);
> +	struct mtk_ecc_stats stats;
> +	int ret;
> +
> +	if (req->type == NAND_PAGE_WRITE) {
> +		/* Restore the source buffer data */
> +		if (req->datalen)
> +			memcpy((void *)req->databuf.out,
> +			       eng->src_page_buf + req->dataoffs,
> +			       req->datalen);
> +
> +		if (req->ooblen)
> +			memcpy((void *)req->oobbuf.out,
> +			       eng->src_oob_buf + req->ooboffs,
> +			       req->ooblen);
> +
> +		if (req->mode != MTD_OPS_RAW)
> +			mtk_ecc_disable(eng->ecc);
> +
> +		nand_ecc_restore_req(&eng->req_ctx, req);
> +
> +		return 0;
> +	}
> +
> +	if (req->mode == MTD_OPS_RAW) {
> +		mtk_ecc_data_format(nand, req);
> +		nand_ecc_restore_req(&eng->req_ctx, req);
> +
> +		return 0;
> +	}
> +
> +	ret = mtk_ecc_wait_done(eng->ecc, ECC_DECODE);
> +	if (ret) {
> +		ret = -ETIMEDOUT;
> +		goto out;
> +	}
> +
> +	if (eng->read_empty) {
> +		memset(req->databuf.in, 0xff, nanddev_page_size(nand));
> +		memset(req->oobbuf.in, 0xff, nanddev_per_page_oobsize(nand));
> +		ret = 0;
> +
> +		goto out;
> +	}
> +
> +	mtk_ecc_get_stats(eng->ecc, &stats, eng->nsteps);
> +	mtd->ecc_stats.corrected += stats.corrected;
> +	mtd->ecc_stats.failed += stats.failed;
> +
> +	/*
> +	 * Return -EBADMSG when exit uncorrect ECC error.
> +	 * Otherwise, return the bitflips.
> +	 */
> +	if (stats.failed)
> +		ret = -EBADMSG;
> +	else
> +		ret = stats.bitflips;
> +
> +	memset(eng->bounce_oob_buf, 0xff, nanddev_per_page_oobsize(nand));
> +	mtk_ecc_oob_free_shift(nand, eng->bounce_oob_buf, req->oobbuf.in, false);
> +	eng->bbm_ctl.bbm_swap(nand, req->databuf.in, eng->bounce_oob_buf);
> +
> +	if (req->ooblen) {
> +		if (req->mode == MTD_OPS_AUTO_OOB)
> +			ret = mtd_ooblayout_get_databytes(mtd,
> +							  req->oobbuf.in,
> +							  eng->bounce_oob_buf,
> +							  req->ooboffs,
> +							  mtd->oobavail);
> +		else
> +			memcpy(req->oobbuf.in,
> +			       eng->bounce_oob_buf + req->ooboffs,
> +			       req->ooblen);
> +	}
> +
> +out:
> +	mtk_ecc_disable(eng->ecc);
> +	nand_ecc_restore_req(&eng->req_ctx, req);
> +
> +	return ret;
> +}
> +
> +int mtk_ecc_init_ctx_pipelined(struct nand_device *nand)
> +{
> +	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
> +	struct mtd_info *mtd = nanddev_to_mtd(nand);
> +	struct mtk_ecc_engine *eng;
> +	struct device *dev;
> +	int free, ret;
> +
> +	/*
> +	 * In the case of a pipelined engine, the device registering the ECC
> +	 * engine is not the actual ECC engine device but the host controller.
> +	 */
> +	dev = mtk_ecc_get_engine_dev(nand->ecc.engine->dev);
> +	if (!dev)
> +		return -EINVAL;
> +
> +	eng = devm_kzalloc(dev, sizeof(*eng), GFP_KERNEL);
> +	if (!eng)
> +		return -ENOMEM;
> +
> +	nand->ecc.ctx.priv = eng;
> +	nand->ecc.engine->priv = eng;
> +
> +	eng->ecc = dev_get_drvdata(dev);
> +
> +	mtk_ecc_set_section_size_and_strength(nand);
> +
> +	ret = mtk_ecc_set_spare_per_section(nand);
> +	if (ret)
> +		return ret;
> +
> +	clk_prepare_enable(eng->ecc->clk);
> +	mtk_ecc_hw_init(eng->ecc);
> +
> +	/* Calculate OOB free bytes except ECC parity data */
> +	free = (conf->strength * mtk_ecc_get_parity_bits(eng->ecc)
> +	       + 7) >> 3;
> +	free = eng->oob_per_section - free;
> +
> +	/*
> +	 * Enhance ECC strength if OOB left is bigger than max FDM size
> +	 * or reduce ECC strength if OOB size is not enough for ECC
> +	 * parity data.
> +	 */
> +	if (free > OOB_FREE_MAX_SIZE)
> +		eng->oob_ecc = eng->oob_per_section - OOB_FREE_MAX_SIZE;
> +	else if (free < 0)
> +		eng->oob_ecc = eng->oob_per_section - OOB_FREE_MIN_SIZE;
> +
> +	/* Calculate and adjust ECC strenth based on OOB ECC bytes */
> +	conf->strength = (eng->oob_ecc << 3) /
> +			 mtk_ecc_get_parity_bits(eng->ecc);
> +	mtk_ecc_adjust_strength(eng->ecc, &conf->strength);
> +
> +	eng->oob_ecc = DIV_ROUND_UP(conf->strength *
> +		       mtk_ecc_get_parity_bits(eng->ecc), 8);
> +
> +	eng->oob_free = eng->oob_per_section - eng->oob_ecc;
> +	if (eng->oob_free > OOB_FREE_MAX_SIZE)
> +		eng->oob_free = OOB_FREE_MAX_SIZE;
> +
> +	eng->oob_free_protected = OOB_FREE_MIN_SIZE;
> +
> +	eng->oob_ecc = eng->oob_per_section - eng->oob_free;
> +
> +	if (!mtd->ooblayout)
> +		mtd_set_ooblayout(mtd, mtk_ecc_get_ooblayout());
> +
> +	ret = nand_ecc_init_req_tweaking(&eng->req_ctx, nand);
> +	if (ret)
> +		return ret;
> +
> +	eng->src_page_buf = kmalloc(nanddev_page_size(nand) +
> +			    nanddev_per_page_oobsize(nand), GFP_KERNEL);
> +	eng->bounce_page_buf = kmalloc(nanddev_page_size(nand) +
> +			       nanddev_per_page_oobsize(nand), GFP_KERNEL);
> +	if (!eng->src_page_buf || !eng->bounce_page_buf) {
> +		ret = -ENOMEM;
> +		goto cleanup_req_tweak;
> +	}
> +
> +	eng->src_oob_buf = eng->src_page_buf + nanddev_page_size(nand);
> +	eng->bounce_oob_buf = eng->bounce_page_buf + nanddev_page_size(nand);
> +
> +	mtk_ecc_set_bbm_ctl(&eng->bbm_ctl, nand);
> +	eng->ecc_cfg.strength = conf->strength;
> +	eng->ecc_cfg.len = conf->step_size + eng->oob_free_protected;
> +	mtd->bitflip_threshold = conf->strength;
> +
> +	return 0;
> +
> +cleanup_req_tweak:
> +	nand_ecc_cleanup_req_tweaking(&eng->req_ctx);
> +
> +	return ret;
> +}
> +
> +void mtk_ecc_cleanup_ctx_pipelined(struct nand_device *nand)
> +{
> +	struct mtk_ecc_engine *eng = nand_to_ecc_ctx(nand);
> +
> +	if (eng) {
> +		nand_ecc_cleanup_req_tweaking(&eng->req_ctx);
> +		kfree(eng->src_page_buf);
> +		kfree(eng->bounce_page_buf);
> +	}
> +}
> +
> +/*
> + * The MTK ECC engine work at pipelined situation,
> + * will be registered by the drivers that wrap it.
> + */
> +static struct nand_ecc_engine_ops mtk_ecc_engine_pipelined_ops = {
> +	.init_ctx = mtk_ecc_init_ctx_pipelined,
> +	.cleanup_ctx = mtk_ecc_cleanup_ctx_pipelined,
> +	.prepare_io_req = mtk_ecc_prepare_io_req_pipelined,
> +	.finish_io_req = mtk_ecc_finish_io_req_pipelined,
> +};
> +
> +struct nand_ecc_engine_ops *mtk_ecc_get_pipelined_ops(void)
> +{
> +	return &mtk_ecc_engine_pipelined_ops;
> +}
> +EXPORT_SYMBOL(mtk_ecc_get_pipelined_ops);
> +
>  static const struct mtk_ecc_caps mtk_ecc_caps_mt2701 = {
>  	.err_mask = 0x3f,
>  	.ecc_strength = ecc_strength_mt2701,
> @@ -472,6 +1083,9 @@ static const struct mtk_ecc_caps mtk_ecc_caps_mt7622 = {
>  	.ecc_strength = ecc_strength_mt7622,
>  	.ecc_regs = mt7622_ecc_regs,
>  	.num_ecc_strength = 7,
> +	.spare_size = spare_size_mt7622,
> +	.num_spare_size = 19,
> +	.max_section_size = 1024,
>  	.ecc_mode_shift = 4,
>  	.parity_bits = 13,
>  	.pg_irq_sel = 0,
> diff --git a/include/linux/mtd/nand-ecc-mtk.h b/include/linux/mtd/nand-ecc-mtk.h
> index 0e48c36e6ca0..6d550032cbd9 100644
> --- a/include/linux/mtd/nand-ecc-mtk.h
> +++ b/include/linux/mtd/nand-ecc-mtk.h
> @@ -33,6 +33,61 @@ struct mtk_ecc_config {
>  	u32 len;
>  };
>  
> +/**
> + * struct mtk_ecc_bbm_ctl - Information relative to the BBM swap
> + * @bbm_swap: BBM swap function
> + * @section: Section number in data area for swap
> + * @position: Position in @section for swap with BBM
> + */
> +struct mtk_ecc_bbm_ctl {
> +	void (*bbm_swap)(struct nand_device *nand, u8 *databuf, u8 *oobbuf);
> +	u32 section;
> +	u32 position;
> +};
> +
> +/**
> + * struct mtk_ecc_engine - Information relative to the ECC
> + * @req_ctx: Save request context and tweak the original request to fit the
> + *           engine needs
> + * @oob_per_section: OOB size for each section to store OOB free/ECC bytes
> + * @oob_per_section_idx: The index for @oob_per_section in spare size array
> + * @oob_ecc: OOB size for each section to store the ECC parity
> + * @oob_free: OOB size for each section to store the OOB free bytes
> + * @oob_free_protected: OOB free bytes will be protected by the ECC engine
> + * @section_size: The size of each section
> + * @read_empty: Indicate whether empty page for one read operation
> + * @nsteps: The number of the sections
> + * @src_page_buf: Buffer used to store source data buffer when write
> + * @src_oob_buf: Buffer used to store source OOB buffer when write
> + * @bounce_page_buf: Data bounce buffer
> + * @bounce_oob_buf: OOB bounce buffer
> + * @ecc: The ECC engine private data structure
> + * @ecc_cfg: The configuration of each ECC operation
> + * @bbm_ctl: Information relative to the BBM swap
> + */
> +struct mtk_ecc_engine {
> +	struct nand_ecc_req_tweak_ctx req_ctx;
> +
> +	u32 oob_per_section;
> +	u32 oob_per_section_idx;
> +	u32 oob_ecc;
> +	u32 oob_free;
> +	u32 oob_free_protected;
> +	u32 section_size;
> +
> +	bool read_empty;
> +	u32 nsteps;
> +
> +	u8 *src_page_buf;
> +	u8 *src_oob_buf;
> +	u8 *bounce_page_buf;
> +	u8 *bounce_oob_buf;
> +
> +	struct mtk_ecc *ecc;
> +	struct mtk_ecc_config ecc_cfg;
> +	struct mtk_ecc_bbm_ctl bbm_ctl;
> +};

This and above should not be exported and be located in the driver.

> +
>  int mtk_ecc_encode(struct mtk_ecc *, struct mtk_ecc_config *, u8 *, u32);
>  void mtk_ecc_get_stats(struct mtk_ecc *, struct mtk_ecc_stats *, int);
>  int mtk_ecc_wait_done(struct mtk_ecc *, enum mtk_ecc_operation);
> @@ -44,4 +99,17 @@ unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc);
>  struct mtk_ecc *of_mtk_ecc_get(struct device_node *);
>  void mtk_ecc_release(struct mtk_ecc *);
>  
> +#if IS_ENABLED(CONFIG_MTD_NAND_ECC_MTK)
> +
> +struct nand_ecc_engine_ops *mtk_ecc_get_pipelined_ops(void);
> +
> +#else /* !CONFIG_MTD_NAND_ECC_MTK */
> +
> +struct nand_ecc_engine_ops *mtk_ecc_get_pipelined_ops(void)
> +{
> +	return NULL;
> +}
> +
> +#endif /* CONFIG_MTD_NAND_ECC_MTK */
> +
>  #endif


Thanks,
Miquèl



More information about the linux-mtd mailing list