From: Miquel Raynal <miquel.raynal@bootlin.com>
To: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
Cc: <broonie@kernel.org>, <benliang.zhao@mediatek.com>,
<dandan.he@mediatek.com>, <guochun.mao@mediatek.com>,
<bin.zhang@mediatek.com>, <sanny.chen@mediatek.com>,
<mao.zhong@mediatek.com>, <yingjoe.chen@mediatek.com>,
<donghunt@amazon.com>, <rdlee@amazon.com>,
<linux-mtd@lists.infradead.org>,
<linux-mediatek@lists.infradead.org>,
<srv_heupstream@mediatek.com>
Subject: Re: [RFC,v4,2/5] mtd: nand: ecc: mtk: Convert to the ECC infrastructure
Date: Thu, 9 Dec 2021 11:32:09 +0100 [thread overview]
Message-ID: <20211209113209.71fe8ea7@xps13> (raw)
In-Reply-To: <20211130083202.14228-3-xiangsheng.hou@mediatek.com>
Hi Xiangsheng,
xiangsheng.hou@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@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
_______________________________________________
Linux-mediatek mailing list
Linux-mediatek@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-mediatek
WARNING: multiple messages have this Message-ID (diff)
From: Miquel Raynal <miquel.raynal@bootlin.com>
To: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
Cc: <broonie@kernel.org>, <benliang.zhao@mediatek.com>,
<dandan.he@mediatek.com>, <guochun.mao@mediatek.com>,
<bin.zhang@mediatek.com>, <sanny.chen@mediatek.com>,
<mao.zhong@mediatek.com>, <yingjoe.chen@mediatek.com>,
<donghunt@amazon.com>, <rdlee@amazon.com>,
<linux-mtd@lists.infradead.org>,
<linux-mediatek@lists.infradead.org>,
<srv_heupstream@mediatek.com>
Subject: Re: [RFC,v4,2/5] mtd: nand: ecc: mtk: Convert to the ECC infrastructure
Date: Thu, 9 Dec 2021 11:32:09 +0100 [thread overview]
Message-ID: <20211209113209.71fe8ea7@xps13> (raw)
In-Reply-To: <20211130083202.14228-3-xiangsheng.hou@mediatek.com>
Hi Xiangsheng,
xiangsheng.hou@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@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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
next prev parent reply other threads:[~2021-12-09 10:32 UTC|newest]
Thread overview: 36+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-11-30 8:31 [RFC,v4,0/5] Add Mediatek SPI Nand controller and convert ECC driver Xiangsheng Hou
2021-11-30 8:31 ` Xiangsheng Hou
2021-11-30 8:31 ` [RFC,v4,1/5] mtd: nand: ecc: Move mediatek " Xiangsheng Hou
2021-11-30 8:31 ` Xiangsheng Hou
2021-11-30 8:31 ` [RFC,v4,2/5] mtd: nand: ecc: mtk: Convert to the ECC infrastructure Xiangsheng Hou
2021-11-30 8:31 ` Xiangsheng Hou
2021-12-09 10:32 ` Miquel Raynal [this message]
2021-12-09 10:32 ` Miquel Raynal
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:34 ` Miquel Raynal
2021-12-10 9:34 ` Miquel Raynal
2021-12-11 3:25 ` xiangsheng.hou
2021-12-11 3:25 ` xiangsheng.hou
2021-12-13 9:29 ` Miquel Raynal
2021-12-13 9:29 ` Miquel Raynal
2021-12-14 3:32 ` xiangsheng.hou
2021-12-14 3:32 ` xiangsheng.hou
2021-12-14 9:47 ` Miquel Raynal
2021-12-14 9:47 ` Miquel Raynal
2021-11-30 8:32 ` [RFC,v4,3/5] spi: mtk: Add mediatek SPI Nand Flash interface driver Xiangsheng Hou
2021-11-30 8:32 ` Xiangsheng Hou
2021-12-09 10:20 ` Miquel Raynal
2021-12-09 10:20 ` Miquel Raynal
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:09 ` xiangsheng.hou
2021-12-10 9:40 ` Miquel Raynal
2021-12-10 9:40 ` Miquel Raynal
2021-11-30 8:32 ` [RFC, v4, 4/5] mtd: spinand: Move set/get OOB databytes to each ECC engines Xiangsheng Hou
2021-11-30 8:32 ` Xiangsheng Hou
2021-12-14 11:41 ` [RFC,v4,4/5] " Miquel Raynal
2021-12-14 11:41 ` Miquel Raynal
2021-12-20 7:37 ` xiangsheng.hou
2021-12-20 7:37 ` xiangsheng.hou
2021-11-30 8:32 ` [RFC,v4,5/5] arm64: dts: mtk: Add snfi node Xiangsheng Hou
2021-11-30 8:32 ` Xiangsheng Hou
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211209113209.71fe8ea7@xps13 \
--to=miquel.raynal@bootlin.com \
--cc=benliang.zhao@mediatek.com \
--cc=bin.zhang@mediatek.com \
--cc=broonie@kernel.org \
--cc=dandan.he@mediatek.com \
--cc=donghunt@amazon.com \
--cc=guochun.mao@mediatek.com \
--cc=linux-mediatek@lists.infradead.org \
--cc=linux-mtd@lists.infradead.org \
--cc=mao.zhong@mediatek.com \
--cc=rdlee@amazon.com \
--cc=sanny.chen@mediatek.com \
--cc=srv_heupstream@mediatek.com \
--cc=xiangsheng.hou@mediatek.com \
--cc=yingjoe.chen@mediatek.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.