162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic Error-Correcting Code (ECC) engine 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019 Macronix 662306a36Sopenharmony_ci * Author: 762306a36Sopenharmony_ci * Miquèl RAYNAL <miquel.raynal@bootlin.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * This file describes the abstraction of any NAND ECC engine. It has been 1162306a36Sopenharmony_ci * designed to fit most cases, including parallel NANDs and SPI-NANDs. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * There are three main situations where instantiating this ECC engine makes 1462306a36Sopenharmony_ci * sense: 1562306a36Sopenharmony_ci * - external: The ECC engine is outside the NAND pipeline, typically this 1662306a36Sopenharmony_ci * is a software ECC engine, or an hardware engine that is 1762306a36Sopenharmony_ci * outside the NAND controller pipeline. 1862306a36Sopenharmony_ci * - pipelined: The ECC engine is inside the NAND pipeline, ie. on the 1962306a36Sopenharmony_ci * controller's side. This is the case of most of the raw NAND 2062306a36Sopenharmony_ci * controllers. In the pipeline case, the ECC bytes are 2162306a36Sopenharmony_ci * generated/data corrected on the fly when a page is 2262306a36Sopenharmony_ci * written/read. 2362306a36Sopenharmony_ci * - ondie: The ECC engine is inside the NAND pipeline, on the chip's side. 2462306a36Sopenharmony_ci * Some NAND chips can correct themselves the data. 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * Besides the initial setup and final cleanups, the interfaces are rather 2762306a36Sopenharmony_ci * simple: 2862306a36Sopenharmony_ci * - prepare: Prepare an I/O request. Enable/disable the ECC engine based on 2962306a36Sopenharmony_ci * the I/O request type. In case of software correction or external 3062306a36Sopenharmony_ci * engine, this step may involve to derive the ECC bytes and place 3162306a36Sopenharmony_ci * them in the OOB area before a write. 3262306a36Sopenharmony_ci * - finish: Finish an I/O request. Correct the data in case of a read 3362306a36Sopenharmony_ci * request and report the number of corrected bits/uncorrectable 3462306a36Sopenharmony_ci * errors. Most likely empty for write operations, unless you have 3562306a36Sopenharmony_ci * hardware specific stuff to do, like shutting down the engine to 3662306a36Sopenharmony_ci * save power. 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * The I/O request should be enclosed in a prepare()/finish() pair of calls 3962306a36Sopenharmony_ci * and will behave differently depending on the requested I/O type: 4062306a36Sopenharmony_ci * - raw: Correction disabled 4162306a36Sopenharmony_ci * - ecc: Correction enabled 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * The request direction is impacting the logic as well: 4462306a36Sopenharmony_ci * - read: Load data from the NAND chip 4562306a36Sopenharmony_ci * - write: Store data in the NAND chip 4662306a36Sopenharmony_ci * 4762306a36Sopenharmony_ci * Mixing all this combinations together gives the following behavior. 4862306a36Sopenharmony_ci * Those are just examples, drivers are free to add custom steps in their 4962306a36Sopenharmony_ci * prepare/finish hook. 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * [external ECC engine] 5262306a36Sopenharmony_ci * - external + prepare + raw + read: do nothing 5362306a36Sopenharmony_ci * - external + finish + raw + read: do nothing 5462306a36Sopenharmony_ci * - external + prepare + raw + write: do nothing 5562306a36Sopenharmony_ci * - external + finish + raw + write: do nothing 5662306a36Sopenharmony_ci * - external + prepare + ecc + read: do nothing 5762306a36Sopenharmony_ci * - external + finish + ecc + read: calculate expected ECC bytes, extract 5862306a36Sopenharmony_ci * ECC bytes from OOB buffer, correct 5962306a36Sopenharmony_ci * and report any bitflip/error 6062306a36Sopenharmony_ci * - external + prepare + ecc + write: calculate ECC bytes and store them at 6162306a36Sopenharmony_ci * the right place in the OOB buffer based 6262306a36Sopenharmony_ci * on the OOB layout 6362306a36Sopenharmony_ci * - external + finish + ecc + write: do nothing 6462306a36Sopenharmony_ci * 6562306a36Sopenharmony_ci * [pipelined ECC engine] 6662306a36Sopenharmony_ci * - pipelined + prepare + raw + read: disable the controller's ECC engine if 6762306a36Sopenharmony_ci * activated 6862306a36Sopenharmony_ci * - pipelined + finish + raw + read: do nothing 6962306a36Sopenharmony_ci * - pipelined + prepare + raw + write: disable the controller's ECC engine if 7062306a36Sopenharmony_ci * activated 7162306a36Sopenharmony_ci * - pipelined + finish + raw + write: do nothing 7262306a36Sopenharmony_ci * - pipelined + prepare + ecc + read: enable the controller's ECC engine if 7362306a36Sopenharmony_ci * deactivated 7462306a36Sopenharmony_ci * - pipelined + finish + ecc + read: check the status, report any 7562306a36Sopenharmony_ci * error/bitflip 7662306a36Sopenharmony_ci * - pipelined + prepare + ecc + write: enable the controller's ECC engine if 7762306a36Sopenharmony_ci * deactivated 7862306a36Sopenharmony_ci * - pipelined + finish + ecc + write: do nothing 7962306a36Sopenharmony_ci * 8062306a36Sopenharmony_ci * [ondie ECC engine] 8162306a36Sopenharmony_ci * - ondie + prepare + raw + read: send commands to disable the on-chip ECC 8262306a36Sopenharmony_ci * engine if activated 8362306a36Sopenharmony_ci * - ondie + finish + raw + read: do nothing 8462306a36Sopenharmony_ci * - ondie + prepare + raw + write: send commands to disable the on-chip ECC 8562306a36Sopenharmony_ci * engine if activated 8662306a36Sopenharmony_ci * - ondie + finish + raw + write: do nothing 8762306a36Sopenharmony_ci * - ondie + prepare + ecc + read: send commands to enable the on-chip ECC 8862306a36Sopenharmony_ci * engine if deactivated 8962306a36Sopenharmony_ci * - ondie + finish + ecc + read: send commands to check the status, report 9062306a36Sopenharmony_ci * any error/bitflip 9162306a36Sopenharmony_ci * - ondie + prepare + ecc + write: send commands to enable the on-chip ECC 9262306a36Sopenharmony_ci * engine if deactivated 9362306a36Sopenharmony_ci * - ondie + finish + ecc + write: do nothing 9462306a36Sopenharmony_ci */ 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci#include <linux/module.h> 9762306a36Sopenharmony_ci#include <linux/mtd/nand.h> 9862306a36Sopenharmony_ci#include <linux/platform_device.h> 9962306a36Sopenharmony_ci#include <linux/slab.h> 10062306a36Sopenharmony_ci#include <linux/of.h> 10162306a36Sopenharmony_ci#include <linux/of_platform.h> 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic LIST_HEAD(on_host_hw_engines); 10462306a36Sopenharmony_cistatic DEFINE_MUTEX(on_host_hw_engines_mutex); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/** 10762306a36Sopenharmony_ci * nand_ecc_init_ctx - Init the ECC engine context 10862306a36Sopenharmony_ci * @nand: the NAND device 10962306a36Sopenharmony_ci * 11062306a36Sopenharmony_ci * On success, the caller is responsible of calling @nand_ecc_cleanup_ctx(). 11162306a36Sopenharmony_ci */ 11262306a36Sopenharmony_ciint nand_ecc_init_ctx(struct nand_device *nand) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci if (!nand->ecc.engine || !nand->ecc.engine->ops->init_ctx) 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return nand->ecc.engine->ops->init_ctx(nand); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_init_ctx); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/** 12262306a36Sopenharmony_ci * nand_ecc_cleanup_ctx - Cleanup the ECC engine context 12362306a36Sopenharmony_ci * @nand: the NAND device 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_civoid nand_ecc_cleanup_ctx(struct nand_device *nand) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci if (nand->ecc.engine && nand->ecc.engine->ops->cleanup_ctx) 12862306a36Sopenharmony_ci nand->ecc.engine->ops->cleanup_ctx(nand); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_cleanup_ctx); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/** 13362306a36Sopenharmony_ci * nand_ecc_prepare_io_req - Prepare an I/O request 13462306a36Sopenharmony_ci * @nand: the NAND device 13562306a36Sopenharmony_ci * @req: the I/O request 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_ciint nand_ecc_prepare_io_req(struct nand_device *nand, 13862306a36Sopenharmony_ci struct nand_page_io_req *req) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci if (!nand->ecc.engine || !nand->ecc.engine->ops->prepare_io_req) 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return nand->ecc.engine->ops->prepare_io_req(nand, req); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_prepare_io_req); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci/** 14862306a36Sopenharmony_ci * nand_ecc_finish_io_req - Finish an I/O request 14962306a36Sopenharmony_ci * @nand: the NAND device 15062306a36Sopenharmony_ci * @req: the I/O request 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ciint nand_ecc_finish_io_req(struct nand_device *nand, 15362306a36Sopenharmony_ci struct nand_page_io_req *req) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci if (!nand->ecc.engine || !nand->ecc.engine->ops->finish_io_req) 15662306a36Sopenharmony_ci return 0; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return nand->ecc.engine->ops->finish_io_req(nand, req); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_finish_io_req); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/* Define default OOB placement schemes for large and small page devices */ 16362306a36Sopenharmony_cistatic int nand_ooblayout_ecc_sp(struct mtd_info *mtd, int section, 16462306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 16762306a36Sopenharmony_ci unsigned int total_ecc_bytes = nand->ecc.ctx.total; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (section > 1) 17062306a36Sopenharmony_ci return -ERANGE; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci if (!section) { 17362306a36Sopenharmony_ci oobregion->offset = 0; 17462306a36Sopenharmony_ci if (mtd->oobsize == 16) 17562306a36Sopenharmony_ci oobregion->length = 4; 17662306a36Sopenharmony_ci else 17762306a36Sopenharmony_ci oobregion->length = 3; 17862306a36Sopenharmony_ci } else { 17962306a36Sopenharmony_ci if (mtd->oobsize == 8) 18062306a36Sopenharmony_ci return -ERANGE; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci oobregion->offset = 6; 18362306a36Sopenharmony_ci oobregion->length = total_ecc_bytes - 4; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic int nand_ooblayout_free_sp(struct mtd_info *mtd, int section, 19062306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci if (section > 1) 19362306a36Sopenharmony_ci return -ERANGE; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (mtd->oobsize == 16) { 19662306a36Sopenharmony_ci if (section) 19762306a36Sopenharmony_ci return -ERANGE; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci oobregion->length = 8; 20062306a36Sopenharmony_ci oobregion->offset = 8; 20162306a36Sopenharmony_ci } else { 20262306a36Sopenharmony_ci oobregion->length = 2; 20362306a36Sopenharmony_ci if (!section) 20462306a36Sopenharmony_ci oobregion->offset = 3; 20562306a36Sopenharmony_ci else 20662306a36Sopenharmony_ci oobregion->offset = 6; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return 0; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = { 21362306a36Sopenharmony_ci .ecc = nand_ooblayout_ecc_sp, 21462306a36Sopenharmony_ci .free = nand_ooblayout_free_sp, 21562306a36Sopenharmony_ci}; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ciconst struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci return &nand_ooblayout_sp_ops; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_get_small_page_ooblayout); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section, 22462306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 22762306a36Sopenharmony_ci unsigned int total_ecc_bytes = nand->ecc.ctx.total; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (section || !total_ecc_bytes) 23062306a36Sopenharmony_ci return -ERANGE; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci oobregion->length = total_ecc_bytes; 23362306a36Sopenharmony_ci oobregion->offset = mtd->oobsize - oobregion->length; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return 0; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic int nand_ooblayout_free_lp(struct mtd_info *mtd, int section, 23962306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 24262306a36Sopenharmony_ci unsigned int total_ecc_bytes = nand->ecc.ctx.total; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (section) 24562306a36Sopenharmony_ci return -ERANGE; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci oobregion->length = mtd->oobsize - total_ecc_bytes - 2; 24862306a36Sopenharmony_ci oobregion->offset = 2; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = { 25462306a36Sopenharmony_ci .ecc = nand_ooblayout_ecc_lp, 25562306a36Sopenharmony_ci .free = nand_ooblayout_free_lp, 25662306a36Sopenharmony_ci}; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ciconst struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci return &nand_ooblayout_lp_ops; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_get_large_page_ooblayout); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci/* 26562306a36Sopenharmony_ci * Support the old "large page" layout used for 1-bit Hamming ECC where ECC 26662306a36Sopenharmony_ci * are placed at a fixed offset. 26762306a36Sopenharmony_ci */ 26862306a36Sopenharmony_cistatic int nand_ooblayout_ecc_lp_hamming(struct mtd_info *mtd, int section, 26962306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 27062306a36Sopenharmony_ci{ 27162306a36Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 27262306a36Sopenharmony_ci unsigned int total_ecc_bytes = nand->ecc.ctx.total; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (section) 27562306a36Sopenharmony_ci return -ERANGE; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci switch (mtd->oobsize) { 27862306a36Sopenharmony_ci case 64: 27962306a36Sopenharmony_ci oobregion->offset = 40; 28062306a36Sopenharmony_ci break; 28162306a36Sopenharmony_ci case 128: 28262306a36Sopenharmony_ci oobregion->offset = 80; 28362306a36Sopenharmony_ci break; 28462306a36Sopenharmony_ci default: 28562306a36Sopenharmony_ci return -EINVAL; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci oobregion->length = total_ecc_bytes; 28962306a36Sopenharmony_ci if (oobregion->offset + oobregion->length > mtd->oobsize) 29062306a36Sopenharmony_ci return -ERANGE; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return 0; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic int nand_ooblayout_free_lp_hamming(struct mtd_info *mtd, int section, 29662306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci struct nand_device *nand = mtd_to_nanddev(mtd); 29962306a36Sopenharmony_ci unsigned int total_ecc_bytes = nand->ecc.ctx.total; 30062306a36Sopenharmony_ci int ecc_offset = 0; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci if (section < 0 || section > 1) 30362306a36Sopenharmony_ci return -ERANGE; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci switch (mtd->oobsize) { 30662306a36Sopenharmony_ci case 64: 30762306a36Sopenharmony_ci ecc_offset = 40; 30862306a36Sopenharmony_ci break; 30962306a36Sopenharmony_ci case 128: 31062306a36Sopenharmony_ci ecc_offset = 80; 31162306a36Sopenharmony_ci break; 31262306a36Sopenharmony_ci default: 31362306a36Sopenharmony_ci return -EINVAL; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (section == 0) { 31762306a36Sopenharmony_ci oobregion->offset = 2; 31862306a36Sopenharmony_ci oobregion->length = ecc_offset - 2; 31962306a36Sopenharmony_ci } else { 32062306a36Sopenharmony_ci oobregion->offset = ecc_offset + total_ecc_bytes; 32162306a36Sopenharmony_ci oobregion->length = mtd->oobsize - oobregion->offset; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops nand_ooblayout_lp_hamming_ops = { 32862306a36Sopenharmony_ci .ecc = nand_ooblayout_ecc_lp_hamming, 32962306a36Sopenharmony_ci .free = nand_ooblayout_free_lp_hamming, 33062306a36Sopenharmony_ci}; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ciconst struct mtd_ooblayout_ops *nand_get_large_page_hamming_ooblayout(void) 33362306a36Sopenharmony_ci{ 33462306a36Sopenharmony_ci return &nand_ooblayout_lp_hamming_ops; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_get_large_page_hamming_ooblayout); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_cistatic enum nand_ecc_engine_type 33962306a36Sopenharmony_ciof_get_nand_ecc_engine_type(struct device_node *np) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci struct device_node *eng_np; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (of_property_read_bool(np, "nand-no-ecc-engine")) 34462306a36Sopenharmony_ci return NAND_ECC_ENGINE_TYPE_NONE; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci if (of_property_read_bool(np, "nand-use-soft-ecc-engine")) 34762306a36Sopenharmony_ci return NAND_ECC_ENGINE_TYPE_SOFT; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci eng_np = of_parse_phandle(np, "nand-ecc-engine", 0); 35062306a36Sopenharmony_ci of_node_put(eng_np); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci if (eng_np) { 35362306a36Sopenharmony_ci if (eng_np == np) 35462306a36Sopenharmony_ci return NAND_ECC_ENGINE_TYPE_ON_DIE; 35562306a36Sopenharmony_ci else 35662306a36Sopenharmony_ci return NAND_ECC_ENGINE_TYPE_ON_HOST; 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci return NAND_ECC_ENGINE_TYPE_INVALID; 36062306a36Sopenharmony_ci} 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic const char * const nand_ecc_placement[] = { 36362306a36Sopenharmony_ci [NAND_ECC_PLACEMENT_OOB] = "oob", 36462306a36Sopenharmony_ci [NAND_ECC_PLACEMENT_INTERLEAVED] = "interleaved", 36562306a36Sopenharmony_ci}; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic enum nand_ecc_placement of_get_nand_ecc_placement(struct device_node *np) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci enum nand_ecc_placement placement; 37062306a36Sopenharmony_ci const char *pm; 37162306a36Sopenharmony_ci int err; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci err = of_property_read_string(np, "nand-ecc-placement", &pm); 37462306a36Sopenharmony_ci if (!err) { 37562306a36Sopenharmony_ci for (placement = NAND_ECC_PLACEMENT_OOB; 37662306a36Sopenharmony_ci placement < ARRAY_SIZE(nand_ecc_placement); placement++) { 37762306a36Sopenharmony_ci if (!strcasecmp(pm, nand_ecc_placement[placement])) 37862306a36Sopenharmony_ci return placement; 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci } 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci return NAND_ECC_PLACEMENT_UNKNOWN; 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic const char * const nand_ecc_algos[] = { 38662306a36Sopenharmony_ci [NAND_ECC_ALGO_HAMMING] = "hamming", 38762306a36Sopenharmony_ci [NAND_ECC_ALGO_BCH] = "bch", 38862306a36Sopenharmony_ci [NAND_ECC_ALGO_RS] = "rs", 38962306a36Sopenharmony_ci}; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_cistatic enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np) 39262306a36Sopenharmony_ci{ 39362306a36Sopenharmony_ci enum nand_ecc_algo ecc_algo; 39462306a36Sopenharmony_ci const char *pm; 39562306a36Sopenharmony_ci int err; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci err = of_property_read_string(np, "nand-ecc-algo", &pm); 39862306a36Sopenharmony_ci if (!err) { 39962306a36Sopenharmony_ci for (ecc_algo = NAND_ECC_ALGO_HAMMING; 40062306a36Sopenharmony_ci ecc_algo < ARRAY_SIZE(nand_ecc_algos); 40162306a36Sopenharmony_ci ecc_algo++) { 40262306a36Sopenharmony_ci if (!strcasecmp(pm, nand_ecc_algos[ecc_algo])) 40362306a36Sopenharmony_ci return ecc_algo; 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci return NAND_ECC_ALGO_UNKNOWN; 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_cistatic int of_get_nand_ecc_step_size(struct device_node *np) 41162306a36Sopenharmony_ci{ 41262306a36Sopenharmony_ci int ret; 41362306a36Sopenharmony_ci u32 val; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci ret = of_property_read_u32(np, "nand-ecc-step-size", &val); 41662306a36Sopenharmony_ci return ret ? ret : val; 41762306a36Sopenharmony_ci} 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic int of_get_nand_ecc_strength(struct device_node *np) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci int ret; 42262306a36Sopenharmony_ci u32 val; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci ret = of_property_read_u32(np, "nand-ecc-strength", &val); 42562306a36Sopenharmony_ci return ret ? ret : val; 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_civoid of_get_nand_ecc_user_config(struct nand_device *nand) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci struct device_node *dn = nanddev_get_of_node(nand); 43162306a36Sopenharmony_ci int strength, size; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci nand->ecc.user_conf.engine_type = of_get_nand_ecc_engine_type(dn); 43462306a36Sopenharmony_ci nand->ecc.user_conf.algo = of_get_nand_ecc_algo(dn); 43562306a36Sopenharmony_ci nand->ecc.user_conf.placement = of_get_nand_ecc_placement(dn); 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci strength = of_get_nand_ecc_strength(dn); 43862306a36Sopenharmony_ci if (strength >= 0) 43962306a36Sopenharmony_ci nand->ecc.user_conf.strength = strength; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci size = of_get_nand_ecc_step_size(dn); 44262306a36Sopenharmony_ci if (size >= 0) 44362306a36Sopenharmony_ci nand->ecc.user_conf.step_size = size; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci if (of_property_read_bool(dn, "nand-ecc-maximize")) 44662306a36Sopenharmony_ci nand->ecc.user_conf.flags |= NAND_ECC_MAXIMIZE_STRENGTH; 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ciEXPORT_SYMBOL(of_get_nand_ecc_user_config); 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci/** 45162306a36Sopenharmony_ci * nand_ecc_is_strong_enough - Check if the chip configuration meets the 45262306a36Sopenharmony_ci * datasheet requirements. 45362306a36Sopenharmony_ci * 45462306a36Sopenharmony_ci * @nand: Device to check 45562306a36Sopenharmony_ci * 45662306a36Sopenharmony_ci * If our configuration corrects A bits per B bytes and the minimum 45762306a36Sopenharmony_ci * required correction level is X bits per Y bytes, then we must ensure 45862306a36Sopenharmony_ci * both of the following are true: 45962306a36Sopenharmony_ci * 46062306a36Sopenharmony_ci * (1) A / B >= X / Y 46162306a36Sopenharmony_ci * (2) A >= X 46262306a36Sopenharmony_ci * 46362306a36Sopenharmony_ci * Requirement (1) ensures we can correct for the required bitflip density. 46462306a36Sopenharmony_ci * Requirement (2) ensures we can correct even when all bitflips are clumped 46562306a36Sopenharmony_ci * in the same sector. 46662306a36Sopenharmony_ci */ 46762306a36Sopenharmony_cibool nand_ecc_is_strong_enough(struct nand_device *nand) 46862306a36Sopenharmony_ci{ 46962306a36Sopenharmony_ci const struct nand_ecc_props *reqs = nanddev_get_ecc_requirements(nand); 47062306a36Sopenharmony_ci const struct nand_ecc_props *conf = nanddev_get_ecc_conf(nand); 47162306a36Sopenharmony_ci struct mtd_info *mtd = nanddev_to_mtd(nand); 47262306a36Sopenharmony_ci int corr, ds_corr; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci if (conf->step_size == 0 || reqs->step_size == 0) 47562306a36Sopenharmony_ci /* Not enough information */ 47662306a36Sopenharmony_ci return true; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci /* 47962306a36Sopenharmony_ci * We get the number of corrected bits per page to compare 48062306a36Sopenharmony_ci * the correction density. 48162306a36Sopenharmony_ci */ 48262306a36Sopenharmony_ci corr = (mtd->writesize * conf->strength) / conf->step_size; 48362306a36Sopenharmony_ci ds_corr = (mtd->writesize * reqs->strength) / reqs->step_size; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci return corr >= ds_corr && conf->strength >= reqs->strength; 48662306a36Sopenharmony_ci} 48762306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_is_strong_enough); 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci/* ECC engine driver internal helpers */ 49062306a36Sopenharmony_ciint nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx, 49162306a36Sopenharmony_ci struct nand_device *nand) 49262306a36Sopenharmony_ci{ 49362306a36Sopenharmony_ci unsigned int total_buffer_size; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci ctx->nand = nand; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci /* Let the user decide the exact length of each buffer */ 49862306a36Sopenharmony_ci if (!ctx->page_buffer_size) 49962306a36Sopenharmony_ci ctx->page_buffer_size = nanddev_page_size(nand); 50062306a36Sopenharmony_ci if (!ctx->oob_buffer_size) 50162306a36Sopenharmony_ci ctx->oob_buffer_size = nanddev_per_page_oobsize(nand); 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci total_buffer_size = ctx->page_buffer_size + ctx->oob_buffer_size; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci ctx->spare_databuf = kzalloc(total_buffer_size, GFP_KERNEL); 50662306a36Sopenharmony_ci if (!ctx->spare_databuf) 50762306a36Sopenharmony_ci return -ENOMEM; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci ctx->spare_oobbuf = ctx->spare_databuf + ctx->page_buffer_size; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci return 0; 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_ecc_init_req_tweaking); 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_civoid nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci kfree(ctx->spare_databuf); 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_ecc_cleanup_req_tweaking); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci/* 52262306a36Sopenharmony_ci * Ensure data and OOB area is fully read/written otherwise the correction might 52362306a36Sopenharmony_ci * not work as expected. 52462306a36Sopenharmony_ci */ 52562306a36Sopenharmony_civoid nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx, 52662306a36Sopenharmony_ci struct nand_page_io_req *req) 52762306a36Sopenharmony_ci{ 52862306a36Sopenharmony_ci struct nand_device *nand = ctx->nand; 52962306a36Sopenharmony_ci struct nand_page_io_req *orig, *tweak; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci /* Save the original request */ 53262306a36Sopenharmony_ci ctx->orig_req = *req; 53362306a36Sopenharmony_ci ctx->bounce_data = false; 53462306a36Sopenharmony_ci ctx->bounce_oob = false; 53562306a36Sopenharmony_ci orig = &ctx->orig_req; 53662306a36Sopenharmony_ci tweak = req; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci /* Ensure the request covers the entire page */ 53962306a36Sopenharmony_ci if (orig->datalen < nanddev_page_size(nand)) { 54062306a36Sopenharmony_ci ctx->bounce_data = true; 54162306a36Sopenharmony_ci tweak->dataoffs = 0; 54262306a36Sopenharmony_ci tweak->datalen = nanddev_page_size(nand); 54362306a36Sopenharmony_ci tweak->databuf.in = ctx->spare_databuf; 54462306a36Sopenharmony_ci memset(tweak->databuf.in, 0xFF, ctx->page_buffer_size); 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci if (orig->ooblen < nanddev_per_page_oobsize(nand)) { 54862306a36Sopenharmony_ci ctx->bounce_oob = true; 54962306a36Sopenharmony_ci tweak->ooboffs = 0; 55062306a36Sopenharmony_ci tweak->ooblen = nanddev_per_page_oobsize(nand); 55162306a36Sopenharmony_ci tweak->oobbuf.in = ctx->spare_oobbuf; 55262306a36Sopenharmony_ci memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size); 55362306a36Sopenharmony_ci } 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci /* Copy the data that must be writen in the bounce buffers, if needed */ 55662306a36Sopenharmony_ci if (orig->type == NAND_PAGE_WRITE) { 55762306a36Sopenharmony_ci if (ctx->bounce_data) 55862306a36Sopenharmony_ci memcpy((void *)tweak->databuf.out + orig->dataoffs, 55962306a36Sopenharmony_ci orig->databuf.out, orig->datalen); 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci if (ctx->bounce_oob) 56262306a36Sopenharmony_ci memcpy((void *)tweak->oobbuf.out + orig->ooboffs, 56362306a36Sopenharmony_ci orig->oobbuf.out, orig->ooblen); 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci} 56662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_ecc_tweak_req); 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_civoid nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx, 56962306a36Sopenharmony_ci struct nand_page_io_req *req) 57062306a36Sopenharmony_ci{ 57162306a36Sopenharmony_ci struct nand_page_io_req *orig, *tweak; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci orig = &ctx->orig_req; 57462306a36Sopenharmony_ci tweak = req; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci /* Restore the data read from the bounce buffers, if needed */ 57762306a36Sopenharmony_ci if (orig->type == NAND_PAGE_READ) { 57862306a36Sopenharmony_ci if (ctx->bounce_data) 57962306a36Sopenharmony_ci memcpy(orig->databuf.in, 58062306a36Sopenharmony_ci tweak->databuf.in + orig->dataoffs, 58162306a36Sopenharmony_ci orig->datalen); 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if (ctx->bounce_oob) 58462306a36Sopenharmony_ci memcpy(orig->oobbuf.in, 58562306a36Sopenharmony_ci tweak->oobbuf.in + orig->ooboffs, 58662306a36Sopenharmony_ci orig->ooblen); 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci /* Ensure the original request is restored */ 59062306a36Sopenharmony_ci *req = *orig; 59162306a36Sopenharmony_ci} 59262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nand_ecc_restore_req); 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_cistruct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand) 59562306a36Sopenharmony_ci{ 59662306a36Sopenharmony_ci unsigned int algo = nand->ecc.user_conf.algo; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci if (algo == NAND_ECC_ALGO_UNKNOWN) 59962306a36Sopenharmony_ci algo = nand->ecc.defaults.algo; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci switch (algo) { 60262306a36Sopenharmony_ci case NAND_ECC_ALGO_HAMMING: 60362306a36Sopenharmony_ci return nand_ecc_sw_hamming_get_engine(); 60462306a36Sopenharmony_ci case NAND_ECC_ALGO_BCH: 60562306a36Sopenharmony_ci return nand_ecc_sw_bch_get_engine(); 60662306a36Sopenharmony_ci default: 60762306a36Sopenharmony_ci break; 60862306a36Sopenharmony_ci } 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci return NULL; 61162306a36Sopenharmony_ci} 61262306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_get_sw_engine); 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_cistruct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand) 61562306a36Sopenharmony_ci{ 61662306a36Sopenharmony_ci return nand->ecc.ondie_engine; 61762306a36Sopenharmony_ci} 61862306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ciint nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine) 62162306a36Sopenharmony_ci{ 62262306a36Sopenharmony_ci struct nand_ecc_engine *item; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci if (!engine) 62562306a36Sopenharmony_ci return -EINVAL; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci /* Prevent multiple registrations of one engine */ 62862306a36Sopenharmony_ci list_for_each_entry(item, &on_host_hw_engines, node) 62962306a36Sopenharmony_ci if (item == engine) 63062306a36Sopenharmony_ci return 0; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci mutex_lock(&on_host_hw_engines_mutex); 63362306a36Sopenharmony_ci list_add_tail(&engine->node, &on_host_hw_engines); 63462306a36Sopenharmony_ci mutex_unlock(&on_host_hw_engines_mutex); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci return 0; 63762306a36Sopenharmony_ci} 63862306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_register_on_host_hw_engine); 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ciint nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine) 64162306a36Sopenharmony_ci{ 64262306a36Sopenharmony_ci if (!engine) 64362306a36Sopenharmony_ci return -EINVAL; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci mutex_lock(&on_host_hw_engines_mutex); 64662306a36Sopenharmony_ci list_del(&engine->node); 64762306a36Sopenharmony_ci mutex_unlock(&on_host_hw_engines_mutex); 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci return 0; 65062306a36Sopenharmony_ci} 65162306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_unregister_on_host_hw_engine); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_cistatic struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *dev) 65462306a36Sopenharmony_ci{ 65562306a36Sopenharmony_ci struct nand_ecc_engine *item; 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci list_for_each_entry(item, &on_host_hw_engines, node) 65862306a36Sopenharmony_ci if (item->dev == dev) 65962306a36Sopenharmony_ci return item; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci return NULL; 66262306a36Sopenharmony_ci} 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_cistruct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand) 66562306a36Sopenharmony_ci{ 66662306a36Sopenharmony_ci struct nand_ecc_engine *engine = NULL; 66762306a36Sopenharmony_ci struct device *dev = &nand->mtd.dev; 66862306a36Sopenharmony_ci struct platform_device *pdev; 66962306a36Sopenharmony_ci struct device_node *np; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci if (list_empty(&on_host_hw_engines)) 67262306a36Sopenharmony_ci return NULL; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci /* Check for an explicit nand-ecc-engine property */ 67562306a36Sopenharmony_ci np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0); 67662306a36Sopenharmony_ci if (np) { 67762306a36Sopenharmony_ci pdev = of_find_device_by_node(np); 67862306a36Sopenharmony_ci if (!pdev) 67962306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci engine = nand_ecc_match_on_host_hw_engine(&pdev->dev); 68262306a36Sopenharmony_ci platform_device_put(pdev); 68362306a36Sopenharmony_ci of_node_put(np); 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci if (!engine) 68662306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 68762306a36Sopenharmony_ci } 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci if (engine) 69062306a36Sopenharmony_ci get_device(engine->dev); 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci return engine; 69362306a36Sopenharmony_ci} 69462306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine); 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_civoid nand_ecc_put_on_host_hw_engine(struct nand_device *nand) 69762306a36Sopenharmony_ci{ 69862306a36Sopenharmony_ci put_device(nand->ecc.engine->dev); 69962306a36Sopenharmony_ci} 70062306a36Sopenharmony_ciEXPORT_SYMBOL(nand_ecc_put_on_host_hw_engine); 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci/* 70362306a36Sopenharmony_ci * In the case of a pipelined engine, the device registering the ECC 70462306a36Sopenharmony_ci * engine is not necessarily the ECC engine itself but may be a host controller. 70562306a36Sopenharmony_ci * It is then useful to provide a helper to retrieve the right device object 70662306a36Sopenharmony_ci * which actually represents the ECC engine. 70762306a36Sopenharmony_ci */ 70862306a36Sopenharmony_cistruct device *nand_ecc_get_engine_dev(struct device *host) 70962306a36Sopenharmony_ci{ 71062306a36Sopenharmony_ci struct platform_device *ecc_pdev; 71162306a36Sopenharmony_ci struct device_node *np; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci /* 71462306a36Sopenharmony_ci * If the device node contains this property, it means we need to follow 71562306a36Sopenharmony_ci * it in order to get the right ECC engine device we are looking for. 71662306a36Sopenharmony_ci */ 71762306a36Sopenharmony_ci np = of_parse_phandle(host->of_node, "nand-ecc-engine", 0); 71862306a36Sopenharmony_ci if (!np) 71962306a36Sopenharmony_ci return host; 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci ecc_pdev = of_find_device_by_node(np); 72262306a36Sopenharmony_ci if (!ecc_pdev) { 72362306a36Sopenharmony_ci of_node_put(np); 72462306a36Sopenharmony_ci return NULL; 72562306a36Sopenharmony_ci } 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci platform_device_put(ecc_pdev); 72862306a36Sopenharmony_ci of_node_put(np); 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci return &ecc_pdev->dev; 73162306a36Sopenharmony_ci} 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 73462306a36Sopenharmony_ciMODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>"); 73562306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic ECC engine"); 736