diff options
Diffstat (limited to 'drivers/mtd/nand/ecc.c')
-rw-r--r-- | drivers/mtd/nand/ecc.c | 140 |
1 files changed, 136 insertions, 4 deletions
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c index 4a56e6c0da67..6c43dfda01d4 100644 --- a/drivers/mtd/nand/ecc.c +++ b/drivers/mtd/nand/ecc.c @@ -95,6 +95,7 @@ #include <linux/module.h> #include <linux/mtd/nand.h> +#include <linux/slab.h> /** * nand_ecc_init_ctx - Init the ECC engine context @@ -104,7 +105,7 @@ */ int nand_ecc_init_ctx(struct nand_device *nand) { - if (!nand->ecc.engine->ops->init_ctx) + if (!nand->ecc.engine || !nand->ecc.engine->ops->init_ctx) return 0; return nand->ecc.engine->ops->init_ctx(nand); @@ -117,7 +118,7 @@ EXPORT_SYMBOL(nand_ecc_init_ctx); */ void nand_ecc_cleanup_ctx(struct nand_device *nand) { - if (nand->ecc.engine->ops->cleanup_ctx) + if (nand->ecc.engine && nand->ecc.engine->ops->cleanup_ctx) nand->ecc.engine->ops->cleanup_ctx(nand); } EXPORT_SYMBOL(nand_ecc_cleanup_ctx); @@ -130,7 +131,7 @@ EXPORT_SYMBOL(nand_ecc_cleanup_ctx); int nand_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req) { - if (!nand->ecc.engine->ops->prepare_io_req) + if (!nand->ecc.engine || !nand->ecc.engine->ops->prepare_io_req) return 0; return nand->ecc.engine->ops->prepare_io_req(nand, req); @@ -145,7 +146,7 @@ EXPORT_SYMBOL(nand_ecc_prepare_io_req); int nand_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req) { - if (!nand->ecc.engine->ops->finish_io_req) + if (!nand->ecc.engine || !nand->ecc.engine->ops->finish_io_req) return 0; return nand->ecc.engine->ops->finish_io_req(nand, req); @@ -479,6 +480,137 @@ bool nand_ecc_is_strong_enough(struct nand_device *nand) } EXPORT_SYMBOL(nand_ecc_is_strong_enough); +/* ECC engine driver internal helpers */ +int nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_device *nand) +{ + unsigned int total_buffer_size; + + ctx->nand = nand; + + /* Let the user decide the exact length of each buffer */ + if (!ctx->page_buffer_size) + ctx->page_buffer_size = nanddev_page_size(nand); + if (!ctx->oob_buffer_size) + ctx->oob_buffer_size = nanddev_per_page_oobsize(nand); + + total_buffer_size = ctx->page_buffer_size + ctx->oob_buffer_size; + + ctx->spare_databuf = kzalloc(total_buffer_size, GFP_KERNEL); + if (!ctx->spare_databuf) + return -ENOMEM; + + ctx->spare_oobbuf = ctx->spare_databuf + ctx->page_buffer_size; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_ecc_init_req_tweaking); + +void nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx) +{ + kfree(ctx->spare_databuf); +} +EXPORT_SYMBOL_GPL(nand_ecc_cleanup_req_tweaking); + +/* + * Ensure data and OOB area is fully read/written otherwise the correction might + * not work as expected. + */ +void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_page_io_req *req) +{ + struct nand_device *nand = ctx->nand; + struct nand_page_io_req *orig, *tweak; + + /* Save the original request */ + ctx->orig_req = *req; + ctx->bounce_data = false; + ctx->bounce_oob = false; + orig = &ctx->orig_req; + tweak = req; + + /* Ensure the request covers the entire page */ + if (orig->datalen < nanddev_page_size(nand)) { + ctx->bounce_data = true; + tweak->dataoffs = 0; + tweak->datalen = nanddev_page_size(nand); + tweak->databuf.in = ctx->spare_databuf; + memset(tweak->databuf.in, 0xFF, ctx->page_buffer_size); + } + + if (orig->ooblen < nanddev_per_page_oobsize(nand)) { + ctx->bounce_oob = true; + tweak->ooboffs = 0; + tweak->ooblen = nanddev_per_page_oobsize(nand); + tweak->oobbuf.in = ctx->spare_oobbuf; + memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size); + } + + /* Copy the data that must be writen in the bounce buffers, if needed */ + if (orig->type == NAND_PAGE_WRITE) { + if (ctx->bounce_data) + memcpy((void *)tweak->databuf.out + orig->dataoffs, + orig->databuf.out, orig->datalen); + + if (ctx->bounce_oob) + memcpy((void *)tweak->oobbuf.out + orig->ooboffs, + orig->oobbuf.out, orig->ooblen); + } +} +EXPORT_SYMBOL_GPL(nand_ecc_tweak_req); + +void nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_page_io_req *req) +{ + struct nand_page_io_req *orig, *tweak; + + orig = &ctx->orig_req; + tweak = req; + + /* Restore the data read from the bounce buffers, if needed */ + if (orig->type == NAND_PAGE_READ) { + if (ctx->bounce_data) + memcpy(orig->databuf.in, + tweak->databuf.in + orig->dataoffs, + orig->datalen); + + if (ctx->bounce_oob) + memcpy(orig->oobbuf.in, + tweak->oobbuf.in + orig->ooboffs, + orig->ooblen); + } + + /* Ensure the original request is restored */ + *req = *orig; +} +EXPORT_SYMBOL_GPL(nand_ecc_restore_req); + +struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand) +{ + unsigned int algo = nand->ecc.user_conf.algo; + + if (algo == NAND_ECC_ALGO_UNKNOWN) + algo = nand->ecc.defaults.algo; + + switch (algo) { + case NAND_ECC_ALGO_HAMMING: + return nand_ecc_sw_hamming_get_engine(); + case NAND_ECC_ALGO_BCH: + return nand_ecc_sw_bch_get_engine(); + default: + break; + } + + return NULL; +} +EXPORT_SYMBOL(nand_ecc_get_sw_engine); + +struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand) +{ + return nand->ecc.ondie_engine; +} +EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>"); MODULE_DESCRIPTION("Generic ECC engine"); |