From 785818fa8385fe55dab253e42a4c6728fca61333 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Sat, 29 Apr 2017 11:06:43 +0200 Subject: mtd: nand: add core support for on-die ECC A number of NAND flashes have a capability called "on-die ECC" where the NAND chip itself is capable of detecting and correcting errors. Linux already has support for using the ECC implementation of the NAND controller, or a software based ECC implementation, but not for using the ECC implementation of the NAND controller. However, such an implementation is sometimes useful in situations where the NAND controller provides ECC algorithms that are not strong enough for the NAND chip used on the system. A typical case is a NAND chip that requires a 4-bit ECC, while the NAND controller only provides a 1-bit ECC algorithm. This commit introduces the support for the NAND_ECC_ON_DIE ECC mode: - Parsing of the "on-die" value for the "nand-ecc-mode" Device Tree property - Handling NAND_ECC_ON_DIE case in nand_scan_tail(). The idea is that the vendor specific code for the NAND chip must implement ->read_page() and ->write_page(). It may optionally provide its own ->read_page_raw() and ->write_page_raw() as well. For OOB operation, we assume the standard operations are good enough, but they can be overridden by the vendor specific code if needed. Signed-off-by: Thomas Petazzoni Reviewed-by: Richard Weinberger Signed-off-by: Boris Brezillon --- include/linux/mtd/nand.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 8f67b1581683..603522097ec9 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -116,6 +116,7 @@ typedef enum { NAND_ECC_HW, NAND_ECC_HW_SYNDROME, NAND_ECC_HW_OOB_FIRST, + NAND_ECC_ON_DIE, } nand_ecc_modes_t; enum nand_ecc_algo { -- cgit v1.2.3-59-g8ed1b From cc0f51ec111266f5d255e753bf3254ad411d5c12 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Sat, 29 Apr 2017 11:06:44 +0200 Subject: mtd: nand: export nand_{read,write}_page_raw() The nand_read_page_raw() and nand_write_page_raw() functions might be re-used by vendor-specific implementations of the read_page/write_page functions. Instead of having vendor-specific code duplicate this code, it is much better to export those functions and allow them to be re-used. Signed-off-by: Thomas Petazzoni Reviewed-by: Richard Weinberger Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 10 ++++++---- include/linux/mtd/nand.h | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index f49aecd3f1de..0b3b1a88091a 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -1524,14 +1524,15 @@ EXPORT_SYMBOL(nand_check_erased_ecc_chunk); * * Not for syndrome calculating ECC controllers, which use a special oob layout. */ -static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, - uint8_t *buf, int oob_required, int page) +int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) { chip->read_buf(mtd, buf, mtd->writesize); if (oob_required) chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); return 0; } +EXPORT_SYMBOL(nand_read_page_raw); /** * nand_read_page_raw_syndrome - [INTERN] read raw page data without ecc @@ -2469,8 +2470,8 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from, * * Not for syndrome calculating ECC controllers, which use a special oob layout. */ -static int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, - const uint8_t *buf, int oob_required, int page) +int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required, int page) { chip->write_buf(mtd, buf, mtd->writesize); if (oob_required) @@ -2478,6 +2479,7 @@ static int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, return 0; } +EXPORT_SYMBOL(nand_write_page_raw); /** * nand_write_page_raw_syndrome - [INTERN] raw page write function diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 603522097ec9..7a01d2eb7443 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -1259,6 +1259,14 @@ int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page); int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, int page); +/* Default read_page_raw implementation */ +int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page); + +/* Default write_page_raw implementation */ +int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required, int page); + /* Reset and initialize a NAND device */ int nand_reset(struct nand_chip *chip, int chipnr); -- cgit v1.2.3-59-g8ed1b From 4a78cc644eed3cf2dae00c3a959910a86c140fd6 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 26 May 2017 17:10:15 +0200 Subject: mtd: nand: Make sure drivers not supporting SET/GET_FEATURES return -ENOTSUPP A lot of drivers are providing their own ->cmdfunc(), and most of the time this implementation does not support all possible NAND operations. But since ->cmdfunc() cannot return an error code, the core has no way to know that the operation it requested is not supported. This is a problem we cannot address for all kind of operations with the current design, but we can prevent these silent failures for the GET/SET FEATURES operation by overloading the default ->onfi_{set,get}_features() methods with one returning -ENOTSUPP. Reported-by: Chris Packham Signed-off-by: Boris Brezillon Tested-by: Chris Packham --- drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c | 2 ++ drivers/mtd/nand/cafe_nand.c | 2 ++ drivers/mtd/nand/denali.c | 2 ++ drivers/mtd/nand/docg4.c | 2 ++ drivers/mtd/nand/fsl_elbc_nand.c | 2 ++ drivers/mtd/nand/fsl_ifc_nand.c | 2 ++ drivers/mtd/nand/hisi504_nand.c | 2 ++ drivers/mtd/nand/mpc5121_nfc.c | 2 ++ drivers/mtd/nand/nand_base.c | 19 +++++++++++++++++++ drivers/mtd/nand/pxa3xx_nand.c | 2 ++ drivers/mtd/nand/qcom_nandc.c | 2 ++ drivers/mtd/nand/sh_flctl.c | 2 ++ drivers/mtd/nand/vf610_nfc.c | 2 ++ drivers/staging/mt29f_spinand/mt29f_spinand.c | 2 ++ include/linux/mtd/nand.h | 5 +++++ 15 files changed, 50 insertions(+) (limited to 'include') diff --git a/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c b/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c index f1da4ea88f2c..54bac5b73f0a 100644 --- a/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c +++ b/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c @@ -392,6 +392,8 @@ int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n) b47n->nand_chip.read_byte = bcm47xxnflash_ops_bcm4706_read_byte; b47n->nand_chip.read_buf = bcm47xxnflash_ops_bcm4706_read_buf; b47n->nand_chip.write_buf = bcm47xxnflash_ops_bcm4706_write_buf; + b47n->nand_chip.onfi_set_features = nand_onfi_get_set_features_notsupp; + b47n->nand_chip.onfi_get_features = nand_onfi_get_set_features_notsupp; nand_chip->chip_delay = 50; b47n->nand_chip.bbt_options = NAND_BBT_USE_FLASH; diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c index d40c32d311d8..2fd733eba0a3 100644 --- a/drivers/mtd/nand/cafe_nand.c +++ b/drivers/mtd/nand/cafe_nand.c @@ -654,6 +654,8 @@ static int cafe_nand_probe(struct pci_dev *pdev, cafe->nand.read_buf = cafe_read_buf; cafe->nand.write_buf = cafe_write_buf; cafe->nand.select_chip = cafe_select_chip; + cafe->nand.onfi_set_features = nand_onfi_get_set_features_notsupp; + cafe->nand.onfi_get_features = nand_onfi_get_set_features_notsupp; cafe->nand.chip_delay = 0; diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c index 16634df2e39a..b3c99d98fdee 100644 --- a/drivers/mtd/nand/denali.c +++ b/drivers/mtd/nand/denali.c @@ -1531,6 +1531,8 @@ int denali_init(struct denali_nand_info *denali) chip->cmdfunc = denali_cmdfunc; chip->read_byte = denali_read_byte; chip->waitfunc = denali_waitfunc; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; /* * scan for NAND devices attached to the controller diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c index 7af2a3cd949e..a27a84fbfb84 100644 --- a/drivers/mtd/nand/docg4.c +++ b/drivers/mtd/nand/docg4.c @@ -1260,6 +1260,8 @@ static void __init init_mtd_structs(struct mtd_info *mtd) nand->read_buf = docg4_read_buf; nand->write_buf = docg4_write_buf16; nand->erase = docg4_erase_block; + nand->onfi_set_features = nand_onfi_get_set_features_notsupp; + nand->onfi_get_features = nand_onfi_get_set_features_notsupp; nand->ecc.read_page = docg4_read_page; nand->ecc.write_page = docg4_write_page; nand->ecc.read_page_raw = docg4_read_page_raw; diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c index 113f76e59937..b9ac16f05057 100644 --- a/drivers/mtd/nand/fsl_elbc_nand.c +++ b/drivers/mtd/nand/fsl_elbc_nand.c @@ -775,6 +775,8 @@ static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv) chip->select_chip = fsl_elbc_select_chip; chip->cmdfunc = fsl_elbc_cmdfunc; chip->waitfunc = fsl_elbc_wait; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; chip->bbt_td = &bbt_main_descr; chip->bbt_md = &bbt_mirror_descr; diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c index d1570f512f0b..89e14daeaba6 100644 --- a/drivers/mtd/nand/fsl_ifc_nand.c +++ b/drivers/mtd/nand/fsl_ifc_nand.c @@ -831,6 +831,8 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) chip->select_chip = fsl_ifc_select_chip; chip->cmdfunc = fsl_ifc_cmdfunc; chip->waitfunc = fsl_ifc_wait; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; chip->bbt_td = &bbt_main_descr; chip->bbt_md = &bbt_mirror_descr; diff --git a/drivers/mtd/nand/hisi504_nand.c b/drivers/mtd/nand/hisi504_nand.c index e40364eeb556..530caa80b1b6 100644 --- a/drivers/mtd/nand/hisi504_nand.c +++ b/drivers/mtd/nand/hisi504_nand.c @@ -764,6 +764,8 @@ static int hisi_nfc_probe(struct platform_device *pdev) chip->write_buf = hisi_nfc_write_buf; chip->read_buf = hisi_nfc_read_buf; chip->chip_delay = HINFC504_CHIP_DELAY; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; hisi_nfc_host_init(host); diff --git a/drivers/mtd/nand/mpc5121_nfc.c b/drivers/mtd/nand/mpc5121_nfc.c index 6d6eaed2d20c..0e86fb6277c3 100644 --- a/drivers/mtd/nand/mpc5121_nfc.c +++ b/drivers/mtd/nand/mpc5121_nfc.c @@ -708,6 +708,8 @@ static int mpc5121_nfc_probe(struct platform_device *op) chip->read_buf = mpc5121_nfc_read_buf; chip->write_buf = mpc5121_nfc_write_buf; chip->select_chip = mpc5121_nfc_select_chip; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; chip->bbt_options = NAND_BBT_USE_FLASH; chip->ecc.mode = NAND_ECC_SOFT; chip->ecc.algo = NAND_ECC_HAMMING; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 0b3b1a88091a..ed08e3946727 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -3420,6 +3420,25 @@ static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip, return 0; } +/** + * nand_onfi_get_set_features_notsupp - set/get features stub returning + * -ENOTSUPP + * @mtd: MTD device structure + * @chip: nand chip info structure + * @addr: feature address. + * @subfeature_param: the subfeature parameters, a four bytes array. + * + * Should be used by NAND controller drivers that do not support the SET/GET + * FEATURES operations. + */ +int nand_onfi_get_set_features_notsupp(struct mtd_info *mtd, + struct nand_chip *chip, int addr, + u8 *subfeature_param) +{ + return -ENOTSUPP; +} +EXPORT_SYMBOL(nand_onfi_get_set_features_notsupp); + /** * nand_suspend - [MTD Interface] Suspend the NAND flash * @mtd: MTD device structure diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index 649ba8200832..74dae4bbdac8 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -1812,6 +1812,8 @@ static int alloc_nand_resource(struct platform_device *pdev) chip->write_buf = pxa3xx_nand_write_buf; chip->options |= NAND_NO_SUBPAGE_WRITE; chip->cmdfunc = nand_cmdfunc; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; } nand_hw_control_init(chip->controller); diff --git a/drivers/mtd/nand/qcom_nandc.c b/drivers/mtd/nand/qcom_nandc.c index 57d483ac5765..88af7145a51a 100644 --- a/drivers/mtd/nand/qcom_nandc.c +++ b/drivers/mtd/nand/qcom_nandc.c @@ -2008,6 +2008,8 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc, chip->read_byte = qcom_nandc_read_byte; chip->read_buf = qcom_nandc_read_buf; chip->write_buf = qcom_nandc_write_buf; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; /* * the bad block marker is readable only when we read the last codeword diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c index 442ce619b3b6..891ac7b99305 100644 --- a/drivers/mtd/nand/sh_flctl.c +++ b/drivers/mtd/nand/sh_flctl.c @@ -1183,6 +1183,8 @@ static int flctl_probe(struct platform_device *pdev) nand->read_buf = flctl_read_buf; nand->select_chip = flctl_select_chip; nand->cmdfunc = flctl_cmdfunc; + nand->onfi_set_features = nand_onfi_get_set_features_notsupp; + nand->onfi_get_features = nand_onfi_get_set_features_notsupp; if (pdata->flcmncr_val & SEL_16BIT) nand->options |= NAND_BUSWIDTH_16; diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c index 3ea4bb19e12d..744ab10e8962 100644 --- a/drivers/mtd/nand/vf610_nfc.c +++ b/drivers/mtd/nand/vf610_nfc.c @@ -703,6 +703,8 @@ static int vf610_nfc_probe(struct platform_device *pdev) chip->read_buf = vf610_nfc_read_buf; chip->write_buf = vf610_nfc_write_buf; chip->select_chip = vf610_nfc_select_chip; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; chip->options |= NAND_NO_SUBPAGE_WRITE; diff --git a/drivers/staging/mt29f_spinand/mt29f_spinand.c b/drivers/staging/mt29f_spinand/mt29f_spinand.c index e389009fca42..a4e3ae8f0c85 100644 --- a/drivers/staging/mt29f_spinand/mt29f_spinand.c +++ b/drivers/staging/mt29f_spinand/mt29f_spinand.c @@ -915,6 +915,8 @@ static int spinand_probe(struct spi_device *spi_nand) chip->waitfunc = spinand_wait; chip->options |= NAND_CACHEPRG; chip->select_chip = spinand_select_chip; + chip->onfi_set_features = nand_onfi_get_set_features_notsupp; + chip->onfi_get_features = nand_onfi_get_set_features_notsupp; mtd = nand_to_mtd(chip); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 7a01d2eb7443..28f7dd9177e9 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -1259,6 +1259,11 @@ int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page); int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, int page); +/* Stub used by drivers that do not support GET/SET FEATURES operations */ +int nand_onfi_get_set_features_notsupp(struct mtd_info *mtd, + struct nand_chip *chip, int addr, + u8 *subfeature_param); + /* Default read_page_raw implementation */ int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page); -- cgit v1.2.3-59-g8ed1b From 9748e1d87573c94191442d6bd0307f523e5cd8b8 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Sat, 29 Apr 2017 11:06:45 +0200 Subject: mtd: nand: add support for Micron on-die ECC Now that the core NAND subsystem has support for on-die ECC, this commit brings the necessary code to support on-die ECC on Micron NANDs. In micron_nand_init(), we detect if the Micron NAND chip supports on-die ECC mode, by checking a number of conditions: - It must be an ONFI NAND - It must be a SLC NAND - Enabling *and* disabling on-die ECC must work - The on-die ECC must be correcting 4 bits per 512 bytes of data. Some Micron NAND chips have an on-die ECC able to correct 8 bits per 512 bytes of data, but they work slightly differently and therefore we don't support them in this patch. Then, if the on-die ECC cannot be disabled (some Micron NAND have on-die ECC forcefully enabled), we bail out, as we don't support such NANDs. Indeed, the implementation of raw_read()/raw_write() make the assumption that on-die ECC can be disabled. Support for Micron NANDs with on-die ECC forcefully enabled can easily be added, but in the absence of such HW for testing, we preferred to simply bail out. If the on-die ECC is supported, and requested in the Device Tree, then it is indeed enabled, by using custom implementations of the ->read_page(), ->read_page_raw(), ->write_page() and ->write_page_raw() operation to properly handle the on-die ECC. In the non-raw functions, we need to enable the internal ECC engine before issuing the NAND_CMD_READ0 or NAND_CMD_SEQIN commands, which is why we set the NAND_ECC_CUSTOM_PAGE_ACCESS option at initialization time (it asks the NAND core to let the NAND driver issue those commands). Signed-off-by: Thomas Petazzoni Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_micron.c | 216 +++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 2 + 2 files changed, 218 insertions(+) (limited to 'include') diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c index 877011069251..9993f8ead1e2 100644 --- a/drivers/mtd/nand/nand_micron.c +++ b/drivers/mtd/nand/nand_micron.c @@ -17,6 +17,12 @@ #include +/* + * Special Micron status bit that indicates when the block has been + * corrected by on-die ECC and should be rewritten + */ +#define NAND_STATUS_WRITE_RECOMMENDED BIT(3) + struct nand_onfi_vendor_micron { u8 two_plane_read; u8 read_cache; @@ -66,9 +72,191 @@ static int micron_nand_onfi_init(struct nand_chip *chip) return 0; } +static int micron_nand_on_die_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section >= 4) + return -ERANGE; + + oobregion->offset = (section * 16) + 8; + oobregion->length = 8; + + return 0; +} + +static int micron_nand_on_die_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section >= 4) + return -ERANGE; + + oobregion->offset = (section * 16) + 2; + oobregion->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops micron_nand_on_die_ooblayout_ops = { + .ecc = micron_nand_on_die_ooblayout_ecc, + .free = micron_nand_on_die_ooblayout_free, +}; + +static int micron_nand_on_die_ecc_setup(struct nand_chip *chip, bool enable) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN] = { 0, }; + + if (enable) + feature[0] |= ONFI_FEATURE_ON_DIE_ECC_EN; + + return chip->onfi_set_features(nand_to_mtd(chip), chip, + ONFI_FEATURE_ON_DIE_ECC, feature); +} + +static int +micron_nand_read_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, + int page) +{ + int status; + int max_bitflips = 0; + + micron_nand_on_die_ecc_setup(chip, true); + + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + status = chip->read_byte(mtd); + if (status & NAND_STATUS_FAIL) + mtd->ecc_stats.failed++; + /* + * The internal ECC doesn't tell us the number of bitflips + * that have been corrected, but tells us if it recommends to + * rewrite the block. If it's the case, then we pretend we had + * a number of bitflips equal to the ECC strength, which will + * hint the NAND core to rewrite the block. + */ + else if (status & NAND_STATUS_WRITE_RECOMMENDED) + max_bitflips = chip->ecc.strength; + + chip->cmdfunc(mtd, NAND_CMD_READ0, -1, -1); + + nand_read_page_raw(mtd, chip, buf, oob_required, page); + + micron_nand_on_die_ecc_setup(chip, false); + + return max_bitflips; +} + +static int +micron_nand_write_page_on_die_ecc(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required, + int page) +{ + micron_nand_on_die_ecc_setup(chip, true); + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + nand_write_page_raw(mtd, chip, buf, oob_required, page); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + micron_nand_on_die_ecc_setup(chip, false); + + return 0; +} + +static int +micron_nand_read_page_raw_on_die_ecc(struct mtd_info *mtd, + struct nand_chip *chip, + uint8_t *buf, int oob_required, + int page) +{ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + nand_read_page_raw(mtd, chip, buf, oob_required, page); + + return 0; +} + +static int +micron_nand_write_page_raw_on_die_ecc(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, int oob_required, + int page) +{ + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + nand_write_page_raw(mtd, chip, buf, oob_required, page); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + return 0; +} + +enum { + /* The NAND flash doesn't support on-die ECC */ + MICRON_ON_DIE_UNSUPPORTED, + + /* + * The NAND flash supports on-die ECC and it can be + * enabled/disabled by a set features command. + */ + MICRON_ON_DIE_SUPPORTED, + + /* + * The NAND flash supports on-die ECC, and it cannot be + * disabled. + */ + MICRON_ON_DIE_MANDATORY, +}; + +/* + * Try to detect if the NAND support on-die ECC. To do this, we enable + * the feature, and read back if it has been enabled as expected. We + * also check if it can be disabled, because some Micron NANDs do not + * allow disabling the on-die ECC and we don't support such NANDs for + * now. + * + * This function also has the side effect of disabling on-die ECC if + * it had been left enabled by the firmware/bootloader. + */ +static int micron_supports_on_die_ecc(struct nand_chip *chip) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN] = { 0, }; + int ret; + + if (chip->onfi_version == 0) + return MICRON_ON_DIE_UNSUPPORTED; + + if (chip->bits_per_cell != 1) + return MICRON_ON_DIE_UNSUPPORTED; + + ret = micron_nand_on_die_ecc_setup(chip, true); + if (ret) + return MICRON_ON_DIE_UNSUPPORTED; + + chip->onfi_get_features(nand_to_mtd(chip), chip, + ONFI_FEATURE_ON_DIE_ECC, feature); + if ((feature[0] & ONFI_FEATURE_ON_DIE_ECC_EN) == 0) + return MICRON_ON_DIE_UNSUPPORTED; + + ret = micron_nand_on_die_ecc_setup(chip, false); + if (ret) + return MICRON_ON_DIE_UNSUPPORTED; + + chip->onfi_get_features(nand_to_mtd(chip), chip, + ONFI_FEATURE_ON_DIE_ECC, feature); + if (feature[0] & ONFI_FEATURE_ON_DIE_ECC_EN) + return MICRON_ON_DIE_MANDATORY; + + /* + * Some Micron NANDs have an on-die ECC of 4/512, some other + * 8/512. We only support the former. + */ + if (chip->onfi_params.ecc_bits != 4) + return MICRON_ON_DIE_UNSUPPORTED; + + return MICRON_ON_DIE_SUPPORTED; +} + static int micron_nand_init(struct nand_chip *chip) { struct mtd_info *mtd = nand_to_mtd(chip); + int ondie; int ret; ret = micron_nand_onfi_init(chip); @@ -78,6 +266,34 @@ static int micron_nand_init(struct nand_chip *chip) if (mtd->writesize == 2048) chip->bbt_options |= NAND_BBT_SCAN2NDPAGE; + ondie = micron_supports_on_die_ecc(chip); + + if (ondie == MICRON_ON_DIE_MANDATORY) { + pr_err("On-die ECC forcefully enabled, not supported\n"); + return -EINVAL; + } + + if (chip->ecc.mode == NAND_ECC_ON_DIE) { + if (ondie == MICRON_ON_DIE_UNSUPPORTED) { + pr_err("On-die ECC selected but not supported\n"); + return -EINVAL; + } + + chip->ecc.options = NAND_ECC_CUSTOM_PAGE_ACCESS; + chip->ecc.bytes = 8; + chip->ecc.size = 512; + chip->ecc.strength = 4; + chip->ecc.algo = NAND_ECC_BCH; + chip->ecc.read_page = micron_nand_read_page_on_die_ecc; + chip->ecc.write_page = micron_nand_write_page_on_die_ecc; + chip->ecc.read_page_raw = + micron_nand_read_page_raw_on_die_ecc; + chip->ecc.write_page_raw = + micron_nand_write_page_raw_on_die_ecc; + + mtd_set_ooblayout(mtd, µn_nand_on_die_ooblayout_ops); + } + return 0; } diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 28f7dd9177e9..893d0ce08030 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -258,6 +258,8 @@ struct nand_chip; /* Vendor-specific feature address (Micron) */ #define ONFI_FEATURE_ADDR_READ_RETRY 0x89 +#define ONFI_FEATURE_ON_DIE_ECC 0x90 +#define ONFI_FEATURE_ON_DIE_ECC_EN BIT(3) /* ONFI subfeature parameters length */ #define ONFI_SUBFEATURE_PARAM_LEN 4 -- cgit v1.2.3-59-g8ed1b From 104e442a67cfba4d0cc982384761befb917fb6a1 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Mar 2017 09:35:58 +0100 Subject: mtd: nand: Pass the CS line to ->setup_data_interface() Some NAND controllers can assign different NAND timings to different CS lines. Pass the CS line information to ->setup_data_interface() so that the NAND controller driver knows which CS line is concerned by the setup_data_interface() request. Signed-off-by: Boris Brezillon --- drivers/mtd/nand/fsmc_nand.c | 7 +++---- drivers/mtd/nand/mxc_nand.c | 12 +++++------- drivers/mtd/nand/nand_base.c | 22 +++++++++++++--------- drivers/mtd/nand/s3c2410.c | 5 ++--- drivers/mtd/nand/sunxi_nand.c | 7 +++---- drivers/mtd/nand/tango_nand.c | 7 +++---- include/linux/mtd/nand.h | 12 ++++++++---- 7 files changed, 37 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/drivers/mtd/nand/fsmc_nand.c b/drivers/mtd/nand/fsmc_nand.c index de57554b8c4f..9d8b051d3187 100644 --- a/drivers/mtd/nand/fsmc_nand.c +++ b/drivers/mtd/nand/fsmc_nand.c @@ -388,9 +388,8 @@ static int fsmc_calc_timings(struct fsmc_nand_data *host, return 0; } -static int fsmc_setup_data_interface(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only) +static int fsmc_setup_data_interface(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) { struct nand_chip *nand = mtd_to_nand(mtd); struct fsmc_nand_data *host = nand_get_controller_data(nand); @@ -406,7 +405,7 @@ static int fsmc_setup_data_interface(struct mtd_info *mtd, if (ret) return ret; - if (check_only) + if (csline == NAND_DATA_IFACE_CHECK_ONLY) return 0; fsmc_nand_setup(host, &tims); diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 61ca020c5272..a764d5ca7536 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -152,9 +152,8 @@ struct mxc_nand_devtype_data { void (*select_chip)(struct mtd_info *mtd, int chip); int (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc); - int (*setup_data_interface)(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only); + int (*setup_data_interface)(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf); /* * On i.MX21 the CONFIG2:INT bit cannot be read if interrupts are masked @@ -1015,9 +1014,8 @@ static void preset_v1(struct mtd_info *mtd) writew(0x4, NFC_V1_V2_WRPROT); } -static int mxc_nand_v2_setup_data_interface(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only) +static int mxc_nand_v2_setup_data_interface(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) { struct nand_chip *nand_chip = mtd_to_nand(mtd); struct mxc_nand_host *host = nand_get_controller_data(nand_chip); @@ -1075,7 +1073,7 @@ static int mxc_nand_v2_setup_data_interface(struct mtd_info *mtd, return -EINVAL; } - if (check_only) + if (csline == NAND_DATA_IFACE_CHECK_ONLY) return 0; ret = clk_set_rate(host->clk, rate); diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 79d98c9fa8a7..3317acf0ce04 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -1042,12 +1042,13 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) /** * nand_reset_data_interface - Reset data interface and timings * @chip: The NAND chip + * @chipnr: Internal die id * * Reset the Data interface and timings to ONFI mode 0. * * Returns 0 for success or negative error code otherwise. */ -static int nand_reset_data_interface(struct nand_chip *chip) +static int nand_reset_data_interface(struct nand_chip *chip, int chipnr) { struct mtd_info *mtd = nand_to_mtd(chip); const struct nand_data_interface *conf; @@ -1071,7 +1072,7 @@ static int nand_reset_data_interface(struct nand_chip *chip) */ conf = nand_get_default_data_interface(); - ret = chip->setup_data_interface(mtd, conf, false); + ret = chip->setup_data_interface(mtd, chipnr, conf); if (ret) pr_err("Failed to configure data interface to SDR timing mode 0\n"); @@ -1081,6 +1082,7 @@ static int nand_reset_data_interface(struct nand_chip *chip) /** * nand_setup_data_interface - Setup the best data interface and timings * @chip: The NAND chip + * @chipnr: Internal die id * * Find and configure the best data interface and NAND timings supported by * the chip and the driver. @@ -1090,7 +1092,7 @@ static int nand_reset_data_interface(struct nand_chip *chip) * * Returns 0 for success or negative error code otherwise. */ -static int nand_setup_data_interface(struct nand_chip *chip) +static int nand_setup_data_interface(struct nand_chip *chip, int chipnr) { struct mtd_info *mtd = nand_to_mtd(chip); int ret; @@ -1114,7 +1116,7 @@ static int nand_setup_data_interface(struct nand_chip *chip) goto err; } - ret = chip->setup_data_interface(mtd, chip->data_interface, false); + ret = chip->setup_data_interface(mtd, chipnr, chip->data_interface); err: return ret; } @@ -1165,8 +1167,10 @@ static int nand_init_data_interface(struct nand_chip *chip) if (ret) continue; - ret = chip->setup_data_interface(mtd, chip->data_interface, - true); + /* Pass -1 to only */ + ret = chip->setup_data_interface(mtd, + NAND_DATA_IFACE_CHECK_ONLY, + chip->data_interface); if (!ret) { chip->onfi_timing_mode_default = mode; break; @@ -1193,7 +1197,7 @@ int nand_reset(struct nand_chip *chip, int chipnr) struct mtd_info *mtd = nand_to_mtd(chip); int ret; - ret = nand_reset_data_interface(chip); + ret = nand_reset_data_interface(chip, chipnr); if (ret) return ret; @@ -1206,7 +1210,7 @@ int nand_reset(struct nand_chip *chip, int chipnr) chip->select_chip(mtd, -1); chip->select_chip(mtd, chipnr); - ret = nand_setup_data_interface(chip); + ret = nand_setup_data_interface(chip, chipnr); chip->select_chip(mtd, -1); if (ret) return ret; @@ -4396,7 +4400,7 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, * For the other dies, nand_reset() will automatically switch to the * best mode for us. */ - ret = nand_setup_data_interface(chip); + ret = nand_setup_data_interface(chip, 0); if (ret) return ret; diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index f0b030d44f71..9e0c849607b9 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -812,9 +812,8 @@ static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, return -ENODEV; } -static int s3c2410_nand_setup_data_interface(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only) +static int s3c2410_nand_setup_data_interface(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); struct s3c2410_platform_nand *pdata = info->platform; diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c index 118a26fff368..9c2dbe352c43 100644 --- a/drivers/mtd/nand/sunxi_nand.c +++ b/drivers/mtd/nand/sunxi_nand.c @@ -1592,9 +1592,8 @@ static int _sunxi_nand_lookup_timing(const s32 *lut, int lut_size, u32 duration, #define sunxi_nand_lookup_timing(l, p, c) \ _sunxi_nand_lookup_timing(l, ARRAY_SIZE(l), p, c) -static int sunxi_nfc_setup_data_interface(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only) +static int sunxi_nfc_setup_data_interface(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) { struct nand_chip *nand = mtd_to_nand(mtd); struct sunxi_nand_chip *chip = to_sunxi_nand(nand); @@ -1707,7 +1706,7 @@ static int sunxi_nfc_setup_data_interface(struct mtd_info *mtd, return tRHW; } - if (check_only) + if (csline == NAND_DATA_IFACE_CHECK_ONLY) return 0; /* diff --git a/drivers/mtd/nand/tango_nand.c b/drivers/mtd/nand/tango_nand.c index 05b6e1065203..85e0d97593e8 100644 --- a/drivers/mtd/nand/tango_nand.c +++ b/drivers/mtd/nand/tango_nand.c @@ -476,9 +476,8 @@ static u32 to_ticks(int kHz, int ps) return DIV_ROUND_UP_ULL((u64)kHz * ps, NSEC_PER_SEC); } -static int tango_set_timings(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only) +static int tango_set_timings(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) { const struct nand_sdr_timings *sdr = nand_get_sdr_timings(conf); struct nand_chip *chip = mtd_to_nand(mtd); @@ -490,7 +489,7 @@ static int tango_set_timings(struct mtd_info *mtd, if (IS_ERR(sdr)) return PTR_ERR(sdr); - if (check_only) + if (csline == NAND_DATA_IFACE_CHECK_ONLY) return 0; Trdy = to_ticks(kHz, sdr->tCEA_max - sdr->tREA_max); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 893d0ce08030..9de3686e738c 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -107,6 +107,8 @@ int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); #define NAND_STATUS_READY 0x40 #define NAND_STATUS_WP 0x80 +#define NAND_DATA_IFACE_CHECK_ONLY -1 + /* * Constants for ECC_MODES */ @@ -818,7 +820,10 @@ struct nand_manufacturer_ops { * @read_retries: [INTERN] the number of read retry modes supported * @onfi_set_features: [REPLACEABLE] set the features for ONFI nand * @onfi_get_features: [REPLACEABLE] get the features for ONFI nand - * @setup_data_interface: [OPTIONAL] setup the data interface and timing + * @setup_data_interface: [OPTIONAL] setup the data interface and timing. If + * chipnr is set to %NAND_DATA_IFACE_CHECK_ONLY this + * means the configuration should not be applied but + * only checked. * @bbt: [INTERN] bad block table pointer * @bbt_td: [REPLACEABLE] bad block table descriptor for flash * lookup. @@ -862,9 +867,8 @@ struct nand_chip { int (*onfi_get_features)(struct mtd_info *mtd, struct nand_chip *chip, int feature_addr, uint8_t *subfeature_para); int (*setup_read_retry)(struct mtd_info *mtd, int retry_mode); - int (*setup_data_interface)(struct mtd_info *mtd, - const struct nand_data_interface *conf, - bool check_only); + int (*setup_data_interface)(struct mtd_info *mtd, int chipnr, + const struct nand_data_interface *conf); int chip_delay; -- cgit v1.2.3-59-g8ed1b From 7d135bcced20be2b50128432c5426a7278ec4f6d Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sat, 6 May 2017 18:03:33 +0200 Subject: mtd: nand: Drop the ->errstat() hook The ->errstat() hook is no longer implemented NAND controller drivers. Get rid of it before someone starts abusing it. Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 16 ---------------- include/linux/mtd/nand.h | 5 ----- 2 files changed, 21 deletions(-) (limited to 'include') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 561f70e05c88..df613125795b 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2753,14 +2753,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, if (nand_standard_page_accessors(&chip->ecc)) chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); status = chip->waitfunc(mtd, chip); - /* - * See if operation failed and additional status checks are - * available. - */ - if ((status & NAND_STATUS_FAIL) && (chip->errstat)) - status = chip->errstat(mtd, chip, FL_WRITING, status, - page); - if (status & NAND_STATUS_FAIL) return -EIO; @@ -3220,14 +3212,6 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, status = chip->erase(mtd, page & chip->pagemask); - /* - * See if operation failed and additional status checks are - * available - */ - if ((status & NAND_STATUS_FAIL) && (chip->errstat)) - status = chip->errstat(mtd, chip, FL_ERASING, - status, page); - /* See if block erase succeeded */ if (status & NAND_STATUS_FAIL) { pr_debug("%s: failed erase, page 0x%08x\n", diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 9de3686e738c..8b3607bde1b5 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -834,9 +834,6 @@ struct nand_manufacturer_ops { * structure which is shared among multiple independent * devices. * @priv: [OPTIONAL] pointer to private chip data - * @errstat: [OPTIONAL] hardware specific function to perform - * additional error status checks (determine if errors are - * correctable). * @manufacturer: [INTERN] Contains manufacturer information */ @@ -860,8 +857,6 @@ struct nand_chip { int(*waitfunc)(struct mtd_info *mtd, struct nand_chip *this); int (*erase)(struct mtd_info *mtd, int page); int (*scan_bbt)(struct mtd_info *mtd); - int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, - int status, int page); int (*onfi_set_features)(struct mtd_info *mtd, struct nand_chip *chip, int feature_addr, uint8_t *subfeature_para); int (*onfi_get_features)(struct mtd_info *mtd, struct nand_chip *chip, -- cgit v1.2.3-59-g8ed1b From 2c8f8afa7f92acb07641bf95b940d384ed1d0294 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Wed, 7 Jun 2017 20:52:10 +0900 Subject: mtd: nand: add generic helpers to check, match, maximize ECC settings Driver are responsible for setting up ECC parameters correctly. Those include: - Check if ECC parameters specified (usually by DT) are valid - Meet the chip's ECC requirement - Maximize ECC strength if NAND_ECC_MAXIMIZE flag is set The logic can be generalized by factoring out common code. This commit adds 3 helpers to the NAND framework: nand_check_ecc_caps - Check if preset step_size and strength are valid nand_match_ecc_req - Match the chip's requirement nand_maximize_ecc - Maximize the ECC strength To use the helpers above, a driver needs to provide: - Data array of supported ECC step size and strength - A hook that calculates ECC bytes from the combination of step_size and strength. By using those helpers, code duplication among drivers will be reduced. Signed-off-by: Masahiro Yamada Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 220 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 33 +++++++ 2 files changed, 253 insertions(+) (limited to 'include') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 2404bb046b69..85edac9b2bb5 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -4523,6 +4523,226 @@ static int nand_set_ecc_soft_ops(struct mtd_info *mtd) } } +/** + * nand_check_ecc_caps - check the sanity of preset ECC settings + * @chip: nand chip info structure + * @caps: ECC caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * When ECC step size and strength are already set, check if they are supported + * by the controller and the calculated ECC bytes fit within the chip's OOB. + * On success, the calculated ECC bytes is set. + */ +int nand_check_ecc_caps(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_step_info *stepinfo; + int preset_step = chip->ecc.size; + int preset_strength = chip->ecc.strength; + int nsteps, ecc_bytes; + int i, j; + + if (WARN_ON(oobavail < 0)) + return -EINVAL; + + if (!preset_step || !preset_strength) + return -ENODATA; + + nsteps = mtd->writesize / preset_step; + + for (i = 0; i < caps->nstepinfos; i++) { + stepinfo = &caps->stepinfos[i]; + + if (stepinfo->stepsize != preset_step) + continue; + + for (j = 0; j < stepinfo->nstrengths; j++) { + if (stepinfo->strengths[j] != preset_strength) + continue; + + ecc_bytes = caps->calc_ecc_bytes(preset_step, + preset_strength); + if (WARN_ON_ONCE(ecc_bytes < 0)) + return ecc_bytes; + + if (ecc_bytes * nsteps > oobavail) { + pr_err("ECC (step, strength) = (%d, %d) does not fit in OOB", + preset_step, preset_strength); + return -ENOSPC; + } + + chip->ecc.bytes = ecc_bytes; + + return 0; + } + } + + pr_err("ECC (step, strength) = (%d, %d) not supported on this controller", + preset_step, preset_strength); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(nand_check_ecc_caps); + +/** + * nand_match_ecc_req - meet the chip's requirement with least ECC bytes + * @chip: nand chip info structure + * @caps: ECC engine caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * If a chip's ECC requirement is provided, try to meet it with the least + * number of ECC bytes (i.e. with the largest number of OOB-free bytes). + * On success, the chosen ECC settings are set. + */ +int nand_match_ecc_req(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_step_info *stepinfo; + int req_step = chip->ecc_step_ds; + int req_strength = chip->ecc_strength_ds; + int req_corr, step_size, strength, nsteps, ecc_bytes, ecc_bytes_total; + int best_step, best_strength, best_ecc_bytes; + int best_ecc_bytes_total = INT_MAX; + int i, j; + + if (WARN_ON(oobavail < 0)) + return -EINVAL; + + /* No information provided by the NAND chip */ + if (!req_step || !req_strength) + return -ENOTSUPP; + + /* number of correctable bits the chip requires in a page */ + req_corr = mtd->writesize / req_step * req_strength; + + for (i = 0; i < caps->nstepinfos; i++) { + stepinfo = &caps->stepinfos[i]; + step_size = stepinfo->stepsize; + + for (j = 0; j < stepinfo->nstrengths; j++) { + strength = stepinfo->strengths[j]; + + /* + * If both step size and strength are smaller than the + * chip's requirement, it is not easy to compare the + * resulted reliability. + */ + if (step_size < req_step && strength < req_strength) + continue; + + if (mtd->writesize % step_size) + continue; + + nsteps = mtd->writesize / step_size; + + ecc_bytes = caps->calc_ecc_bytes(step_size, strength); + if (WARN_ON_ONCE(ecc_bytes < 0)) + continue; + ecc_bytes_total = ecc_bytes * nsteps; + + if (ecc_bytes_total > oobavail || + strength * nsteps < req_corr) + continue; + + /* + * We assume the best is to meet the chip's requrement + * with the least number of ECC bytes. + */ + if (ecc_bytes_total < best_ecc_bytes_total) { + best_ecc_bytes_total = ecc_bytes_total; + best_step = step_size; + best_strength = strength; + best_ecc_bytes = ecc_bytes; + } + } + } + + if (best_ecc_bytes_total == INT_MAX) + return -ENOTSUPP; + + chip->ecc.size = best_step; + chip->ecc.strength = best_strength; + chip->ecc.bytes = best_ecc_bytes; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_match_ecc_req); + +/** + * nand_maximize_ecc - choose the max ECC strength available + * @chip: nand chip info structure + * @caps: ECC engine caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * Choose the max ECC strength that is supported on the controller, and can fit + * within the chip's OOB. On success, the chosen ECC settings are set. + */ +int nand_maximize_ecc(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_step_info *stepinfo; + int step_size, strength, nsteps, ecc_bytes, corr; + int best_corr = 0; + int best_step = 0; + int best_strength, best_ecc_bytes; + int i, j; + + if (WARN_ON(oobavail < 0)) + return -EINVAL; + + for (i = 0; i < caps->nstepinfos; i++) { + stepinfo = &caps->stepinfos[i]; + step_size = stepinfo->stepsize; + + /* If chip->ecc.size is already set, respect it */ + if (chip->ecc.size && step_size != chip->ecc.size) + continue; + + for (j = 0; j < stepinfo->nstrengths; j++) { + strength = stepinfo->strengths[j]; + + if (mtd->writesize % step_size) + continue; + + nsteps = mtd->writesize / step_size; + + ecc_bytes = caps->calc_ecc_bytes(step_size, strength); + if (WARN_ON_ONCE(ecc_bytes < 0)) + continue; + + if (ecc_bytes * nsteps > oobavail) + continue; + + corr = strength * nsteps; + + /* + * If the number of correctable bits is the same, + * bigger step_size has more reliability. + */ + if (corr > best_corr || + (corr == best_corr && step_size > best_step)) { + best_corr = corr; + best_step = step_size; + best_strength = strength; + best_ecc_bytes = ecc_bytes; + } + } + } + + if (!best_corr) + return -ENOTSUPP; + + chip->ecc.size = best_step; + chip->ecc.strength = best_strength; + chip->ecc.bytes = best_ecc_bytes; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_maximize_ecc); + /* * Check if the chip configuration meet the datasheet requirements. diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 8b3607bde1b5..568f53e812cd 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -481,6 +481,30 @@ static inline void nand_hw_control_init(struct nand_hw_control *nfc) init_waitqueue_head(&nfc->wq); } +/** + * struct nand_ecc_step_info - ECC step information of ECC engine + * @stepsize: data bytes per ECC step + * @strengths: array of supported strengths + * @nstrengths: number of supported strengths + */ +struct nand_ecc_step_info { + int stepsize; + const int *strengths; + int nstrengths; +}; + +/** + * struct nand_ecc_caps - capability of ECC engine + * @stepinfos: array of ECC step information + * @nstepinfos: number of ECC step information + * @calc_ecc_bytes: driver's hook to calculate ECC bytes per step + */ +struct nand_ecc_caps { + const struct nand_ecc_step_info *stepinfos; + int nstepinfos; + int (*calc_ecc_bytes)(int step_size, int strength); +}; + /** * struct nand_ecc_ctrl - Control structure for ECC * @mode: ECC mode @@ -1246,6 +1270,15 @@ int nand_check_erased_ecc_chunk(void *data, int datalen, void *extraoob, int extraooblen, int threshold); +int nand_check_ecc_caps(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail); + +int nand_match_ecc_req(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail); + +int nand_maximize_ecc(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail); + /* Default write_oob implementation */ int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page); -- cgit v1.2.3-59-g8ed1b From a03c60178c181767ecfb26fb311a88742d228118 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Wed, 7 Jun 2017 20:52:11 +0900 Subject: mtd: nand: add a shorthand to generate nand_ecc_caps structure struct nand_ecc_caps was designed as flexible as possible to support multiple stepsizes (like sunxi_nand.c). So, we need to write multiple arrays even for the simplest case. I guess many controllers support a single stepsize, so here is a shorthand macro for the case. It allows to describe like ... NAND_ECC_CAPS_SINGLE(denali_pci_ecc_caps, denali_calc_ecc_bytes, 512, 8, 15); ... instead of static const int denali_pci_ecc_strengths[] = {8, 15}; static const struct nand_ecc_step_info denali_pci_ecc_stepinfo = { .stepsize = 512, .strengths = denali_pci_ecc_strengths, .nstrengths = ARRAY_SIZE(denali_pci_ecc_strengths), }; static const struct nand_ecc_caps denali_pci_ecc_caps = { .stepinfos = &denali_pci_ecc_stepinfo, .nstepinfos = 1, .calc_ecc_bytes = denali_calc_ecc_bytes, }; Signed-off-by: Masahiro Yamada Signed-off-by: Boris Brezillon --- include/linux/mtd/nand.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'include') diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 568f53e812cd..dc8fbc033442 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -505,6 +505,20 @@ struct nand_ecc_caps { int (*calc_ecc_bytes)(int step_size, int strength); }; +/* a shorthand to generate struct nand_ecc_caps with only one ECC stepsize */ +#define NAND_ECC_CAPS_SINGLE(__name, __calc, __step, ...) \ +static const int __name##_strengths[] = { __VA_ARGS__ }; \ +static const struct nand_ecc_step_info __name##_stepinfo = { \ + .stepsize = __step, \ + .strengths = __name##_strengths, \ + .nstrengths = ARRAY_SIZE(__name##_strengths), \ +}; \ +static const struct nand_ecc_caps __name = { \ + .stepinfos = &__name##_stepinfo, \ + .nstepinfos = 1, \ + .calc_ecc_bytes = __calc, \ +} + /** * struct nand_ecc_ctrl - Control structure for ECC * @mode: ECC mode -- cgit v1.2.3-59-g8ed1b