xref: /openbmc/linux/drivers/mtd/nand/ecc-mxic.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
148e6633aSMiquel Raynal // SPDX-License-Identifier: GPL-2.0
248e6633aSMiquel Raynal /*
348e6633aSMiquel Raynal  * Support for Macronix external hardware ECC engine for NAND devices, also
448e6633aSMiquel Raynal  * called DPE for Data Processing Engine.
548e6633aSMiquel Raynal  *
648e6633aSMiquel Raynal  * Copyright © 2019 Macronix
748e6633aSMiquel Raynal  * Author: Miquel Raynal <miquel.raynal@bootlin.com>
848e6633aSMiquel Raynal  */
948e6633aSMiquel Raynal 
1048e6633aSMiquel Raynal #include <linux/dma-mapping.h>
1148e6633aSMiquel Raynal #include <linux/init.h>
1248e6633aSMiquel Raynal #include <linux/interrupt.h>
1348e6633aSMiquel Raynal #include <linux/io.h>
1448e6633aSMiquel Raynal #include <linux/iopoll.h>
1548e6633aSMiquel Raynal #include <linux/kernel.h>
1648e6633aSMiquel Raynal #include <linux/module.h>
1748e6633aSMiquel Raynal #include <linux/mtd/mtd.h>
1848e6633aSMiquel Raynal #include <linux/mtd/nand.h>
1970e038f8SMiquel Raynal #include <linux/mtd/nand-ecc-mxic.h>
2048e6633aSMiquel Raynal #include <linux/mutex.h>
21*c2fc6b69SRob Herring #include <linux/of.h>
2248e6633aSMiquel Raynal #include <linux/of_platform.h>
2348e6633aSMiquel Raynal #include <linux/platform_device.h>
2448e6633aSMiquel Raynal #include <linux/slab.h>
2548e6633aSMiquel Raynal 
2648e6633aSMiquel Raynal /* DPE Configuration */
2748e6633aSMiquel Raynal #define DP_CONFIG 0x00
2848e6633aSMiquel Raynal #define   ECC_EN BIT(0)
2948e6633aSMiquel Raynal #define   ECC_TYP(idx) (((idx) << 3) & GENMASK(6, 3))
3048e6633aSMiquel Raynal /* DPE Interrupt Status */
3148e6633aSMiquel Raynal #define INTRPT_STS 0x04
3248e6633aSMiquel Raynal #define   TRANS_CMPLT BIT(0)
3348e6633aSMiquel Raynal #define   SDMA_MAIN BIT(1)
3448e6633aSMiquel Raynal #define   SDMA_SPARE BIT(2)
3548e6633aSMiquel Raynal #define   ECC_ERR BIT(3)
3648e6633aSMiquel Raynal #define   TO_SPARE BIT(4)
3748e6633aSMiquel Raynal #define   TO_MAIN BIT(5)
3848e6633aSMiquel Raynal /* DPE Interrupt Status Enable */
3948e6633aSMiquel Raynal #define INTRPT_STS_EN 0x08
4048e6633aSMiquel Raynal /* DPE Interrupt Signal Enable */
4148e6633aSMiquel Raynal #define INTRPT_SIG_EN 0x0C
4248e6633aSMiquel Raynal /* Host Controller Configuration */
4348e6633aSMiquel Raynal #define HC_CONFIG 0x10
4470e038f8SMiquel Raynal #define   DEV2MEM 0 /* TRANS_TYP_DMA in the spec */
4548e6633aSMiquel Raynal #define   MEM2MEM BIT(4) /* TRANS_TYP_IO in the spec */
4670e038f8SMiquel Raynal #define   MAPPING BIT(5) /* TRANS_TYP_MAPPING in the spec */
4748e6633aSMiquel Raynal #define   ECC_PACKED 0 /* LAYOUT_TYP_INTEGRATED in the spec */
4848e6633aSMiquel Raynal #define   ECC_INTERLEAVED BIT(2) /* LAYOUT_TYP_DISTRIBUTED in the spec */
4948e6633aSMiquel Raynal #define   BURST_TYP_FIXED 0
5048e6633aSMiquel Raynal #define   BURST_TYP_INCREASING BIT(0)
5148e6633aSMiquel Raynal /* Host Controller Slave Address */
5248e6633aSMiquel Raynal #define HC_SLV_ADDR 0x14
5348e6633aSMiquel Raynal /* ECC Chunk Size */
5448e6633aSMiquel Raynal #define CHUNK_SIZE 0x20
5548e6633aSMiquel Raynal /* Main Data Size */
5648e6633aSMiquel Raynal #define MAIN_SIZE 0x24
5748e6633aSMiquel Raynal /* Spare Data Size */
5848e6633aSMiquel Raynal #define SPARE_SIZE 0x28
5948e6633aSMiquel Raynal #define   META_SZ(reg) ((reg) & GENMASK(7, 0))
6048e6633aSMiquel Raynal #define   PARITY_SZ(reg) (((reg) & GENMASK(15, 8)) >> 8)
6148e6633aSMiquel Raynal #define   RSV_SZ(reg) (((reg) & GENMASK(23, 16)) >> 16)
6248e6633aSMiquel Raynal #define   SPARE_SZ(reg) ((reg) >> 24)
6348e6633aSMiquel Raynal /* ECC Chunk Count */
6448e6633aSMiquel Raynal #define CHUNK_CNT 0x30
6548e6633aSMiquel Raynal /* SDMA Control */
6648e6633aSMiquel Raynal #define SDMA_CTRL 0x40
6748e6633aSMiquel Raynal #define   WRITE_NAND 0
6848e6633aSMiquel Raynal #define   READ_NAND BIT(1)
6948e6633aSMiquel Raynal #define   CONT_NAND BIT(29)
7048e6633aSMiquel Raynal #define   CONT_SYSM BIT(30) /* Continue System Memory? */
7148e6633aSMiquel Raynal #define   SDMA_STRT BIT(31)
7248e6633aSMiquel Raynal /* SDMA Address of Main Data */
7348e6633aSMiquel Raynal #define SDMA_MAIN_ADDR 0x44
7448e6633aSMiquel Raynal /* SDMA Address of Spare Data */
7548e6633aSMiquel Raynal #define SDMA_SPARE_ADDR 0x48
7648e6633aSMiquel Raynal /* DPE Version Number */
7748e6633aSMiquel Raynal #define DP_VER 0xD0
7848e6633aSMiquel Raynal #define   DP_VER_OFFSET 16
7948e6633aSMiquel Raynal 
8048e6633aSMiquel Raynal /* Status bytes between each chunk of spare data */
8148e6633aSMiquel Raynal #define STAT_BYTES 4
8248e6633aSMiquel Raynal #define   NO_ERR 0x00
8348e6633aSMiquel Raynal #define   MAX_CORR_ERR 0x28
8448e6633aSMiquel Raynal #define   UNCORR_ERR 0xFE
8548e6633aSMiquel Raynal #define   ERASED_CHUNK 0xFF
8648e6633aSMiquel Raynal 
8748e6633aSMiquel Raynal struct mxic_ecc_engine {
8848e6633aSMiquel Raynal 	struct device *dev;
8948e6633aSMiquel Raynal 	void __iomem *regs;
9048e6633aSMiquel Raynal 	int irq;
9148e6633aSMiquel Raynal 	struct completion complete;
9248e6633aSMiquel Raynal 	struct nand_ecc_engine external_engine;
9370e038f8SMiquel Raynal 	struct nand_ecc_engine pipelined_engine;
9448e6633aSMiquel Raynal 	struct mutex lock;
9548e6633aSMiquel Raynal };
9648e6633aSMiquel Raynal 
9748e6633aSMiquel Raynal struct mxic_ecc_ctx {
9848e6633aSMiquel Raynal 	/* ECC machinery */
9948e6633aSMiquel Raynal 	unsigned int data_step_sz;
10048e6633aSMiquel Raynal 	unsigned int oob_step_sz;
10148e6633aSMiquel Raynal 	unsigned int parity_sz;
10248e6633aSMiquel Raynal 	unsigned int meta_sz;
10348e6633aSMiquel Raynal 	u8 *status;
10448e6633aSMiquel Raynal 	int steps;
10548e6633aSMiquel Raynal 
10648e6633aSMiquel Raynal 	/* DMA boilerplate */
10748e6633aSMiquel Raynal 	struct nand_ecc_req_tweak_ctx req_ctx;
10848e6633aSMiquel Raynal 	u8 *oobwithstat;
10948e6633aSMiquel Raynal 	struct scatterlist sg[2];
11048e6633aSMiquel Raynal 	struct nand_page_io_req *req;
11170e038f8SMiquel Raynal 	unsigned int pageoffs;
11248e6633aSMiquel Raynal };
11348e6633aSMiquel Raynal 
ext_ecc_eng_to_mxic(struct nand_ecc_engine * eng)11448e6633aSMiquel Raynal static struct mxic_ecc_engine *ext_ecc_eng_to_mxic(struct nand_ecc_engine *eng)
11548e6633aSMiquel Raynal {
11648e6633aSMiquel Raynal 	return container_of(eng, struct mxic_ecc_engine, external_engine);
11748e6633aSMiquel Raynal }
11848e6633aSMiquel Raynal 
pip_ecc_eng_to_mxic(struct nand_ecc_engine * eng)11970e038f8SMiquel Raynal static struct mxic_ecc_engine *pip_ecc_eng_to_mxic(struct nand_ecc_engine *eng)
12070e038f8SMiquel Raynal {
12170e038f8SMiquel Raynal 	return container_of(eng, struct mxic_ecc_engine, pipelined_engine);
12270e038f8SMiquel Raynal }
12370e038f8SMiquel Raynal 
nand_to_mxic(struct nand_device * nand)12448e6633aSMiquel Raynal static struct mxic_ecc_engine *nand_to_mxic(struct nand_device *nand)
12548e6633aSMiquel Raynal {
12648e6633aSMiquel Raynal 	struct nand_ecc_engine *eng = nand->ecc.engine;
12748e6633aSMiquel Raynal 
12870e038f8SMiquel Raynal 	if (eng->integration == NAND_ECC_ENGINE_INTEGRATION_EXTERNAL)
12948e6633aSMiquel Raynal 		return ext_ecc_eng_to_mxic(eng);
13070e038f8SMiquel Raynal 	else
13170e038f8SMiquel Raynal 		return pip_ecc_eng_to_mxic(eng);
13248e6633aSMiquel Raynal }
13348e6633aSMiquel Raynal 
mxic_ecc_ooblayout_ecc(struct mtd_info * mtd,int section,struct mtd_oob_region * oobregion)13448e6633aSMiquel Raynal static int mxic_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
13548e6633aSMiquel Raynal 				  struct mtd_oob_region *oobregion)
13648e6633aSMiquel Raynal {
13748e6633aSMiquel Raynal 	struct nand_device *nand = mtd_to_nanddev(mtd);
13848e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
13948e6633aSMiquel Raynal 
14048e6633aSMiquel Raynal 	if (section < 0 || section >= ctx->steps)
14148e6633aSMiquel Raynal 		return -ERANGE;
14248e6633aSMiquel Raynal 
14348e6633aSMiquel Raynal 	oobregion->offset = (section * ctx->oob_step_sz) + ctx->meta_sz;
14448e6633aSMiquel Raynal 	oobregion->length = ctx->parity_sz;
14548e6633aSMiquel Raynal 
14648e6633aSMiquel Raynal 	return 0;
14748e6633aSMiquel Raynal }
14848e6633aSMiquel Raynal 
mxic_ecc_ooblayout_free(struct mtd_info * mtd,int section,struct mtd_oob_region * oobregion)14948e6633aSMiquel Raynal static int mxic_ecc_ooblayout_free(struct mtd_info *mtd, int section,
15048e6633aSMiquel Raynal 				   struct mtd_oob_region *oobregion)
15148e6633aSMiquel Raynal {
15248e6633aSMiquel Raynal 	struct nand_device *nand = mtd_to_nanddev(mtd);
15348e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
15448e6633aSMiquel Raynal 
15548e6633aSMiquel Raynal 	if (section < 0 || section >= ctx->steps)
15648e6633aSMiquel Raynal 		return -ERANGE;
15748e6633aSMiquel Raynal 
15848e6633aSMiquel Raynal 	if (!section) {
15948e6633aSMiquel Raynal 		oobregion->offset = 2;
16048e6633aSMiquel Raynal 		oobregion->length = ctx->meta_sz - 2;
16148e6633aSMiquel Raynal 	} else {
16248e6633aSMiquel Raynal 		oobregion->offset = section * ctx->oob_step_sz;
16348e6633aSMiquel Raynal 		oobregion->length = ctx->meta_sz;
16448e6633aSMiquel Raynal 	}
16548e6633aSMiquel Raynal 
16648e6633aSMiquel Raynal 	return 0;
16748e6633aSMiquel Raynal }
16848e6633aSMiquel Raynal 
16948e6633aSMiquel Raynal static const struct mtd_ooblayout_ops mxic_ecc_ooblayout_ops = {
17048e6633aSMiquel Raynal 	.ecc = mxic_ecc_ooblayout_ecc,
17148e6633aSMiquel Raynal 	.free = mxic_ecc_ooblayout_free,
17248e6633aSMiquel Raynal };
17348e6633aSMiquel Raynal 
mxic_ecc_disable_engine(struct mxic_ecc_engine * mxic)17448e6633aSMiquel Raynal static void mxic_ecc_disable_engine(struct mxic_ecc_engine *mxic)
17548e6633aSMiquel Raynal {
17648e6633aSMiquel Raynal 	u32 reg;
17748e6633aSMiquel Raynal 
17848e6633aSMiquel Raynal 	reg = readl(mxic->regs + DP_CONFIG);
17948e6633aSMiquel Raynal 	reg &= ~ECC_EN;
18048e6633aSMiquel Raynal 	writel(reg, mxic->regs + DP_CONFIG);
18148e6633aSMiquel Raynal }
18248e6633aSMiquel Raynal 
mxic_ecc_enable_engine(struct mxic_ecc_engine * mxic)18348e6633aSMiquel Raynal static void mxic_ecc_enable_engine(struct mxic_ecc_engine *mxic)
18448e6633aSMiquel Raynal {
18548e6633aSMiquel Raynal 	u32 reg;
18648e6633aSMiquel Raynal 
18748e6633aSMiquel Raynal 	reg = readl(mxic->regs + DP_CONFIG);
18848e6633aSMiquel Raynal 	reg |= ECC_EN;
18948e6633aSMiquel Raynal 	writel(reg, mxic->regs + DP_CONFIG);
19048e6633aSMiquel Raynal }
19148e6633aSMiquel Raynal 
mxic_ecc_disable_int(struct mxic_ecc_engine * mxic)19248e6633aSMiquel Raynal static void mxic_ecc_disable_int(struct mxic_ecc_engine *mxic)
19348e6633aSMiquel Raynal {
19448e6633aSMiquel Raynal 	writel(0, mxic->regs + INTRPT_SIG_EN);
19548e6633aSMiquel Raynal }
19648e6633aSMiquel Raynal 
mxic_ecc_enable_int(struct mxic_ecc_engine * mxic)19748e6633aSMiquel Raynal static void mxic_ecc_enable_int(struct mxic_ecc_engine *mxic)
19848e6633aSMiquel Raynal {
19948e6633aSMiquel Raynal 	writel(TRANS_CMPLT, mxic->regs + INTRPT_SIG_EN);
20048e6633aSMiquel Raynal }
20148e6633aSMiquel Raynal 
mxic_ecc_isr(int irq,void * dev_id)20248e6633aSMiquel Raynal static irqreturn_t mxic_ecc_isr(int irq, void *dev_id)
20348e6633aSMiquel Raynal {
20448e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic = dev_id;
20548e6633aSMiquel Raynal 	u32 sts;
20648e6633aSMiquel Raynal 
20748e6633aSMiquel Raynal 	sts = readl(mxic->regs + INTRPT_STS);
20848e6633aSMiquel Raynal 	if (!sts)
20948e6633aSMiquel Raynal 		return IRQ_NONE;
21048e6633aSMiquel Raynal 
21148e6633aSMiquel Raynal 	if (sts & TRANS_CMPLT)
21248e6633aSMiquel Raynal 		complete(&mxic->complete);
21348e6633aSMiquel Raynal 
21448e6633aSMiquel Raynal 	writel(sts, mxic->regs + INTRPT_STS);
21548e6633aSMiquel Raynal 
21648e6633aSMiquel Raynal 	return IRQ_HANDLED;
21748e6633aSMiquel Raynal }
21848e6633aSMiquel Raynal 
mxic_ecc_init_ctx(struct nand_device * nand,struct device * dev)21948e6633aSMiquel Raynal static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev)
22048e6633aSMiquel Raynal {
22148e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
22248e6633aSMiquel Raynal 	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
22348e6633aSMiquel Raynal 	struct nand_ecc_props *reqs = &nand->ecc.requirements;
22448e6633aSMiquel Raynal 	struct nand_ecc_props *user = &nand->ecc.user_conf;
22548e6633aSMiquel Raynal 	struct mtd_info *mtd = nanddev_to_mtd(nand);
22648e6633aSMiquel Raynal 	int step_size = 0, strength = 0, desired_correction = 0, steps, idx;
2275a368fb6SColin Ian King 	static const int possible_strength[] = {4, 8, 40, 48};
2285a368fb6SColin Ian King 	static const int spare_size[] = {32, 32, 96, 96};
22948e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx;
23048e6633aSMiquel Raynal 	u32 spare_reg;
23148e6633aSMiquel Raynal 	int ret;
23248e6633aSMiquel Raynal 
23348e6633aSMiquel Raynal 	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
23448e6633aSMiquel Raynal 	if (!ctx)
23548e6633aSMiquel Raynal 		return -ENOMEM;
23648e6633aSMiquel Raynal 
23748e6633aSMiquel Raynal 	nand->ecc.ctx.priv = ctx;
23848e6633aSMiquel Raynal 
23948e6633aSMiquel Raynal 	/* Only large page NAND chips may use BCH */
24048e6633aSMiquel Raynal 	if (mtd->oobsize < 64) {
24148e6633aSMiquel Raynal 		pr_err("BCH cannot be used with small page NAND chips\n");
24248e6633aSMiquel Raynal 		return -EINVAL;
24348e6633aSMiquel Raynal 	}
24448e6633aSMiquel Raynal 
24548e6633aSMiquel Raynal 	mtd_set_ooblayout(mtd, &mxic_ecc_ooblayout_ops);
24648e6633aSMiquel Raynal 
24748e6633aSMiquel Raynal 	/* Enable all status bits */
24848e6633aSMiquel Raynal 	writel(TRANS_CMPLT | SDMA_MAIN | SDMA_SPARE | ECC_ERR |
24948e6633aSMiquel Raynal 	       TO_SPARE | TO_MAIN, mxic->regs + INTRPT_STS_EN);
25048e6633aSMiquel Raynal 
25148e6633aSMiquel Raynal 	/* Configure the correction depending on the NAND device topology */
25248e6633aSMiquel Raynal 	if (user->step_size && user->strength) {
25348e6633aSMiquel Raynal 		step_size = user->step_size;
25448e6633aSMiquel Raynal 		strength = user->strength;
25548e6633aSMiquel Raynal 	} else if (reqs->step_size && reqs->strength) {
25648e6633aSMiquel Raynal 		step_size = reqs->step_size;
25748e6633aSMiquel Raynal 		strength = reqs->strength;
25848e6633aSMiquel Raynal 	}
25948e6633aSMiquel Raynal 
26048e6633aSMiquel Raynal 	if (step_size && strength) {
26148e6633aSMiquel Raynal 		steps = mtd->writesize / step_size;
26248e6633aSMiquel Raynal 		desired_correction = steps * strength;
26348e6633aSMiquel Raynal 	}
26448e6633aSMiquel Raynal 
26548e6633aSMiquel Raynal 	/* Step size is fixed to 1kiB, strength may vary (4 possible values) */
26648e6633aSMiquel Raynal 	conf->step_size = SZ_1K;
26748e6633aSMiquel Raynal 	steps = mtd->writesize / conf->step_size;
26848e6633aSMiquel Raynal 
26948e6633aSMiquel Raynal 	ctx->status = devm_kzalloc(dev, steps * sizeof(u8), GFP_KERNEL);
27048e6633aSMiquel Raynal 	if (!ctx->status)
27148e6633aSMiquel Raynal 		return -ENOMEM;
27248e6633aSMiquel Raynal 
27348e6633aSMiquel Raynal 	if (desired_correction) {
27448e6633aSMiquel Raynal 		strength = desired_correction / steps;
27548e6633aSMiquel Raynal 
27648e6633aSMiquel Raynal 		for (idx = 0; idx < ARRAY_SIZE(possible_strength); idx++)
27748e6633aSMiquel Raynal 			if (possible_strength[idx] >= strength)
27848e6633aSMiquel Raynal 				break;
27948e6633aSMiquel Raynal 
28048e6633aSMiquel Raynal 		idx = min_t(unsigned int, idx,
28148e6633aSMiquel Raynal 			    ARRAY_SIZE(possible_strength) - 1);
28248e6633aSMiquel Raynal 	} else {
28348e6633aSMiquel Raynal 		/* Missing data, maximize the correction */
28448e6633aSMiquel Raynal 		idx = ARRAY_SIZE(possible_strength) - 1;
28548e6633aSMiquel Raynal 	}
28648e6633aSMiquel Raynal 
28748e6633aSMiquel Raynal 	/* Tune the selected strength until it fits in the OOB area */
28848e6633aSMiquel Raynal 	for (; idx >= 0; idx--) {
28948e6633aSMiquel Raynal 		if (spare_size[idx] * steps <= mtd->oobsize)
29048e6633aSMiquel Raynal 			break;
29148e6633aSMiquel Raynal 	}
29248e6633aSMiquel Raynal 
29348e6633aSMiquel Raynal 	/* This engine cannot be used with this NAND device */
29448e6633aSMiquel Raynal 	if (idx < 0)
29548e6633aSMiquel Raynal 		return -EINVAL;
29648e6633aSMiquel Raynal 
29748e6633aSMiquel Raynal 	/* Configure the engine for the desired strength */
29848e6633aSMiquel Raynal 	writel(ECC_TYP(idx), mxic->regs + DP_CONFIG);
29948e6633aSMiquel Raynal 	conf->strength = possible_strength[idx];
30048e6633aSMiquel Raynal 	spare_reg = readl(mxic->regs + SPARE_SIZE);
30148e6633aSMiquel Raynal 
30248e6633aSMiquel Raynal 	ctx->steps = steps;
30348e6633aSMiquel Raynal 	ctx->data_step_sz = mtd->writesize / steps;
30448e6633aSMiquel Raynal 	ctx->oob_step_sz = mtd->oobsize / steps;
30548e6633aSMiquel Raynal 	ctx->parity_sz = PARITY_SZ(spare_reg);
30648e6633aSMiquel Raynal 	ctx->meta_sz = META_SZ(spare_reg);
30748e6633aSMiquel Raynal 
30848e6633aSMiquel Raynal 	/* Ensure buffers will contain enough bytes to store the STAT_BYTES */
30948e6633aSMiquel Raynal 	ctx->req_ctx.oob_buffer_size = nanddev_per_page_oobsize(nand) +
31048e6633aSMiquel Raynal 					(ctx->steps * STAT_BYTES);
31148e6633aSMiquel Raynal 	ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
31248e6633aSMiquel Raynal 	if (ret)
31348e6633aSMiquel Raynal 		return ret;
31448e6633aSMiquel Raynal 
31548e6633aSMiquel Raynal 	ctx->oobwithstat = kmalloc(mtd->oobsize + (ctx->steps * STAT_BYTES),
31648e6633aSMiquel Raynal 				   GFP_KERNEL);
31748e6633aSMiquel Raynal 	if (!ctx->oobwithstat) {
31848e6633aSMiquel Raynal 		ret = -ENOMEM;
31948e6633aSMiquel Raynal 		goto cleanup_req_tweak;
32048e6633aSMiquel Raynal 	}
32148e6633aSMiquel Raynal 
32248e6633aSMiquel Raynal 	sg_init_table(ctx->sg, 2);
32348e6633aSMiquel Raynal 
32448e6633aSMiquel Raynal 	/* Configuration dump and sanity checks */
32548e6633aSMiquel Raynal 	dev_err(dev, "DPE version number: %d\n",
32648e6633aSMiquel Raynal 		readl(mxic->regs + DP_VER) >> DP_VER_OFFSET);
32748e6633aSMiquel Raynal 	dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
32848e6633aSMiquel Raynal 	dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
32948e6633aSMiquel Raynal 	dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
33048e6633aSMiquel Raynal 	dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
33148e6633aSMiquel Raynal 	dev_err(dev, "Parity size: %d\n", ctx->parity_sz);
33248e6633aSMiquel Raynal 	dev_err(dev, "Meta size: %d\n", ctx->meta_sz);
33348e6633aSMiquel Raynal 
33448e6633aSMiquel Raynal 	if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) !=
33548e6633aSMiquel Raynal 	    SPARE_SZ(spare_reg)) {
33648e6633aSMiquel Raynal 		dev_err(dev, "Wrong OOB configuration: %d + %d + %ld != %d\n",
33748e6633aSMiquel Raynal 			ctx->meta_sz, ctx->parity_sz, RSV_SZ(spare_reg),
33848e6633aSMiquel Raynal 			SPARE_SZ(spare_reg));
33948e6633aSMiquel Raynal 		ret = -EINVAL;
34048e6633aSMiquel Raynal 		goto free_oobwithstat;
34148e6633aSMiquel Raynal 	}
34248e6633aSMiquel Raynal 
34348e6633aSMiquel Raynal 	if (ctx->oob_step_sz != SPARE_SZ(spare_reg)) {
34448e6633aSMiquel Raynal 		dev_err(dev, "Wrong OOB configuration: %d != %d\n",
34548e6633aSMiquel Raynal 			ctx->oob_step_sz, SPARE_SZ(spare_reg));
34648e6633aSMiquel Raynal 		ret = -EINVAL;
34748e6633aSMiquel Raynal 		goto free_oobwithstat;
34848e6633aSMiquel Raynal 	}
34948e6633aSMiquel Raynal 
35048e6633aSMiquel Raynal 	return 0;
35148e6633aSMiquel Raynal 
35248e6633aSMiquel Raynal free_oobwithstat:
35348e6633aSMiquel Raynal 	kfree(ctx->oobwithstat);
35448e6633aSMiquel Raynal cleanup_req_tweak:
35548e6633aSMiquel Raynal 	nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
35648e6633aSMiquel Raynal 
35748e6633aSMiquel Raynal 	return ret;
35848e6633aSMiquel Raynal }
35948e6633aSMiquel Raynal 
mxic_ecc_init_ctx_external(struct nand_device * nand)36048e6633aSMiquel Raynal static int mxic_ecc_init_ctx_external(struct nand_device *nand)
36148e6633aSMiquel Raynal {
36248e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
36348e6633aSMiquel Raynal 	struct device *dev = nand->ecc.engine->dev;
36448e6633aSMiquel Raynal 	int ret;
36548e6633aSMiquel Raynal 
36648e6633aSMiquel Raynal 	dev_info(dev, "Macronix ECC engine in external mode\n");
36748e6633aSMiquel Raynal 
36848e6633aSMiquel Raynal 	ret = mxic_ecc_init_ctx(nand, dev);
36948e6633aSMiquel Raynal 	if (ret)
37048e6633aSMiquel Raynal 		return ret;
37148e6633aSMiquel Raynal 
37248e6633aSMiquel Raynal 	/* Trigger each step manually */
37348e6633aSMiquel Raynal 	writel(1, mxic->regs + CHUNK_CNT);
37448e6633aSMiquel Raynal 	writel(BURST_TYP_INCREASING | ECC_PACKED | MEM2MEM,
37548e6633aSMiquel Raynal 	       mxic->regs + HC_CONFIG);
37648e6633aSMiquel Raynal 
37748e6633aSMiquel Raynal 	return 0;
37848e6633aSMiquel Raynal }
37948e6633aSMiquel Raynal 
mxic_ecc_init_ctx_pipelined(struct nand_device * nand)38070e038f8SMiquel Raynal static int mxic_ecc_init_ctx_pipelined(struct nand_device *nand)
38170e038f8SMiquel Raynal {
38270e038f8SMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
38370e038f8SMiquel Raynal 	struct mxic_ecc_ctx *ctx;
38470e038f8SMiquel Raynal 	struct device *dev;
38570e038f8SMiquel Raynal 	int ret;
38670e038f8SMiquel Raynal 
38770e038f8SMiquel Raynal 	dev = nand_ecc_get_engine_dev(nand->ecc.engine->dev);
38870e038f8SMiquel Raynal 	if (!dev)
38970e038f8SMiquel Raynal 		return -EINVAL;
39070e038f8SMiquel Raynal 
39170e038f8SMiquel Raynal 	dev_info(dev, "Macronix ECC engine in pipelined/mapping mode\n");
39270e038f8SMiquel Raynal 
39370e038f8SMiquel Raynal 	ret = mxic_ecc_init_ctx(nand, dev);
39470e038f8SMiquel Raynal 	if (ret)
39570e038f8SMiquel Raynal 		return ret;
39670e038f8SMiquel Raynal 
39770e038f8SMiquel Raynal 	ctx = nand_to_ecc_ctx(nand);
39870e038f8SMiquel Raynal 
39970e038f8SMiquel Raynal 	/* All steps should be handled in one go directly by the internal DMA */
40070e038f8SMiquel Raynal 	writel(ctx->steps, mxic->regs + CHUNK_CNT);
40170e038f8SMiquel Raynal 
40270e038f8SMiquel Raynal 	/*
40370e038f8SMiquel Raynal 	 * Interleaved ECC scheme cannot be used otherwise factory bad block
40470e038f8SMiquel Raynal 	 * markers would be lost. A packed layout is mandatory.
40570e038f8SMiquel Raynal 	 */
40670e038f8SMiquel Raynal 	writel(BURST_TYP_INCREASING | ECC_PACKED | MAPPING,
40770e038f8SMiquel Raynal 	       mxic->regs + HC_CONFIG);
40870e038f8SMiquel Raynal 
40970e038f8SMiquel Raynal 	return 0;
41070e038f8SMiquel Raynal }
41170e038f8SMiquel Raynal 
mxic_ecc_cleanup_ctx(struct nand_device * nand)41248e6633aSMiquel Raynal static void mxic_ecc_cleanup_ctx(struct nand_device *nand)
41348e6633aSMiquel Raynal {
41448e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
41548e6633aSMiquel Raynal 
41648e6633aSMiquel Raynal 	if (ctx) {
41748e6633aSMiquel Raynal 		nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
41848e6633aSMiquel Raynal 		kfree(ctx->oobwithstat);
41948e6633aSMiquel Raynal 	}
42048e6633aSMiquel Raynal }
42148e6633aSMiquel Raynal 
mxic_ecc_data_xfer_wait_for_completion(struct mxic_ecc_engine * mxic)42248e6633aSMiquel Raynal static int mxic_ecc_data_xfer_wait_for_completion(struct mxic_ecc_engine *mxic)
42348e6633aSMiquel Raynal {
42448e6633aSMiquel Raynal 	u32 val;
42548e6633aSMiquel Raynal 	int ret;
42648e6633aSMiquel Raynal 
42748e6633aSMiquel Raynal 	if (mxic->irq) {
42848e6633aSMiquel Raynal 		reinit_completion(&mxic->complete);
42948e6633aSMiquel Raynal 		mxic_ecc_enable_int(mxic);
43048e6633aSMiquel Raynal 		ret = wait_for_completion_timeout(&mxic->complete,
43148e6633aSMiquel Raynal 						  msecs_to_jiffies(1000));
43275dce6a9SChristophe JAILLET 		ret = ret ? 0 : -ETIMEDOUT;
43348e6633aSMiquel Raynal 		mxic_ecc_disable_int(mxic);
43448e6633aSMiquel Raynal 	} else {
43548e6633aSMiquel Raynal 		ret = readl_poll_timeout(mxic->regs + INTRPT_STS, val,
43648e6633aSMiquel Raynal 					 val & TRANS_CMPLT, 10, USEC_PER_SEC);
43748e6633aSMiquel Raynal 		writel(val, mxic->regs + INTRPT_STS);
43848e6633aSMiquel Raynal 	}
43948e6633aSMiquel Raynal 
44048e6633aSMiquel Raynal 	if (ret) {
44148e6633aSMiquel Raynal 		dev_err(mxic->dev, "Timeout on data xfer completion\n");
44248e6633aSMiquel Raynal 		return -ETIMEDOUT;
44348e6633aSMiquel Raynal 	}
44448e6633aSMiquel Raynal 
44548e6633aSMiquel Raynal 	return 0;
44648e6633aSMiquel Raynal }
44748e6633aSMiquel Raynal 
mxic_ecc_process_data(struct mxic_ecc_engine * mxic,unsigned int direction)44848e6633aSMiquel Raynal static int mxic_ecc_process_data(struct mxic_ecc_engine *mxic,
44948e6633aSMiquel Raynal 				 unsigned int direction)
45048e6633aSMiquel Raynal {
45148e6633aSMiquel Raynal 	unsigned int dir = (direction == NAND_PAGE_READ) ?
45248e6633aSMiquel Raynal 			   READ_NAND : WRITE_NAND;
45348e6633aSMiquel Raynal 	int ret;
45448e6633aSMiquel Raynal 
45548e6633aSMiquel Raynal 	mxic_ecc_enable_engine(mxic);
45648e6633aSMiquel Raynal 
45748e6633aSMiquel Raynal 	/* Trigger processing */
45848e6633aSMiquel Raynal 	writel(SDMA_STRT | dir, mxic->regs + SDMA_CTRL);
45948e6633aSMiquel Raynal 
46048e6633aSMiquel Raynal 	/* Wait for completion */
46148e6633aSMiquel Raynal 	ret = mxic_ecc_data_xfer_wait_for_completion(mxic);
46248e6633aSMiquel Raynal 
46348e6633aSMiquel Raynal 	mxic_ecc_disable_engine(mxic);
46448e6633aSMiquel Raynal 
46548e6633aSMiquel Raynal 	return ret;
46648e6633aSMiquel Raynal }
46748e6633aSMiquel Raynal 
mxic_ecc_process_data_pipelined(struct nand_ecc_engine * eng,unsigned int direction,dma_addr_t dirmap)46870e038f8SMiquel Raynal int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng,
46970e038f8SMiquel Raynal 				    unsigned int direction, dma_addr_t dirmap)
47070e038f8SMiquel Raynal {
47170e038f8SMiquel Raynal 	struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
47270e038f8SMiquel Raynal 
47370e038f8SMiquel Raynal 	if (dirmap)
47470e038f8SMiquel Raynal 		writel(dirmap, mxic->regs + HC_SLV_ADDR);
47570e038f8SMiquel Raynal 
47670e038f8SMiquel Raynal 	return mxic_ecc_process_data(mxic, direction);
47770e038f8SMiquel Raynal }
47870e038f8SMiquel Raynal EXPORT_SYMBOL_GPL(mxic_ecc_process_data_pipelined);
47970e038f8SMiquel Raynal 
mxic_ecc_extract_status_bytes(struct mxic_ecc_ctx * ctx)48048e6633aSMiquel Raynal static void mxic_ecc_extract_status_bytes(struct mxic_ecc_ctx *ctx)
48148e6633aSMiquel Raynal {
48248e6633aSMiquel Raynal 	u8 *buf = ctx->oobwithstat;
48348e6633aSMiquel Raynal 	int next_stat_pos;
48448e6633aSMiquel Raynal 	int step;
48548e6633aSMiquel Raynal 
48648e6633aSMiquel Raynal 	/* Extract the ECC status */
48748e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++) {
48848e6633aSMiquel Raynal 		next_stat_pos = ctx->oob_step_sz +
48948e6633aSMiquel Raynal 				((STAT_BYTES + ctx->oob_step_sz) * step);
49048e6633aSMiquel Raynal 
49148e6633aSMiquel Raynal 		ctx->status[step] = buf[next_stat_pos];
49248e6633aSMiquel Raynal 	}
49348e6633aSMiquel Raynal }
49448e6633aSMiquel Raynal 
mxic_ecc_reconstruct_oobbuf(struct mxic_ecc_ctx * ctx,u8 * dst,const u8 * src)49548e6633aSMiquel Raynal static void mxic_ecc_reconstruct_oobbuf(struct mxic_ecc_ctx *ctx,
49648e6633aSMiquel Raynal 					u8 *dst, const u8 *src)
49748e6633aSMiquel Raynal {
49848e6633aSMiquel Raynal 	int step;
49948e6633aSMiquel Raynal 
50048e6633aSMiquel Raynal 	/* Reconstruct the OOB buffer linearly (without the ECC status bytes) */
50148e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++)
50248e6633aSMiquel Raynal 		memcpy(dst + (step * ctx->oob_step_sz),
50348e6633aSMiquel Raynal 		       src + (step * (ctx->oob_step_sz + STAT_BYTES)),
50448e6633aSMiquel Raynal 		       ctx->oob_step_sz);
50548e6633aSMiquel Raynal }
50648e6633aSMiquel Raynal 
mxic_ecc_add_room_in_oobbuf(struct mxic_ecc_ctx * ctx,u8 * dst,const u8 * src)50748e6633aSMiquel Raynal static void mxic_ecc_add_room_in_oobbuf(struct mxic_ecc_ctx *ctx,
50848e6633aSMiquel Raynal 					u8 *dst, const u8 *src)
50948e6633aSMiquel Raynal {
51048e6633aSMiquel Raynal 	int step;
51148e6633aSMiquel Raynal 
51248e6633aSMiquel Raynal 	/* Add some space in the OOB buffer for the status bytes */
51348e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++)
51448e6633aSMiquel Raynal 		memcpy(dst + (step * (ctx->oob_step_sz + STAT_BYTES)),
51548e6633aSMiquel Raynal 		       src + (step * ctx->oob_step_sz),
51648e6633aSMiquel Raynal 		       ctx->oob_step_sz);
51748e6633aSMiquel Raynal }
51848e6633aSMiquel Raynal 
mxic_ecc_count_biterrs(struct mxic_ecc_engine * mxic,struct nand_device * nand)51948e6633aSMiquel Raynal static int mxic_ecc_count_biterrs(struct mxic_ecc_engine *mxic,
52048e6633aSMiquel Raynal 				  struct nand_device *nand)
52148e6633aSMiquel Raynal {
52248e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
52348e6633aSMiquel Raynal 	struct mtd_info *mtd = nanddev_to_mtd(nand);
52448e6633aSMiquel Raynal 	struct device *dev = mxic->dev;
52548e6633aSMiquel Raynal 	unsigned int max_bf = 0;
52648e6633aSMiquel Raynal 	bool failure = false;
52748e6633aSMiquel Raynal 	int step;
52848e6633aSMiquel Raynal 
52948e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++) {
53048e6633aSMiquel Raynal 		u8 stat = ctx->status[step];
53148e6633aSMiquel Raynal 
53248e6633aSMiquel Raynal 		if (stat == NO_ERR) {
53348e6633aSMiquel Raynal 			dev_dbg(dev, "ECC step %d: no error\n", step);
53448e6633aSMiquel Raynal 		} else if (stat == ERASED_CHUNK) {
53548e6633aSMiquel Raynal 			dev_dbg(dev, "ECC step %d: erased\n", step);
53648e6633aSMiquel Raynal 		} else if (stat == UNCORR_ERR || stat > MAX_CORR_ERR) {
53748e6633aSMiquel Raynal 			dev_dbg(dev, "ECC step %d: uncorrectable\n", step);
53848e6633aSMiquel Raynal 			mtd->ecc_stats.failed++;
53948e6633aSMiquel Raynal 			failure = true;
54048e6633aSMiquel Raynal 		} else {
54148e6633aSMiquel Raynal 			dev_dbg(dev, "ECC step %d: %d bits corrected\n",
54248e6633aSMiquel Raynal 				step, stat);
54348e6633aSMiquel Raynal 			max_bf = max_t(unsigned int, max_bf, stat);
54448e6633aSMiquel Raynal 			mtd->ecc_stats.corrected += stat;
54548e6633aSMiquel Raynal 		}
54648e6633aSMiquel Raynal 	}
54748e6633aSMiquel Raynal 
54848e6633aSMiquel Raynal 	return failure ? -EBADMSG : max_bf;
54948e6633aSMiquel Raynal }
55048e6633aSMiquel Raynal 
55148e6633aSMiquel Raynal /* External ECC engine helpers */
mxic_ecc_prepare_io_req_external(struct nand_device * nand,struct nand_page_io_req * req)55248e6633aSMiquel Raynal static int mxic_ecc_prepare_io_req_external(struct nand_device *nand,
55348e6633aSMiquel Raynal 					    struct nand_page_io_req *req)
55448e6633aSMiquel Raynal {
55548e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
55648e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
55748e6633aSMiquel Raynal 	struct mtd_info *mtd = nanddev_to_mtd(nand);
55848e6633aSMiquel Raynal 	int offset, nents, step, ret;
55948e6633aSMiquel Raynal 
56048e6633aSMiquel Raynal 	if (req->mode == MTD_OPS_RAW)
56148e6633aSMiquel Raynal 		return 0;
56248e6633aSMiquel Raynal 
56348e6633aSMiquel Raynal 	nand_ecc_tweak_req(&ctx->req_ctx, req);
56448e6633aSMiquel Raynal 	ctx->req = req;
56548e6633aSMiquel Raynal 
56648e6633aSMiquel Raynal 	if (req->type == NAND_PAGE_READ)
56748e6633aSMiquel Raynal 		return 0;
56848e6633aSMiquel Raynal 
56948e6633aSMiquel Raynal 	mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat,
57048e6633aSMiquel Raynal 				    ctx->req->oobbuf.out);
57148e6633aSMiquel Raynal 
57248e6633aSMiquel Raynal 	sg_set_buf(&ctx->sg[0], req->databuf.out, req->datalen);
57348e6633aSMiquel Raynal 	sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
57448e6633aSMiquel Raynal 		   req->ooblen + (ctx->steps * STAT_BYTES));
57548e6633aSMiquel Raynal 
57648e6633aSMiquel Raynal 	nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
57748e6633aSMiquel Raynal 	if (!nents)
57848e6633aSMiquel Raynal 		return -EINVAL;
57948e6633aSMiquel Raynal 
58048e6633aSMiquel Raynal 	mutex_lock(&mxic->lock);
58148e6633aSMiquel Raynal 
58248e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++) {
58348e6633aSMiquel Raynal 		writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
58448e6633aSMiquel Raynal 		       mxic->regs + SDMA_MAIN_ADDR);
58548e6633aSMiquel Raynal 		writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
58648e6633aSMiquel Raynal 		       mxic->regs + SDMA_SPARE_ADDR);
58748e6633aSMiquel Raynal 		ret = mxic_ecc_process_data(mxic, ctx->req->type);
58848e6633aSMiquel Raynal 		if (ret)
58948e6633aSMiquel Raynal 			break;
59048e6633aSMiquel Raynal 	}
59148e6633aSMiquel Raynal 
59248e6633aSMiquel Raynal 	mutex_unlock(&mxic->lock);
59348e6633aSMiquel Raynal 
59448e6633aSMiquel Raynal 	dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
59548e6633aSMiquel Raynal 
59648e6633aSMiquel Raynal 	if (ret)
59748e6633aSMiquel Raynal 		return ret;
59848e6633aSMiquel Raynal 
59948e6633aSMiquel Raynal 	/* Retrieve the calculated ECC bytes */
60048e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++) {
60148e6633aSMiquel Raynal 		offset = ctx->meta_sz + (step * ctx->oob_step_sz);
60248e6633aSMiquel Raynal 		mtd_ooblayout_get_eccbytes(mtd,
60348e6633aSMiquel Raynal 					   (u8 *)ctx->req->oobbuf.out + offset,
60448e6633aSMiquel Raynal 					   ctx->oobwithstat + (step * STAT_BYTES),
60548e6633aSMiquel Raynal 					   step * ctx->parity_sz,
60648e6633aSMiquel Raynal 					   ctx->parity_sz);
60748e6633aSMiquel Raynal 	}
60848e6633aSMiquel Raynal 
60948e6633aSMiquel Raynal 	return 0;
61048e6633aSMiquel Raynal }
61148e6633aSMiquel Raynal 
mxic_ecc_finish_io_req_external(struct nand_device * nand,struct nand_page_io_req * req)61248e6633aSMiquel Raynal static int mxic_ecc_finish_io_req_external(struct nand_device *nand,
61348e6633aSMiquel Raynal 					   struct nand_page_io_req *req)
61448e6633aSMiquel Raynal {
61548e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
61648e6633aSMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
61748e6633aSMiquel Raynal 	int nents, step, ret;
61848e6633aSMiquel Raynal 
61948e6633aSMiquel Raynal 	if (req->mode == MTD_OPS_RAW)
62048e6633aSMiquel Raynal 		return 0;
62148e6633aSMiquel Raynal 
62248e6633aSMiquel Raynal 	if (req->type == NAND_PAGE_WRITE) {
62348e6633aSMiquel Raynal 		nand_ecc_restore_req(&ctx->req_ctx, req);
62448e6633aSMiquel Raynal 		return 0;
62548e6633aSMiquel Raynal 	}
62648e6633aSMiquel Raynal 
62748e6633aSMiquel Raynal 	/* Copy the OOB buffer and add room for the ECC engine status bytes */
62848e6633aSMiquel Raynal 	mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
62948e6633aSMiquel Raynal 
63048e6633aSMiquel Raynal 	sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
63148e6633aSMiquel Raynal 	sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
63248e6633aSMiquel Raynal 		   req->ooblen + (ctx->steps * STAT_BYTES));
63348e6633aSMiquel Raynal 	nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
63448e6633aSMiquel Raynal 	if (!nents)
63548e6633aSMiquel Raynal 		return -EINVAL;
63648e6633aSMiquel Raynal 
63748e6633aSMiquel Raynal 	mutex_lock(&mxic->lock);
63848e6633aSMiquel Raynal 
63948e6633aSMiquel Raynal 	for (step = 0; step < ctx->steps; step++) {
64048e6633aSMiquel Raynal 		writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
64148e6633aSMiquel Raynal 		       mxic->regs + SDMA_MAIN_ADDR);
64248e6633aSMiquel Raynal 		writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
64348e6633aSMiquel Raynal 		       mxic->regs + SDMA_SPARE_ADDR);
64448e6633aSMiquel Raynal 		ret = mxic_ecc_process_data(mxic, ctx->req->type);
64548e6633aSMiquel Raynal 		if (ret)
64648e6633aSMiquel Raynal 			break;
64748e6633aSMiquel Raynal 	}
64848e6633aSMiquel Raynal 
64948e6633aSMiquel Raynal 	mutex_unlock(&mxic->lock);
65048e6633aSMiquel Raynal 
65148e6633aSMiquel Raynal 	dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
65248e6633aSMiquel Raynal 
65370e038f8SMiquel Raynal 	if (ret) {
65470e038f8SMiquel Raynal 		nand_ecc_restore_req(&ctx->req_ctx, req);
65570e038f8SMiquel Raynal 		return ret;
65670e038f8SMiquel Raynal 	}
65770e038f8SMiquel Raynal 
65848e6633aSMiquel Raynal 	/* Extract the status bytes and reconstruct the buffer */
65948e6633aSMiquel Raynal 	mxic_ecc_extract_status_bytes(ctx);
66048e6633aSMiquel Raynal 	mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in, ctx->oobwithstat);
66148e6633aSMiquel Raynal 
66248e6633aSMiquel Raynal 	nand_ecc_restore_req(&ctx->req_ctx, req);
66348e6633aSMiquel Raynal 
66448e6633aSMiquel Raynal 	return mxic_ecc_count_biterrs(mxic, nand);
66548e6633aSMiquel Raynal }
66648e6633aSMiquel Raynal 
66770e038f8SMiquel Raynal /* Pipelined ECC engine helpers */
mxic_ecc_prepare_io_req_pipelined(struct nand_device * nand,struct nand_page_io_req * req)66870e038f8SMiquel Raynal static int mxic_ecc_prepare_io_req_pipelined(struct nand_device *nand,
66970e038f8SMiquel Raynal 					     struct nand_page_io_req *req)
67070e038f8SMiquel Raynal {
67170e038f8SMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
67270e038f8SMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
67370e038f8SMiquel Raynal 	int nents;
67470e038f8SMiquel Raynal 
67570e038f8SMiquel Raynal 	if (req->mode == MTD_OPS_RAW)
67670e038f8SMiquel Raynal 		return 0;
67770e038f8SMiquel Raynal 
67870e038f8SMiquel Raynal 	nand_ecc_tweak_req(&ctx->req_ctx, req);
67970e038f8SMiquel Raynal 	ctx->req = req;
68070e038f8SMiquel Raynal 
68170e038f8SMiquel Raynal 	/* Copy the OOB buffer and add room for the ECC engine status bytes */
68270e038f8SMiquel Raynal 	mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
68370e038f8SMiquel Raynal 
68470e038f8SMiquel Raynal 	sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
68570e038f8SMiquel Raynal 	sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
68670e038f8SMiquel Raynal 		   req->ooblen + (ctx->steps * STAT_BYTES));
68770e038f8SMiquel Raynal 
68870e038f8SMiquel Raynal 	nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
68970e038f8SMiquel Raynal 	if (!nents)
69070e038f8SMiquel Raynal 		return -EINVAL;
69170e038f8SMiquel Raynal 
69270e038f8SMiquel Raynal 	mutex_lock(&mxic->lock);
69370e038f8SMiquel Raynal 
69470e038f8SMiquel Raynal 	writel(sg_dma_address(&ctx->sg[0]), mxic->regs + SDMA_MAIN_ADDR);
69570e038f8SMiquel Raynal 	writel(sg_dma_address(&ctx->sg[1]), mxic->regs + SDMA_SPARE_ADDR);
69670e038f8SMiquel Raynal 
69770e038f8SMiquel Raynal 	return 0;
69870e038f8SMiquel Raynal }
69970e038f8SMiquel Raynal 
mxic_ecc_finish_io_req_pipelined(struct nand_device * nand,struct nand_page_io_req * req)70070e038f8SMiquel Raynal static int mxic_ecc_finish_io_req_pipelined(struct nand_device *nand,
70170e038f8SMiquel Raynal 					    struct nand_page_io_req *req)
70270e038f8SMiquel Raynal {
70370e038f8SMiquel Raynal 	struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
70470e038f8SMiquel Raynal 	struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
70570e038f8SMiquel Raynal 	int ret = 0;
70670e038f8SMiquel Raynal 
70770e038f8SMiquel Raynal 	if (req->mode == MTD_OPS_RAW)
70870e038f8SMiquel Raynal 		return 0;
70970e038f8SMiquel Raynal 
71070e038f8SMiquel Raynal 	mutex_unlock(&mxic->lock);
71170e038f8SMiquel Raynal 
71270e038f8SMiquel Raynal 	dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
71370e038f8SMiquel Raynal 
71470e038f8SMiquel Raynal 	if (req->type == NAND_PAGE_READ) {
71570e038f8SMiquel Raynal 		mxic_ecc_extract_status_bytes(ctx);
71670e038f8SMiquel Raynal 		mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in,
71770e038f8SMiquel Raynal 					    ctx->oobwithstat);
71870e038f8SMiquel Raynal 		ret = mxic_ecc_count_biterrs(mxic, nand);
71970e038f8SMiquel Raynal 	}
72070e038f8SMiquel Raynal 
72170e038f8SMiquel Raynal 	nand_ecc_restore_req(&ctx->req_ctx, req);
72270e038f8SMiquel Raynal 
72370e038f8SMiquel Raynal 	return ret;
72470e038f8SMiquel Raynal }
72570e038f8SMiquel Raynal 
72648e6633aSMiquel Raynal static struct nand_ecc_engine_ops mxic_ecc_engine_external_ops = {
72748e6633aSMiquel Raynal 	.init_ctx = mxic_ecc_init_ctx_external,
72848e6633aSMiquel Raynal 	.cleanup_ctx = mxic_ecc_cleanup_ctx,
72948e6633aSMiquel Raynal 	.prepare_io_req = mxic_ecc_prepare_io_req_external,
73048e6633aSMiquel Raynal 	.finish_io_req = mxic_ecc_finish_io_req_external,
73148e6633aSMiquel Raynal };
73248e6633aSMiquel Raynal 
73370e038f8SMiquel Raynal static struct nand_ecc_engine_ops mxic_ecc_engine_pipelined_ops = {
73470e038f8SMiquel Raynal 	.init_ctx = mxic_ecc_init_ctx_pipelined,
73570e038f8SMiquel Raynal 	.cleanup_ctx = mxic_ecc_cleanup_ctx,
73670e038f8SMiquel Raynal 	.prepare_io_req = mxic_ecc_prepare_io_req_pipelined,
73770e038f8SMiquel Raynal 	.finish_io_req = mxic_ecc_finish_io_req_pipelined,
73870e038f8SMiquel Raynal };
73970e038f8SMiquel Raynal 
mxic_ecc_get_pipelined_ops(void)74070e038f8SMiquel Raynal struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void)
74170e038f8SMiquel Raynal {
74270e038f8SMiquel Raynal 	return &mxic_ecc_engine_pipelined_ops;
74370e038f8SMiquel Raynal }
74470e038f8SMiquel Raynal EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_ops);
74570e038f8SMiquel Raynal 
74670e038f8SMiquel Raynal static struct platform_device *
mxic_ecc_get_pdev(struct platform_device * spi_pdev)74770e038f8SMiquel Raynal mxic_ecc_get_pdev(struct platform_device *spi_pdev)
74870e038f8SMiquel Raynal {
74970e038f8SMiquel Raynal 	struct platform_device *eng_pdev;
75070e038f8SMiquel Raynal 	struct device_node *np;
75170e038f8SMiquel Raynal 
75270e038f8SMiquel Raynal 	/* Retrieve the nand-ecc-engine phandle */
75370e038f8SMiquel Raynal 	np = of_parse_phandle(spi_pdev->dev.of_node, "nand-ecc-engine", 0);
75470e038f8SMiquel Raynal 	if (!np)
75570e038f8SMiquel Raynal 		return NULL;
75670e038f8SMiquel Raynal 
75770e038f8SMiquel Raynal 	/* Jump to the engine's device node */
75870e038f8SMiquel Raynal 	eng_pdev = of_find_device_by_node(np);
75970e038f8SMiquel Raynal 	of_node_put(np);
76070e038f8SMiquel Raynal 
76170e038f8SMiquel Raynal 	return eng_pdev;
76270e038f8SMiquel Raynal }
76370e038f8SMiquel Raynal 
mxic_ecc_put_pipelined_engine(struct nand_ecc_engine * eng)76470e038f8SMiquel Raynal void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng)
76570e038f8SMiquel Raynal {
76670e038f8SMiquel Raynal 	struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
76770e038f8SMiquel Raynal 
76870e038f8SMiquel Raynal 	platform_device_put(to_platform_device(mxic->dev));
76970e038f8SMiquel Raynal }
77070e038f8SMiquel Raynal EXPORT_SYMBOL_GPL(mxic_ecc_put_pipelined_engine);
77170e038f8SMiquel Raynal 
77270e038f8SMiquel Raynal struct nand_ecc_engine *
mxic_ecc_get_pipelined_engine(struct platform_device * spi_pdev)77370e038f8SMiquel Raynal mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev)
77470e038f8SMiquel Raynal {
77570e038f8SMiquel Raynal 	struct platform_device *eng_pdev;
77670e038f8SMiquel Raynal 	struct mxic_ecc_engine *mxic;
77770e038f8SMiquel Raynal 
77870e038f8SMiquel Raynal 	eng_pdev = mxic_ecc_get_pdev(spi_pdev);
77970e038f8SMiquel Raynal 	if (!eng_pdev)
78070e038f8SMiquel Raynal 		return ERR_PTR(-ENODEV);
78170e038f8SMiquel Raynal 
78270e038f8SMiquel Raynal 	mxic = platform_get_drvdata(eng_pdev);
78370e038f8SMiquel Raynal 	if (!mxic) {
78470e038f8SMiquel Raynal 		platform_device_put(eng_pdev);
78570e038f8SMiquel Raynal 		return ERR_PTR(-EPROBE_DEFER);
78670e038f8SMiquel Raynal 	}
78770e038f8SMiquel Raynal 
78870e038f8SMiquel Raynal 	return &mxic->pipelined_engine;
78970e038f8SMiquel Raynal }
79070e038f8SMiquel Raynal EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_engine);
79170e038f8SMiquel Raynal 
79270e038f8SMiquel Raynal /*
79370e038f8SMiquel Raynal  * Only the external ECC engine is exported as the pipelined is SoC specific, so
79470e038f8SMiquel Raynal  * it is registered directly by the drivers that wrap it.
79570e038f8SMiquel Raynal  */
mxic_ecc_probe(struct platform_device * pdev)79648e6633aSMiquel Raynal static int mxic_ecc_probe(struct platform_device *pdev)
79748e6633aSMiquel Raynal {
79848e6633aSMiquel Raynal 	struct device *dev = &pdev->dev;
79948e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic;
80048e6633aSMiquel Raynal 	int ret;
80148e6633aSMiquel Raynal 
80248e6633aSMiquel Raynal 	mxic = devm_kzalloc(&pdev->dev, sizeof(*mxic), GFP_KERNEL);
80348e6633aSMiquel Raynal 	if (!mxic)
80448e6633aSMiquel Raynal 		return -ENOMEM;
80548e6633aSMiquel Raynal 
80648e6633aSMiquel Raynal 	mxic->dev = &pdev->dev;
80748e6633aSMiquel Raynal 
80848e6633aSMiquel Raynal 	/*
80948e6633aSMiquel Raynal 	 * Both memory regions for the ECC engine itself and the AXI slave
81048e6633aSMiquel Raynal 	 * address are mandatory.
81148e6633aSMiquel Raynal 	 */
81248e6633aSMiquel Raynal 	mxic->regs = devm_platform_ioremap_resource(pdev, 0);
81348e6633aSMiquel Raynal 	if (IS_ERR(mxic->regs)) {
81448e6633aSMiquel Raynal 		dev_err(&pdev->dev, "Missing memory region\n");
81548e6633aSMiquel Raynal 		return PTR_ERR(mxic->regs);
81648e6633aSMiquel Raynal 	}
81748e6633aSMiquel Raynal 
81848e6633aSMiquel Raynal 	mxic_ecc_disable_engine(mxic);
81948e6633aSMiquel Raynal 	mxic_ecc_disable_int(mxic);
82048e6633aSMiquel Raynal 
82148e6633aSMiquel Raynal 	/* IRQ is optional yet much more efficient */
82248e6633aSMiquel Raynal 	mxic->irq = platform_get_irq_byname_optional(pdev, "ecc-engine");
82348e6633aSMiquel Raynal 	if (mxic->irq > 0) {
82448e6633aSMiquel Raynal 		ret = devm_request_irq(&pdev->dev, mxic->irq, mxic_ecc_isr, 0,
82548e6633aSMiquel Raynal 				       "mxic-ecc", mxic);
82648e6633aSMiquel Raynal 		if (ret)
82748e6633aSMiquel Raynal 			return ret;
82848e6633aSMiquel Raynal 	} else {
82948e6633aSMiquel Raynal 		dev_info(dev, "Invalid or missing IRQ, fallback to polling\n");
83048e6633aSMiquel Raynal 		mxic->irq = 0;
83148e6633aSMiquel Raynal 	}
83248e6633aSMiquel Raynal 
83348e6633aSMiquel Raynal 	mutex_init(&mxic->lock);
83448e6633aSMiquel Raynal 
83548e6633aSMiquel Raynal 	/*
83648e6633aSMiquel Raynal 	 * In external mode, the device is the ECC engine. In pipelined mode,
83748e6633aSMiquel Raynal 	 * the device is the host controller. The device is used to match the
83848e6633aSMiquel Raynal 	 * right ECC engine based on the DT properties.
83948e6633aSMiquel Raynal 	 */
84048e6633aSMiquel Raynal 	mxic->external_engine.dev = &pdev->dev;
84148e6633aSMiquel Raynal 	mxic->external_engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
84248e6633aSMiquel Raynal 	mxic->external_engine.ops = &mxic_ecc_engine_external_ops;
84348e6633aSMiquel Raynal 
84448e6633aSMiquel Raynal 	nand_ecc_register_on_host_hw_engine(&mxic->external_engine);
84548e6633aSMiquel Raynal 
84648e6633aSMiquel Raynal 	platform_set_drvdata(pdev, mxic);
84748e6633aSMiquel Raynal 
84848e6633aSMiquel Raynal 	return 0;
84948e6633aSMiquel Raynal }
85048e6633aSMiquel Raynal 
mxic_ecc_remove(struct platform_device * pdev)851ec185b18SUwe Kleine-König static void mxic_ecc_remove(struct platform_device *pdev)
85248e6633aSMiquel Raynal {
85348e6633aSMiquel Raynal 	struct mxic_ecc_engine *mxic = platform_get_drvdata(pdev);
85448e6633aSMiquel Raynal 
85548e6633aSMiquel Raynal 	nand_ecc_unregister_on_host_hw_engine(&mxic->external_engine);
85648e6633aSMiquel Raynal }
85748e6633aSMiquel Raynal 
85848e6633aSMiquel Raynal static const struct of_device_id mxic_ecc_of_ids[] = {
85948e6633aSMiquel Raynal 	{
86048e6633aSMiquel Raynal 		.compatible = "mxicy,nand-ecc-engine-rev3",
86148e6633aSMiquel Raynal 	},
86248e6633aSMiquel Raynal 	{ /* sentinel */ },
86348e6633aSMiquel Raynal };
86448e6633aSMiquel Raynal MODULE_DEVICE_TABLE(of, mxic_ecc_of_ids);
86548e6633aSMiquel Raynal 
86648e6633aSMiquel Raynal static struct platform_driver mxic_ecc_driver = {
86748e6633aSMiquel Raynal 	.driver	= {
86848e6633aSMiquel Raynal 		.name = "mxic-nand-ecc-engine",
86948e6633aSMiquel Raynal 		.of_match_table = mxic_ecc_of_ids,
87048e6633aSMiquel Raynal 	},
87148e6633aSMiquel Raynal 	.probe = mxic_ecc_probe,
872ec185b18SUwe Kleine-König 	.remove_new = mxic_ecc_remove,
87348e6633aSMiquel Raynal };
87448e6633aSMiquel Raynal module_platform_driver(mxic_ecc_driver);
87548e6633aSMiquel Raynal 
87648e6633aSMiquel Raynal MODULE_LICENSE("GPL");
87748e6633aSMiquel Raynal MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
87848e6633aSMiquel Raynal MODULE_DESCRIPTION("Macronix NAND hardware ECC controller");
879