| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Support for Macronix external hardware ECC engine for NAND devices, also
- * called DPE for Data Processing Engine.
- *
- * Copyright © 2019 Macronix
- * Author: Miquel Raynal <miquel.raynal@bootlin.com>
- */
- #include <linux/dma-mapping.h>
- #include <linux/init.h>
- #include <linux/interrupt.h>
- #include <linux/io.h>
- #include <linux/iopoll.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/mtd/mtd.h>
- #include <linux/mtd/nand.h>
- #include <linux/mtd/nand-ecc-mxic.h>
- #include <linux/mutex.h>
- #include <linux/of.h>
- #include <linux/of_platform.h>
- #include <linux/platform_device.h>
- #include <linux/slab.h>
- /* DPE Configuration */
- #define DP_CONFIG 0x00
- #define ECC_EN BIT(0)
- #define ECC_TYP(idx) (((idx) << 3) & GENMASK(6, 3))
- /* DPE Interrupt Status */
- #define INTRPT_STS 0x04
- #define TRANS_CMPLT BIT(0)
- #define SDMA_MAIN BIT(1)
- #define SDMA_SPARE BIT(2)
- #define ECC_ERR BIT(3)
- #define TO_SPARE BIT(4)
- #define TO_MAIN BIT(5)
- /* DPE Interrupt Status Enable */
- #define INTRPT_STS_EN 0x08
- /* DPE Interrupt Signal Enable */
- #define INTRPT_SIG_EN 0x0C
- /* Host Controller Configuration */
- #define HC_CONFIG 0x10
- #define DEV2MEM 0 /* TRANS_TYP_DMA in the spec */
- #define MEM2MEM BIT(4) /* TRANS_TYP_IO in the spec */
- #define MAPPING BIT(5) /* TRANS_TYP_MAPPING in the spec */
- #define ECC_PACKED 0 /* LAYOUT_TYP_INTEGRATED in the spec */
- #define ECC_INTERLEAVED BIT(2) /* LAYOUT_TYP_DISTRIBUTED in the spec */
- #define BURST_TYP_FIXED 0
- #define BURST_TYP_INCREASING BIT(0)
- /* Host Controller Slave Address */
- #define HC_SLV_ADDR 0x14
- /* ECC Chunk Size */
- #define CHUNK_SIZE 0x20
- /* Main Data Size */
- #define MAIN_SIZE 0x24
- /* Spare Data Size */
- #define SPARE_SIZE 0x28
- #define META_SZ(reg) ((reg) & GENMASK(7, 0))
- #define PARITY_SZ(reg) (((reg) & GENMASK(15, 8)) >> 8)
- #define RSV_SZ(reg) (((reg) & GENMASK(23, 16)) >> 16)
- #define SPARE_SZ(reg) ((reg) >> 24)
- /* ECC Chunk Count */
- #define CHUNK_CNT 0x30
- /* SDMA Control */
- #define SDMA_CTRL 0x40
- #define WRITE_NAND 0
- #define READ_NAND BIT(1)
- #define CONT_NAND BIT(29)
- #define CONT_SYSM BIT(30) /* Continue System Memory? */
- #define SDMA_STRT BIT(31)
- /* SDMA Address of Main Data */
- #define SDMA_MAIN_ADDR 0x44
- /* SDMA Address of Spare Data */
- #define SDMA_SPARE_ADDR 0x48
- /* DPE Version Number */
- #define DP_VER 0xD0
- #define DP_VER_OFFSET 16
- /* Status bytes between each chunk of spare data */
- #define STAT_BYTES 4
- #define NO_ERR 0x00
- #define MAX_CORR_ERR 0x28
- #define UNCORR_ERR 0xFE
- #define ERASED_CHUNK 0xFF
- struct mxic_ecc_engine {
- struct device *dev;
- void __iomem *regs;
- int irq;
- struct completion complete;
- struct nand_ecc_engine external_engine;
- struct nand_ecc_engine pipelined_engine;
- struct mutex lock;
- };
- struct mxic_ecc_ctx {
- /* ECC machinery */
- unsigned int data_step_sz;
- unsigned int oob_step_sz;
- unsigned int parity_sz;
- unsigned int meta_sz;
- u8 *status;
- int steps;
- /* DMA boilerplate */
- struct nand_ecc_req_tweak_ctx req_ctx;
- u8 *oobwithstat;
- struct scatterlist sg[2];
- struct nand_page_io_req *req;
- unsigned int pageoffs;
- };
- static struct mxic_ecc_engine *ext_ecc_eng_to_mxic(struct nand_ecc_engine *eng)
- {
- return container_of(eng, struct mxic_ecc_engine, external_engine);
- }
- static struct mxic_ecc_engine *pip_ecc_eng_to_mxic(struct nand_ecc_engine *eng)
- {
- return container_of(eng, struct mxic_ecc_engine, pipelined_engine);
- }
- static struct mxic_ecc_engine *nand_to_mxic(struct nand_device *nand)
- {
- struct nand_ecc_engine *eng = nand->ecc.engine;
- if (eng->integration == NAND_ECC_ENGINE_INTEGRATION_EXTERNAL)
- return ext_ecc_eng_to_mxic(eng);
- else
- return pip_ecc_eng_to_mxic(eng);
- }
- static int mxic_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
- struct mtd_oob_region *oobregion)
- {
- struct nand_device *nand = mtd_to_nanddev(mtd);
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- if (section < 0 || section >= ctx->steps)
- return -ERANGE;
- oobregion->offset = (section * ctx->oob_step_sz) + ctx->meta_sz;
- oobregion->length = ctx->parity_sz;
- return 0;
- }
- static int mxic_ecc_ooblayout_free(struct mtd_info *mtd, int section,
- struct mtd_oob_region *oobregion)
- {
- struct nand_device *nand = mtd_to_nanddev(mtd);
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- if (section < 0 || section >= ctx->steps)
- return -ERANGE;
- if (!section) {
- oobregion->offset = 2;
- oobregion->length = ctx->meta_sz - 2;
- } else {
- oobregion->offset = section * ctx->oob_step_sz;
- oobregion->length = ctx->meta_sz;
- }
- return 0;
- }
- static const struct mtd_ooblayout_ops mxic_ecc_ooblayout_ops = {
- .ecc = mxic_ecc_ooblayout_ecc,
- .free = mxic_ecc_ooblayout_free,
- };
- static void mxic_ecc_disable_engine(struct mxic_ecc_engine *mxic)
- {
- u32 reg;
- reg = readl(mxic->regs + DP_CONFIG);
- reg &= ~ECC_EN;
- writel(reg, mxic->regs + DP_CONFIG);
- }
- static void mxic_ecc_enable_engine(struct mxic_ecc_engine *mxic)
- {
- u32 reg;
- reg = readl(mxic->regs + DP_CONFIG);
- reg |= ECC_EN;
- writel(reg, mxic->regs + DP_CONFIG);
- }
- static void mxic_ecc_disable_int(struct mxic_ecc_engine *mxic)
- {
- writel(0, mxic->regs + INTRPT_SIG_EN);
- }
- static void mxic_ecc_enable_int(struct mxic_ecc_engine *mxic)
- {
- writel(TRANS_CMPLT, mxic->regs + INTRPT_SIG_EN);
- }
- static irqreturn_t mxic_ecc_isr(int irq, void *dev_id)
- {
- struct mxic_ecc_engine *mxic = dev_id;
- u32 sts;
- sts = readl(mxic->regs + INTRPT_STS);
- if (!sts)
- return IRQ_NONE;
- if (sts & TRANS_CMPLT)
- complete(&mxic->complete);
- writel(sts, mxic->regs + INTRPT_STS);
- return IRQ_HANDLED;
- }
- static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
- struct nand_ecc_props *reqs = &nand->ecc.requirements;
- struct nand_ecc_props *user = &nand->ecc.user_conf;
- struct mtd_info *mtd = nanddev_to_mtd(nand);
- int step_size = 0, strength = 0, desired_correction = 0, steps, idx;
- static const int possible_strength[] = {4, 8, 40, 48};
- static const int spare_size[] = {32, 32, 96, 96};
- struct mxic_ecc_ctx *ctx;
- u32 spare_reg;
- int ret;
- ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
- if (!ctx)
- return -ENOMEM;
- nand->ecc.ctx.priv = ctx;
- /* Only large page NAND chips may use BCH */
- if (mtd->oobsize < 64) {
- pr_err("BCH cannot be used with small page NAND chips\n");
- return -EINVAL;
- }
- mtd_set_ooblayout(mtd, &mxic_ecc_ooblayout_ops);
- /* Enable all status bits */
- writel(TRANS_CMPLT | SDMA_MAIN | SDMA_SPARE | ECC_ERR |
- TO_SPARE | TO_MAIN, mxic->regs + INTRPT_STS_EN);
- /* Configure the correction depending on the NAND device topology */
- if (user->step_size && user->strength) {
- step_size = user->step_size;
- strength = user->strength;
- } else if (reqs->step_size && reqs->strength) {
- step_size = reqs->step_size;
- strength = reqs->strength;
- }
- if (step_size && strength) {
- steps = mtd->writesize / step_size;
- desired_correction = steps * strength;
- }
- /* Step size is fixed to 1kiB, strength may vary (4 possible values) */
- conf->step_size = SZ_1K;
- steps = mtd->writesize / conf->step_size;
- ctx->status = devm_kzalloc(dev, steps * sizeof(u8), GFP_KERNEL);
- if (!ctx->status)
- return -ENOMEM;
- if (desired_correction) {
- strength = desired_correction / steps;
- for (idx = 0; idx < ARRAY_SIZE(possible_strength); idx++)
- if (possible_strength[idx] >= strength)
- break;
- idx = min_t(unsigned int, idx,
- ARRAY_SIZE(possible_strength) - 1);
- } else {
- /* Missing data, maximize the correction */
- idx = ARRAY_SIZE(possible_strength) - 1;
- }
- /* Tune the selected strength until it fits in the OOB area */
- for (; idx >= 0; idx--) {
- if (spare_size[idx] * steps <= mtd->oobsize)
- break;
- }
- /* This engine cannot be used with this NAND device */
- if (idx < 0)
- return -EINVAL;
- /* Configure the engine for the desired strength */
- writel(ECC_TYP(idx), mxic->regs + DP_CONFIG);
- conf->strength = possible_strength[idx];
- spare_reg = readl(mxic->regs + SPARE_SIZE);
- ctx->steps = steps;
- ctx->data_step_sz = mtd->writesize / steps;
- ctx->oob_step_sz = mtd->oobsize / steps;
- ctx->parity_sz = PARITY_SZ(spare_reg);
- ctx->meta_sz = META_SZ(spare_reg);
- /* Ensure buffers will contain enough bytes to store the STAT_BYTES */
- ctx->req_ctx.oob_buffer_size = nanddev_per_page_oobsize(nand) +
- (ctx->steps * STAT_BYTES);
- ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
- if (ret)
- return ret;
- ctx->oobwithstat = kmalloc(mtd->oobsize + (ctx->steps * STAT_BYTES),
- GFP_KERNEL);
- if (!ctx->oobwithstat) {
- ret = -ENOMEM;
- goto cleanup_req_tweak;
- }
- sg_init_table(ctx->sg, 2);
- /* Configuration dump and sanity checks */
- dev_err(dev, "DPE version number: %d\n",
- readl(mxic->regs + DP_VER) >> DP_VER_OFFSET);
- dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
- dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
- dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
- dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
- dev_err(dev, "Parity size: %d\n", ctx->parity_sz);
- dev_err(dev, "Meta size: %d\n", ctx->meta_sz);
- if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) !=
- SPARE_SZ(spare_reg)) {
- dev_err(dev, "Wrong OOB configuration: %d + %d + %ld != %d\n",
- ctx->meta_sz, ctx->parity_sz, RSV_SZ(spare_reg),
- SPARE_SZ(spare_reg));
- ret = -EINVAL;
- goto free_oobwithstat;
- }
- if (ctx->oob_step_sz != SPARE_SZ(spare_reg)) {
- dev_err(dev, "Wrong OOB configuration: %d != %d\n",
- ctx->oob_step_sz, SPARE_SZ(spare_reg));
- ret = -EINVAL;
- goto free_oobwithstat;
- }
- return 0;
- free_oobwithstat:
- kfree(ctx->oobwithstat);
- cleanup_req_tweak:
- nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
- return ret;
- }
- static int mxic_ecc_init_ctx_external(struct nand_device *nand)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct device *dev = nand->ecc.engine->dev;
- int ret;
- dev_info(dev, "Macronix ECC engine in external mode\n");
- ret = mxic_ecc_init_ctx(nand, dev);
- if (ret)
- return ret;
- /* Trigger each step manually */
- writel(1, mxic->regs + CHUNK_CNT);
- writel(BURST_TYP_INCREASING | ECC_PACKED | MEM2MEM,
- mxic->regs + HC_CONFIG);
- return 0;
- }
- static int mxic_ecc_init_ctx_pipelined(struct nand_device *nand)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct mxic_ecc_ctx *ctx;
- struct device *dev;
- int ret;
- dev = nand_ecc_get_engine_dev(nand->ecc.engine->dev);
- if (!dev)
- return -EINVAL;
- dev_info(dev, "Macronix ECC engine in pipelined/mapping mode\n");
- ret = mxic_ecc_init_ctx(nand, dev);
- if (ret)
- return ret;
- ctx = nand_to_ecc_ctx(nand);
- /* All steps should be handled in one go directly by the internal DMA */
- writel(ctx->steps, mxic->regs + CHUNK_CNT);
- /*
- * Interleaved ECC scheme cannot be used otherwise factory bad block
- * markers would be lost. A packed layout is mandatory.
- */
- writel(BURST_TYP_INCREASING | ECC_PACKED | MAPPING,
- mxic->regs + HC_CONFIG);
- return 0;
- }
- static void mxic_ecc_cleanup_ctx(struct nand_device *nand)
- {
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- if (ctx) {
- nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
- kfree(ctx->oobwithstat);
- }
- }
- static int mxic_ecc_data_xfer_wait_for_completion(struct mxic_ecc_engine *mxic)
- {
- u32 val;
- int ret;
- if (mxic->irq) {
- reinit_completion(&mxic->complete);
- mxic_ecc_enable_int(mxic);
- ret = wait_for_completion_timeout(&mxic->complete,
- msecs_to_jiffies(1000));
- ret = ret ? 0 : -ETIMEDOUT;
- mxic_ecc_disable_int(mxic);
- } else {
- ret = readl_poll_timeout(mxic->regs + INTRPT_STS, val,
- val & TRANS_CMPLT, 10, USEC_PER_SEC);
- writel(val, mxic->regs + INTRPT_STS);
- }
- if (ret) {
- dev_err(mxic->dev, "Timeout on data xfer completion\n");
- return -ETIMEDOUT;
- }
- return 0;
- }
- static int mxic_ecc_process_data(struct mxic_ecc_engine *mxic,
- unsigned int direction)
- {
- unsigned int dir = (direction == NAND_PAGE_READ) ?
- READ_NAND : WRITE_NAND;
- int ret;
- mxic_ecc_enable_engine(mxic);
- /* Trigger processing */
- writel(SDMA_STRT | dir, mxic->regs + SDMA_CTRL);
- /* Wait for completion */
- ret = mxic_ecc_data_xfer_wait_for_completion(mxic);
- mxic_ecc_disable_engine(mxic);
- return ret;
- }
- int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng,
- unsigned int direction, dma_addr_t dirmap)
- {
- struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
- if (dirmap)
- writel(dirmap, mxic->regs + HC_SLV_ADDR);
- return mxic_ecc_process_data(mxic, direction);
- }
- EXPORT_SYMBOL_GPL(mxic_ecc_process_data_pipelined);
- static void mxic_ecc_extract_status_bytes(struct mxic_ecc_ctx *ctx)
- {
- u8 *buf = ctx->oobwithstat;
- int next_stat_pos;
- int step;
- /* Extract the ECC status */
- for (step = 0; step < ctx->steps; step++) {
- next_stat_pos = ctx->oob_step_sz +
- ((STAT_BYTES + ctx->oob_step_sz) * step);
- ctx->status[step] = buf[next_stat_pos];
- }
- }
- static void mxic_ecc_reconstruct_oobbuf(struct mxic_ecc_ctx *ctx,
- u8 *dst, const u8 *src)
- {
- int step;
- /* Reconstruct the OOB buffer linearly (without the ECC status bytes) */
- for (step = 0; step < ctx->steps; step++)
- memcpy(dst + (step * ctx->oob_step_sz),
- src + (step * (ctx->oob_step_sz + STAT_BYTES)),
- ctx->oob_step_sz);
- }
- static void mxic_ecc_add_room_in_oobbuf(struct mxic_ecc_ctx *ctx,
- u8 *dst, const u8 *src)
- {
- int step;
- /* Add some space in the OOB buffer for the status bytes */
- for (step = 0; step < ctx->steps; step++)
- memcpy(dst + (step * (ctx->oob_step_sz + STAT_BYTES)),
- src + (step * ctx->oob_step_sz),
- ctx->oob_step_sz);
- }
- static int mxic_ecc_count_biterrs(struct mxic_ecc_engine *mxic,
- struct nand_device *nand)
- {
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- struct mtd_info *mtd = nanddev_to_mtd(nand);
- struct device *dev = mxic->dev;
- unsigned int max_bf = 0;
- bool failure = false;
- int step;
- for (step = 0; step < ctx->steps; step++) {
- u8 stat = ctx->status[step];
- if (stat == NO_ERR) {
- dev_dbg(dev, "ECC step %d: no error\n", step);
- } else if (stat == ERASED_CHUNK) {
- dev_dbg(dev, "ECC step %d: erased\n", step);
- } else if (stat == UNCORR_ERR || stat > MAX_CORR_ERR) {
- dev_dbg(dev, "ECC step %d: uncorrectable\n", step);
- mtd->ecc_stats.failed++;
- failure = true;
- } else {
- dev_dbg(dev, "ECC step %d: %d bits corrected\n",
- step, stat);
- max_bf = max_t(unsigned int, max_bf, stat);
- mtd->ecc_stats.corrected += stat;
- }
- }
- return failure ? -EBADMSG : max_bf;
- }
- /* External ECC engine helpers */
- static int mxic_ecc_prepare_io_req_external(struct nand_device *nand,
- struct nand_page_io_req *req)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- struct mtd_info *mtd = nanddev_to_mtd(nand);
- int offset, nents, step, ret;
- if (req->mode == MTD_OPS_RAW)
- return 0;
- nand_ecc_tweak_req(&ctx->req_ctx, req);
- ctx->req = req;
- if (req->type == NAND_PAGE_READ)
- return 0;
- mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat,
- ctx->req->oobbuf.out);
- sg_set_buf(&ctx->sg[0], req->databuf.out, req->datalen);
- sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
- req->ooblen + (ctx->steps * STAT_BYTES));
- nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
- if (!nents)
- return -EINVAL;
- mutex_lock(&mxic->lock);
- for (step = 0; step < ctx->steps; step++) {
- writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
- mxic->regs + SDMA_MAIN_ADDR);
- writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
- mxic->regs + SDMA_SPARE_ADDR);
- ret = mxic_ecc_process_data(mxic, ctx->req->type);
- if (ret)
- break;
- }
- mutex_unlock(&mxic->lock);
- dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
- if (ret)
- return ret;
- /* Retrieve the calculated ECC bytes */
- for (step = 0; step < ctx->steps; step++) {
- offset = ctx->meta_sz + (step * ctx->oob_step_sz);
- mtd_ooblayout_get_eccbytes(mtd,
- (u8 *)ctx->req->oobbuf.out + offset,
- ctx->oobwithstat + (step * STAT_BYTES),
- step * ctx->parity_sz,
- ctx->parity_sz);
- }
- return 0;
- }
- static int mxic_ecc_finish_io_req_external(struct nand_device *nand,
- struct nand_page_io_req *req)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- int nents, step, ret = 0;
- if (req->mode == MTD_OPS_RAW)
- return 0;
- if (req->type == NAND_PAGE_WRITE) {
- nand_ecc_restore_req(&ctx->req_ctx, req);
- return 0;
- }
- /* Copy the OOB buffer and add room for the ECC engine status bytes */
- mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
- sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
- sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
- req->ooblen + (ctx->steps * STAT_BYTES));
- nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
- if (!nents)
- return -EINVAL;
- mutex_lock(&mxic->lock);
- for (step = 0; step < ctx->steps; step++) {
- writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
- mxic->regs + SDMA_MAIN_ADDR);
- writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
- mxic->regs + SDMA_SPARE_ADDR);
- ret = mxic_ecc_process_data(mxic, ctx->req->type);
- if (ret)
- break;
- }
- mutex_unlock(&mxic->lock);
- dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
- if (ret) {
- nand_ecc_restore_req(&ctx->req_ctx, req);
- return ret;
- }
- /* Extract the status bytes and reconstruct the buffer */
- mxic_ecc_extract_status_bytes(ctx);
- mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in, ctx->oobwithstat);
- nand_ecc_restore_req(&ctx->req_ctx, req);
- return mxic_ecc_count_biterrs(mxic, nand);
- }
- /* Pipelined ECC engine helpers */
- static int mxic_ecc_prepare_io_req_pipelined(struct nand_device *nand,
- struct nand_page_io_req *req)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- int nents;
- if (req->mode == MTD_OPS_RAW)
- return 0;
- nand_ecc_tweak_req(&ctx->req_ctx, req);
- ctx->req = req;
- /* Copy the OOB buffer and add room for the ECC engine status bytes */
- mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
- sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
- sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
- req->ooblen + (ctx->steps * STAT_BYTES));
- nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
- if (!nents)
- return -EINVAL;
- mutex_lock(&mxic->lock);
- writel(sg_dma_address(&ctx->sg[0]), mxic->regs + SDMA_MAIN_ADDR);
- writel(sg_dma_address(&ctx->sg[1]), mxic->regs + SDMA_SPARE_ADDR);
- return 0;
- }
- static int mxic_ecc_finish_io_req_pipelined(struct nand_device *nand,
- struct nand_page_io_req *req)
- {
- struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
- struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- int ret = 0;
- if (req->mode == MTD_OPS_RAW)
- return 0;
- mutex_unlock(&mxic->lock);
- dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
- if (req->type == NAND_PAGE_READ) {
- mxic_ecc_extract_status_bytes(ctx);
- mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in,
- ctx->oobwithstat);
- ret = mxic_ecc_count_biterrs(mxic, nand);
- }
- nand_ecc_restore_req(&ctx->req_ctx, req);
- return ret;
- }
- static struct nand_ecc_engine_ops mxic_ecc_engine_external_ops = {
- .init_ctx = mxic_ecc_init_ctx_external,
- .cleanup_ctx = mxic_ecc_cleanup_ctx,
- .prepare_io_req = mxic_ecc_prepare_io_req_external,
- .finish_io_req = mxic_ecc_finish_io_req_external,
- };
- static struct nand_ecc_engine_ops mxic_ecc_engine_pipelined_ops = {
- .init_ctx = mxic_ecc_init_ctx_pipelined,
- .cleanup_ctx = mxic_ecc_cleanup_ctx,
- .prepare_io_req = mxic_ecc_prepare_io_req_pipelined,
- .finish_io_req = mxic_ecc_finish_io_req_pipelined,
- };
- struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void)
- {
- return &mxic_ecc_engine_pipelined_ops;
- }
- EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_ops);
- static struct platform_device *
- mxic_ecc_get_pdev(struct platform_device *spi_pdev)
- {
- struct platform_device *eng_pdev;
- struct device_node *np;
- /* Retrieve the nand-ecc-engine phandle */
- np = of_parse_phandle(spi_pdev->dev.of_node, "nand-ecc-engine", 0);
- if (!np)
- return NULL;
- /* Jump to the engine's device node */
- eng_pdev = of_find_device_by_node(np);
- of_node_put(np);
- return eng_pdev;
- }
- void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng)
- {
- struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
- platform_device_put(to_platform_device(mxic->dev));
- }
- EXPORT_SYMBOL_GPL(mxic_ecc_put_pipelined_engine);
- struct nand_ecc_engine *
- mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev)
- {
- struct platform_device *eng_pdev;
- struct mxic_ecc_engine *mxic;
- eng_pdev = mxic_ecc_get_pdev(spi_pdev);
- if (!eng_pdev)
- return ERR_PTR(-ENODEV);
- mxic = platform_get_drvdata(eng_pdev);
- if (!mxic) {
- platform_device_put(eng_pdev);
- return ERR_PTR(-EPROBE_DEFER);
- }
- return &mxic->pipelined_engine;
- }
- EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_engine);
- /*
- * Only the external ECC engine is exported as the pipelined is SoC specific, so
- * it is registered directly by the drivers that wrap it.
- */
- static int mxic_ecc_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct mxic_ecc_engine *mxic;
- int ret;
- mxic = devm_kzalloc(&pdev->dev, sizeof(*mxic), GFP_KERNEL);
- if (!mxic)
- return -ENOMEM;
- mxic->dev = &pdev->dev;
- /*
- * Both memory regions for the ECC engine itself and the AXI slave
- * address are mandatory.
- */
- mxic->regs = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(mxic->regs)) {
- dev_err(&pdev->dev, "Missing memory region\n");
- return PTR_ERR(mxic->regs);
- }
- mxic_ecc_disable_engine(mxic);
- mxic_ecc_disable_int(mxic);
- /* IRQ is optional yet much more efficient */
- mxic->irq = platform_get_irq_byname_optional(pdev, "ecc-engine");
- if (mxic->irq > 0) {
- ret = devm_request_irq(&pdev->dev, mxic->irq, mxic_ecc_isr, 0,
- "mxic-ecc", mxic);
- if (ret)
- return ret;
- } else {
- dev_info(dev, "Invalid or missing IRQ, fallback to polling\n");
- mxic->irq = 0;
- }
- mutex_init(&mxic->lock);
- /*
- * In external mode, the device is the ECC engine. In pipelined mode,
- * the device is the host controller. The device is used to match the
- * right ECC engine based on the DT properties.
- */
- mxic->external_engine.dev = &pdev->dev;
- mxic->external_engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
- mxic->external_engine.ops = &mxic_ecc_engine_external_ops;
- nand_ecc_register_on_host_hw_engine(&mxic->external_engine);
- platform_set_drvdata(pdev, mxic);
- return 0;
- }
- static void mxic_ecc_remove(struct platform_device *pdev)
- {
- struct mxic_ecc_engine *mxic = platform_get_drvdata(pdev);
- nand_ecc_unregister_on_host_hw_engine(&mxic->external_engine);
- }
- static const struct of_device_id mxic_ecc_of_ids[] = {
- {
- .compatible = "mxicy,nand-ecc-engine-rev3",
- },
- { /* sentinel */ },
- };
- MODULE_DEVICE_TABLE(of, mxic_ecc_of_ids);
- static struct platform_driver mxic_ecc_driver = {
- .driver = {
- .name = "mxic-nand-ecc-engine",
- .of_match_table = mxic_ecc_of_ids,
- },
- .probe = mxic_ecc_probe,
- .remove_new = mxic_ecc_remove,
- };
- module_platform_driver(mxic_ecc_driver);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
- MODULE_DESCRIPTION("Macronix NAND hardware ECC controller");
|