diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c index 4a56e6c0da67297f4bd10ad4b35ed804999816bc..541137736fd3c1934db026953b028b9636466566 100644 --- a/drivers/mtd/nand/ecc.c +++ b/drivers/mtd/nand/ecc.c @@ -95,6 +95,7 @@ #include #include +#include /** * nand_ecc_init_ctx - Init the ECC engine context @@ -479,6 +480,111 @@ 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); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Miquel Raynal "); MODULE_DESCRIPTION("Generic ECC engine"); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 697ea2474a7c75a76df16977f17e45ca2bb17823..36e4fe08d0ea39241a6046d51260f0e396f18bcf 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -278,6 +278,38 @@ int nand_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req); bool nand_ecc_is_strong_enough(struct nand_device *nand); +/** + * struct nand_ecc_req_tweak_ctx - Help for automatically tweaking requests + * @orig_req: Pointer to the original IO request + * @nand: Related NAND device, to have access to its memory organization + * @page_buffer_size: Real size of the page buffer to use (can be set by the + * user before the tweaking mechanism initialization) + * @oob_buffer_size: Real size of the OOB buffer to use (can be set by the + * user before the tweaking mechanism initialization) + * @spare_databuf: Data bounce buffer + * @spare_oobbuf: OOB bounce buffer + * @bounce_data: Flag indicating a data bounce buffer is used + * @bounce_oob: Flag indicating an OOB bounce buffer is used + */ +struct nand_ecc_req_tweak_ctx { + struct nand_page_io_req orig_req; + struct nand_device *nand; + unsigned int page_buffer_size; + unsigned int oob_buffer_size; + void *spare_databuf; + void *spare_oobbuf; + bool bounce_data; + bool bounce_oob; +}; + +int nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_device *nand); +void nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx); +void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_page_io_req *req); +void nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_page_io_req *req); + /** * struct nand_ecc - Information relative to the ECC * @defaults: Default values, depend on the underlying subsystem