From 477b0229ac9bc275f6f8d2c27a2d08b246fccd0e Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Mon, 16 Nov 2015 15:53:13 +0100 Subject: mtd: introduce the mtd_pairing_scheme concept MLC and TLC NAND devices are using NAND cells exposing more than one bit, but instead of attaching all the bits in a given cell to a single NAND page, each bit is usually attached to a different page. This concept is called 'page pairing', and has significant impacts on the flash storage usage. The main problem showed by these devices is that interrupting a page program operation may not only corrupt the page we are programming but also the page it is paired with, hence the need to expose to MTD users the pairing scheme information. The pairing APIs allows one to query pairing information attached to a given page (here called wunit), or the other way around (the wunit pointed by pairing information). It also provides several helpers to help the conversion between absolute offsets and wunits, and query the number of pairing groups. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris --- drivers/mtd/mtdcore.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mtd/mtdpart.c | 1 + 2 files changed, 105 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index e3936b847c6b..4f270482cfd0 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -375,6 +375,110 @@ static int mtd_reboot_notifier(struct notifier_block *n, unsigned long state, return NOTIFY_DONE; } +/** + * mtd_wunit_to_pairing_info - get pairing information of a wunit + * @mtd: pointer to new MTD device info structure + * @wunit: write unit we are interested in + * @info: returned pairing information + * + * Retrieve pairing information associated to the wunit. + * This is mainly useful when dealing with MLC/TLC NANDs where pages can be + * paired together, and where programming a page may influence the page it is + * paired with. + * The notion of page is replaced by the term wunit (write-unit) to stay + * consistent with the ->writesize field. + * + * The @wunit argument can be extracted from an absolute offset using + * mtd_offset_to_wunit(). @info is filled with the pairing information attached + * to @wunit. + * + * From the pairing info the MTD user can find all the wunits paired with + * @wunit using the following loop: + * + * for (i = 0; i < mtd_pairing_groups(mtd); i++) { + * info.pair = i; + * mtd_pairing_info_to_wunit(mtd, &info); + * ... + * } + */ +int mtd_wunit_to_pairing_info(struct mtd_info *mtd, int wunit, + struct mtd_pairing_info *info) +{ + int npairs = mtd_wunit_per_eb(mtd) / mtd_pairing_groups(mtd); + + if (wunit < 0 || wunit >= npairs) + return -EINVAL; + + if (mtd->pairing && mtd->pairing->get_info) + return mtd->pairing->get_info(mtd, wunit, info); + + info->group = 0; + info->pair = wunit; + + return 0; +} +EXPORT_SYMBOL_GPL(mtd_wunit_to_pairing_info); + +/** + * mtd_wunit_to_pairing_info - get wunit from pairing information + * @mtd: pointer to new MTD device info structure + * @info: pairing information struct + * + * Returns a positive number representing the wunit associated to the info + * struct, or a negative error code. + * + * This is the reverse of mtd_wunit_to_pairing_info(), and can help one to + * iterate over all wunits of a given pair (see mtd_wunit_to_pairing_info() + * doc). + * + * It can also be used to only program the first page of each pair (i.e. + * page attached to group 0), which allows one to use an MLC NAND in + * software-emulated SLC mode: + * + * info.group = 0; + * npairs = mtd_wunit_per_eb(mtd) / mtd_pairing_groups(mtd); + * for (info.pair = 0; info.pair < npairs; info.pair++) { + * wunit = mtd_pairing_info_to_wunit(mtd, &info); + * mtd_write(mtd, mtd_wunit_to_offset(mtd, blkoffs, wunit), + * mtd->writesize, &retlen, buf + (i * mtd->writesize)); + * } + */ +int mtd_pairing_info_to_wunit(struct mtd_info *mtd, + const struct mtd_pairing_info *info) +{ + int ngroups = mtd_pairing_groups(mtd); + int npairs = mtd_wunit_per_eb(mtd) / ngroups; + + if (!info || info->pair < 0 || info->pair >= npairs || + info->group < 0 || info->group >= ngroups) + return -EINVAL; + + if (mtd->pairing && mtd->pairing->get_wunit) + return mtd->pairing->get_wunit(mtd, info); + + return info->pair; +} +EXPORT_SYMBOL_GPL(mtd_pairing_info_to_wunit); + +/** + * mtd_pairing_groups - get the number of pairing groups + * @mtd: pointer to new MTD device info structure + * + * Returns the number of pairing groups. + * + * This number is usually equal to the number of bits exposed by a single + * cell, and can be used in conjunction with mtd_pairing_info_to_wunit() + * to iterate over all pages of a given pair. + */ +int mtd_pairing_groups(struct mtd_info *mtd) +{ + if (!mtd->pairing || !mtd->pairing->ngroups) + return 1; + + return mtd->pairing->ngroups; +} +EXPORT_SYMBOL_GPL(mtd_pairing_groups); + /** * add_mtd_device - register an MTD device * @mtd: pointer to new MTD device info structure diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 1f13e32556f8..e32a0ac2298f 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -397,6 +397,7 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, slave->mtd.oobsize = master->oobsize; slave->mtd.oobavail = master->oobavail; slave->mtd.subpage_sft = master->subpage_sft; + slave->mtd.pairing = master->pairing; slave->mtd.name = name; slave->mtd.owner = master->owner; -- cgit v1.2.3-59-g8ed1b From d45bc58dd3bdcaabc1d7d8d9b0b8dee826635cc6 Mon Sep 17 00:00:00 2001 From: Marc Gonzalez Date: Wed, 27 Jul 2016 11:23:52 +0200 Subject: mtd: nand: import nand_hw_control_init() The code to initialize a struct nand_hw_control is duplicated across several drivers. Factorize it using an inline function. Signed-off-by: Marc Gonzalez Signed-off-by: Boris Brezillon --- drivers/mtd/nand/bf5xx_nand.c | 3 +-- drivers/mtd/nand/brcmnand/brcmnand.c | 3 +-- drivers/mtd/nand/docg4.c | 3 +-- drivers/mtd/nand/fsl_elbc_nand.c | 3 +-- drivers/mtd/nand/fsl_ifc_nand.c | 3 +-- drivers/mtd/nand/jz4780_nand.c | 3 +-- drivers/mtd/nand/nand_base.c | 3 +-- drivers/mtd/nand/ndfc.c | 3 +-- drivers/mtd/nand/pxa3xx_nand.c | 3 +-- drivers/mtd/nand/qcom_nandc.c | 3 +-- drivers/mtd/nand/s3c2410.c | 3 +-- drivers/mtd/nand/sunxi_nand.c | 3 +-- drivers/mtd/nand/txx9ndfmc.c | 3 +-- include/linux/mtd/nand.h | 7 +++++++ 14 files changed, 20 insertions(+), 26 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c index 37da4236ab90..3962f55bd034 100644 --- a/drivers/mtd/nand/bf5xx_nand.c +++ b/drivers/mtd/nand/bf5xx_nand.c @@ -761,8 +761,7 @@ static int bf5xx_nand_probe(struct platform_device *pdev) platform_set_drvdata(pdev, info); - spin_lock_init(&info->controller.lock); - init_waitqueue_head(&info->controller.wq); + nand_hw_control_init(&info->controller); info->device = &pdev->dev; info->platform = plat; diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c index 8eb2c64df38c..82ec36bb8296 100644 --- a/drivers/mtd/nand/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/brcmnand/brcmnand.c @@ -2370,8 +2370,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc) init_completion(&ctrl->done); init_completion(&ctrl->dma_done); - spin_lock_init(&ctrl->controller.lock); - init_waitqueue_head(&ctrl->controller.wq); + nand_hw_control_init(&ctrl->controller); INIT_LIST_HEAD(&ctrl->host_list); /* NAND register range */ diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c index 47316998017f..7af2a3cd949e 100644 --- a/drivers/mtd/nand/docg4.c +++ b/drivers/mtd/nand/docg4.c @@ -1249,8 +1249,7 @@ static void __init init_mtd_structs(struct mtd_info *mtd) nand->options = NAND_BUSWIDTH_16 | NAND_NO_SUBPAGE_WRITE; nand->IO_ADDR_R = nand->IO_ADDR_W = doc->virtadr + DOC_IOSPACE_DATA; nand->controller = &nand->hwcontrol; - spin_lock_init(&nand->controller->lock); - init_waitqueue_head(&nand->controller->wq); + nand_hw_control_init(nand->controller); /* methods */ nand->cmdfunc = docg4_command; diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c index 60a88f24c6b3..113f76e59937 100644 --- a/drivers/mtd/nand/fsl_elbc_nand.c +++ b/drivers/mtd/nand/fsl_elbc_nand.c @@ -879,8 +879,7 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev) } elbc_fcm_ctrl->counter++; - spin_lock_init(&elbc_fcm_ctrl->controller.lock); - init_waitqueue_head(&elbc_fcm_ctrl->controller.wq); + nand_hw_control_init(&elbc_fcm_ctrl->controller); fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl; } else { elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand; diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c index 4e9e5fd8faf3..0a177b1bfe3e 100644 --- a/drivers/mtd/nand/fsl_ifc_nand.c +++ b/drivers/mtd/nand/fsl_ifc_nand.c @@ -987,8 +987,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev) ifc_nand_ctrl->addr = NULL; fsl_ifc_ctrl_dev->nand = ifc_nand_ctrl; - spin_lock_init(&ifc_nand_ctrl->controller.lock); - init_waitqueue_head(&ifc_nand_ctrl->controller.wq); + nand_hw_control_init(&ifc_nand_ctrl->controller); } else { ifc_nand_ctrl = fsl_ifc_ctrl_dev->nand; } diff --git a/drivers/mtd/nand/jz4780_nand.c b/drivers/mtd/nand/jz4780_nand.c index 175f67da25af..a39bb70175ee 100644 --- a/drivers/mtd/nand/jz4780_nand.c +++ b/drivers/mtd/nand/jz4780_nand.c @@ -368,9 +368,8 @@ static int jz4780_nand_probe(struct platform_device *pdev) nfc->dev = dev; nfc->num_banks = num_banks; - spin_lock_init(&nfc->controller.lock); + nand_hw_control_init(&nfc->controller); INIT_LIST_HEAD(&nfc->chips); - init_waitqueue_head(&nfc->controller.wq); ret = jz4780_nand_init_chips(nfc, pdev); if (ret) { diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 77533f7f2429..53ea79628dde 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -3191,8 +3191,7 @@ static void nand_set_defaults(struct nand_chip *chip, int busw) if (!chip->controller) { chip->controller = &chip->hwcontrol; - spin_lock_init(&chip->controller->lock); - init_waitqueue_head(&chip->controller->wq); + nand_hw_control_init(chip->controller); } } diff --git a/drivers/mtd/nand/ndfc.c b/drivers/mtd/nand/ndfc.c index 218c789ca7ab..28e6118362f7 100644 --- a/drivers/mtd/nand/ndfc.c +++ b/drivers/mtd/nand/ndfc.c @@ -218,8 +218,7 @@ static int ndfc_probe(struct platform_device *ofdev) ndfc = &ndfc_ctrl[cs]; ndfc->chip_select = cs; - spin_lock_init(&ndfc->ndfc_control.lock); - init_waitqueue_head(&ndfc->ndfc_control.wq); + nand_hw_control_init(&ndfc->ndfc_control); ndfc->ofdev = ofdev; dev_set_drvdata(&ofdev->dev, ndfc); diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index 436dd6dc11f4..b121bf4ed73a 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -1810,8 +1810,7 @@ static int alloc_nand_resource(struct platform_device *pdev) chip->cmdfunc = nand_cmdfunc; } - spin_lock_init(&chip->controller->lock); - init_waitqueue_head(&chip->controller->wq); + nand_hw_control_init(chip->controller); info->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(info->clk)) { dev_err(&pdev->dev, "failed to get nand clock\n"); diff --git a/drivers/mtd/nand/qcom_nandc.c b/drivers/mtd/nand/qcom_nandc.c index de7d28e62d4e..57d483ac5765 100644 --- a/drivers/mtd/nand/qcom_nandc.c +++ b/drivers/mtd/nand/qcom_nandc.c @@ -1957,8 +1957,7 @@ static int qcom_nandc_alloc(struct qcom_nand_controller *nandc) INIT_LIST_HEAD(&nandc->desc_list); INIT_LIST_HEAD(&nandc->host_list); - spin_lock_init(&nandc->controller.lock); - init_waitqueue_head(&nandc->controller.wq); + nand_hw_control_init(&nandc->controller); return 0; } diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index d9309cf0ce2e..b1734d76cbc1 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -977,8 +977,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev) platform_set_drvdata(pdev, info); - spin_lock_init(&info->controller.lock); - init_waitqueue_head(&info->controller.wq); + nand_hw_control_init(&info->controller); /* get the clock source and enable it */ diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c index e414b31b71c1..8b5dadc9cd26 100644 --- a/drivers/mtd/nand/sunxi_nand.c +++ b/drivers/mtd/nand/sunxi_nand.c @@ -2175,8 +2175,7 @@ static int sunxi_nfc_probe(struct platform_device *pdev) return -ENOMEM; nfc->dev = dev; - spin_lock_init(&nfc->controller.lock); - init_waitqueue_head(&nfc->controller.wq); + nand_hw_control_init(&nfc->controller); INIT_LIST_HEAD(&nfc->chips); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/mtd/nand/txx9ndfmc.c b/drivers/mtd/nand/txx9ndfmc.c index 04d63f56baa4..0a14fda2e41b 100644 --- a/drivers/mtd/nand/txx9ndfmc.c +++ b/drivers/mtd/nand/txx9ndfmc.c @@ -303,8 +303,7 @@ static int __init txx9ndfmc_probe(struct platform_device *dev) dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n", (gbusclk + 500000) / 1000000, hold, spw); - spin_lock_init(&drvdata->hw_control.lock); - init_waitqueue_head(&drvdata->hw_control.wq); + nand_hw_control_init(&drvdata->hw_control); platform_set_drvdata(dev, drvdata); txx9ndfmc_initialize(dev); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 8dd6e01f45c0..f6a2d5e7313c 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -460,6 +460,13 @@ struct nand_hw_control { wait_queue_head_t wq; }; +static inline void nand_hw_control_init(struct nand_hw_control *nfc) +{ + nfc->active = NULL; + spin_lock_init(&nfc->lock); + init_waitqueue_head(&nfc->wq); +} + /** * struct nand_ecc_ctrl - Control structure for ECC * @mode: ECC mode -- cgit v1.2.3-59-g8ed1b From 107896c5889a3f1dc7d985d6a0fdb3a1c0e6e047 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 21 Jul 2016 14:59:18 -0700 Subject: Kconfig: nand: Make MTD_NAND_FSL_ELBC depend on FSL_SOC MTD_NAND_FSL_ELBC selects FSL_LBC that in turn depends on FSL_SOC, so depending on PPC instead of FSL_SOC leads to this message: warning: (MPC836x_RDK && MTD_NAND_FSL_ELBC && MTD_NAND_FSL_UPM) selects FSL_LBC which has unmet direct dependencies (FSL_SOC) when doing make ARCH=powerpc \ CROSS_COMPILE=powerpc-e500v2-linux-gnuspe- \ allmodconfig" Changing dependency to FSL_SOC fixes that. Signed-off-by: Andrey Smirnov Signed-off-by: Boris Brezillon --- drivers/mtd/nand/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 21ff58099f3b..da23ca88c16b 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -428,7 +428,7 @@ config MTD_NAND_ORION config MTD_NAND_FSL_ELBC tristate "NAND support for Freescale eLBC controllers" - depends on PPC + depends on FSL_SOC select FSL_LBC help Various Freescale chips, including the 8313, include a NAND Flash -- cgit v1.2.3-59-g8ed1b From 75c19e58abaed01ae20ad5802f6a6aa5a255117d Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 21 Jul 2016 14:59:19 -0700 Subject: Kconfig: nand: Remove redundant dependency on MTD_NAND Config MTD_NAND_FSL_IFC is already located inside 'if MTD_NAND' statment, so there's no need to explicitly specify it as a dependency. Signed-off-by: Andrey Smirnov Signed-off-by: Boris Brezillon --- drivers/mtd/nand/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index da23ca88c16b..b2ba9ef553bd 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -438,7 +438,7 @@ config MTD_NAND_FSL_ELBC config MTD_NAND_FSL_IFC tristate "NAND support for Freescale IFC controller" - depends on MTD_NAND && (FSL_SOC || ARCH_LAYERSCAPE) + depends on FSL_SOC || ARCH_LAYERSCAPE select FSL_IFC select MEMORY help -- cgit v1.2.3-59-g8ed1b From 76fe334f71dd20f7eadd4c624441ec7d1fb92f33 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 21 Jul 2016 14:59:20 -0700 Subject: mtd: nand: Error out if cmd_ctrl() is missing If no user specified chip->select_chip() function is provided, code in nand_base.c will automatically set this hook to nand_select_chip(), which in turn depends on chip->cmd_ctrl() hook being valid. Not providing both of those functions in NAND controller driver (for example by mistake) will result in a bit cryptic segfault. Same is true for chip->cmdfunc(). To avoid the above scenario add a check in nand_scan_dent and error out if cmd_ctrl() is not provided. Suggested-by: Boris Brezillon Suggested-by: Brian Norris Signed-off-by: Andrey Smirnov Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 53ea79628dde..7f742c9c9015 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -4140,6 +4140,15 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, if (!mtd->name && mtd->dev.parent) mtd->name = dev_name(mtd->dev.parent); + if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) { + /* + * Default functions assigned for chip_select() and + * cmdfunc() both expect cmd_ctrl() to be populated, + * so we need to check that that's the case + */ + pr_err("chip.cmd_ctrl() callback is not provided"); + return -EINVAL; + } /* Set the default functions */ nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16); -- cgit v1.2.3-59-g8ed1b From fc6b4d12bc394aa9b5c22aa3a2f5128ad6c84a72 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 21 Jul 2016 14:59:21 -0700 Subject: mtd: nand: Get rid of needless 'goto' Using "goto" and "switch" statement only makes it harder to follow control flow and doesn't bring any advantages. Rewrite the code to avoid using "goto". Signed-off-by: Brian Norris Signed-off-by: Andrey Smirnov Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 7f742c9c9015..108adefcc8cc 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2162,7 +2162,7 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, static int nand_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { - int ret = -ENOTSUPP; + int ret; ops->retlen = 0; @@ -2173,24 +2173,18 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from, return -EINVAL; } - nand_get_device(mtd, FL_READING); - - switch (ops->mode) { - case MTD_OPS_PLACE_OOB: - case MTD_OPS_AUTO_OOB: - case MTD_OPS_RAW: - break; + if (ops->mode != MTD_OPS_PLACE_OOB && + ops->mode != MTD_OPS_AUTO_OOB && + ops->mode != MTD_OPS_RAW) + return -ENOTSUPP; - default: - goto out; - } + nand_get_device(mtd, FL_READING); if (!ops->datbuf) ret = nand_do_read_oob(mtd, from, ops); else ret = nand_do_read_ops(mtd, from, ops); -out: nand_release_device(mtd); return ret; } -- cgit v1.2.3-59-g8ed1b From d9ca77f0eafabb5495542386777ab2833442089b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jun 2016 14:51:38 +0200 Subject: mtd: nand: s3c2410: Register cpufreq notifier only on S3C24xx The driver registered for CPU frequency transitions to recalculate its clock when ARM clock frequency changes (ratio between frequencies of ARM's parent clock (fclk) and clock for peripherals remains fixed). This is needed only on S3C24xx platform when cpufreq driver is enabled so limit the ifdef to respective cpufreq Kconfig. Suggested-by: Marek Szyprowski Signed-off-by: Krzysztof Kozlowski Signed-off-by: Boris Brezillon --- drivers/mtd/nand/s3c2410.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index b1734d76cbc1..d459c19d78de 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -180,7 +180,7 @@ struct s3c2410_nand_info { enum s3c_cpu_type cpu_type; -#ifdef CONFIG_CPU_FREQ +#ifdef CONFIG_ARM_S3C24XX_CPUFREQ struct notifier_block freq_transition; #endif }; @@ -701,7 +701,7 @@ static void s3c2440_nand_write_buf(struct mtd_info *mtd, const u_char *buf, /* cpufreq driver support */ -#ifdef CONFIG_CPU_FREQ +#ifdef CONFIG_ARM_S3C24XX_CPUFREQ static int s3c2410_nand_cpufreq_transition(struct notifier_block *nb, unsigned long val, void *data) -- cgit v1.2.3-59-g8ed1b From 3fe4f900bf3061b3994c56eea497d8a839241277 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Thu, 5 May 2016 12:42:44 -0700 Subject: mtd: nand: sh_flctl: handle dma_submit() errors Some build tools noticed that 'cookie' is being set but not used. Might as well catch the errors here and handle them the same way we handle other DMA prep steps. Signed-off-by: Brian Norris Signed-off-by: Boris Brezillon --- drivers/mtd/nand/sh_flctl.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c index 6fa3bcd59769..442ce619b3b6 100644 --- a/drivers/mtd/nand/sh_flctl.c +++ b/drivers/mtd/nand/sh_flctl.c @@ -397,7 +397,7 @@ static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, struct dma_chan *chan; enum dma_transfer_direction tr_dir; dma_addr_t dma_addr; - dma_cookie_t cookie = -EINVAL; + dma_cookie_t cookie; uint32_t reg; int ret; @@ -423,6 +423,12 @@ static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, desc->callback = flctl_dma_complete; desc->callback_param = flctl; cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) { + ret = dma_submit_error(cookie); + dev_warn(&flctl->pdev->dev, + "DMA submit failed, falling back to PIO\n"); + goto out; + } dma_async_issue_pending(chan); } else { -- cgit v1.2.3-59-g8ed1b From 1848a057a29f74d2a0517c5123a281002739ba31 Mon Sep 17 00:00:00 2001 From: Han Xu Date: Tue, 12 Apr 2016 17:06:33 -0500 Subject: mtd: nand: gpmi: get correct free oob space change the way to calculate pagesize to get correct free oob space for legacy_set_geometry function. Signed-off-by: Han Xu Signed-off-by: Boris Brezillon --- drivers/mtd/nand/gpmi-nand/gpmi-nand.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c index 6e461560c6a8..6c062b8251d2 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c @@ -318,7 +318,8 @@ static int legacy_set_geometry(struct gpmi_nand_data *this) return -EINVAL; } - geo->page_size = mtd->writesize + mtd->oobsize; + geo->page_size = mtd->writesize + geo->metadata_size + + (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8; geo->payload_size = mtd->writesize; /* -- cgit v1.2.3-59-g8ed1b From 74a332e78e8f0e2b8bef7ebb6b3470bb0df1a0bf Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 16:21:06 +0200 Subject: mtd: nand: timings: Fix tADL_min for ONFI 4.0 chips ONFI 4.0 spec defines different values for the tADL_min timing. Since we don't want to have different timings depending on the ONFI version, we just set tADL_min to the maximum value (the one specified in the ONFI 4.0 spec). Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_timings.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c index e81470a8ac67..c0941d5eb701 100644 --- a/drivers/mtd/nand/nand_timings.c +++ b/drivers/mtd/nand/nand_timings.c @@ -16,7 +16,7 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { /* Mode 0 */ { - .tADL_min = 200000, + .tADL_min = 400000, .tALH_min = 20000, .tALS_min = 50000, .tAR_min = 25000, @@ -53,7 +53,7 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { }, /* Mode 1 */ { - .tADL_min = 100000, + .tADL_min = 400000, .tALH_min = 10000, .tALS_min = 25000, .tAR_min = 10000, @@ -90,7 +90,7 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { }, /* Mode 2 */ { - .tADL_min = 100000, + .tADL_min = 400000, .tALH_min = 10000, .tALS_min = 15000, .tAR_min = 10000, @@ -127,7 +127,7 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { }, /* Mode 3 */ { - .tADL_min = 100000, + .tADL_min = 400000, .tALH_min = 5000, .tALS_min = 10000, .tAR_min = 10000, @@ -164,7 +164,7 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { }, /* Mode 4 */ { - .tADL_min = 70000, + .tADL_min = 400000, .tALH_min = 5000, .tALS_min = 10000, .tAR_min = 10000, @@ -201,7 +201,7 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { }, /* Mode 5 */ { - .tADL_min = 70000, + .tADL_min = 400000, .tALH_min = 5000, .tALS_min = 10000, .tAR_min = 10000, -- cgit v1.2.3-59-g8ed1b From 57d419a42c501713fd023bc21f8798c14ad4e005 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 16:21:07 +0200 Subject: mtd: nand: timings: Reorder tRR_min def in mode 0 In the ONFI spec, the tRR_min entry is defined before the tRST_max one. Reoder the definition to make it easier to review. Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_timings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c index c0941d5eb701..30119c588e0f 100644 --- a/drivers/mtd/nand/nand_timings.c +++ b/drivers/mtd/nand/nand_timings.c @@ -42,9 +42,9 @@ static const struct nand_sdr_timings onfi_sdr_timings[] = { .tRHZ_max = 200000, .tRLOH_min = 0, .tRP_min = 50000, + .tRR_min = 40000, .tRST_max = 250000000000ULL, .tWB_max = 200000, - .tRR_min = 40000, .tWC_min = 100000, .tWH_min = 30000, .tWHR_min = 120000, -- cgit v1.2.3-59-g8ed1b From c3baf278d3bf8d628df0b28aa4921c5457d40211 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Mon, 15 Aug 2016 18:22:01 -0500 Subject: mtd: nand_bbt: Move BBT block selection logic out of write_bbt() This clarifies the write_bbt() function by removing the write label and simplifying the error/exit path. Signed-off-by: Boris Brezillon Tested-by: Kyle Roeschley --- drivers/mtd/nand/nand_bbt.c | 110 +++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 36 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index 2fbb523df066..9fbd66482403 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -604,6 +604,69 @@ static void search_read_bbts(struct mtd_info *mtd, uint8_t *buf, search_bbt(mtd, buf, md); } +/** + * get_bbt_block - Get the first valid eraseblock suitable to store a BBT + * @this: the NAND device + * @td: the BBT description + * @md: the mirror BBT descriptor + * @chip: the CHIP selector + * + * This functions returns a positive block number pointing a valid eraseblock + * suitable to store a BBT (i.e. in the range reserved for BBT), or -ENOSPC if + * all blocks are already used of marked bad. If td->pages[chip] was already + * pointing to a valid block we re-use it, otherwise we search for the next + * valid one. + */ +static int get_bbt_block(struct nand_chip *this, struct nand_bbt_descr *td, + struct nand_bbt_descr *md, int chip) +{ + int startblock, dir, page, numblocks, i; + + /* + * There was already a version of the table, reuse the page. This + * applies for absolute placement too, as we have the page number in + * td->pages. + */ + if (td->pages[chip] != -1) + return td->pages[chip] >> + (this->bbt_erase_shift - this->page_shift); + + numblocks = (int)(this->chipsize >> this->bbt_erase_shift); + if (!(td->options & NAND_BBT_PERCHIP)) + numblocks *= this->numchips; + + /* + * Automatic placement of the bad block table. Search direction + * top -> down? + */ + if (td->options & NAND_BBT_LASTBLOCK) { + startblock = numblocks * (chip + 1) - 1; + dir = -1; + } else { + startblock = chip * numblocks; + dir = 1; + } + + for (i = 0; i < td->maxblocks; i++) { + int block = startblock + dir * i; + + /* Check, if the block is bad */ + switch (bbt_get_entry(this, block)) { + case BBT_BLOCK_WORN: + case BBT_BLOCK_FACTORY_BAD: + continue; + } + + page = block << (this->bbt_erase_shift - this->page_shift); + + /* Check, if the block is used by the mirror table */ + if (!md || md->pages[chip] != page) + return block; + } + + return -ENOSPC; +} + /** * write_bbt - [GENERIC] (Re)write the bad block table * @mtd: MTD device structure @@ -621,7 +684,7 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_chip *this = mtd_to_nand(mtd); struct erase_info einfo; int i, res, chip = 0; - int bits, startblock, dir, page, offs, numblocks, sft, sftmsk; + int bits, page, offs, numblocks, sft, sftmsk; int nrchips, pageoffs, ooboffs; uint8_t msk[4]; uint8_t rcode = td->reserved_block_code; @@ -653,45 +716,20 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, /* Loop through the chips */ for (; chip < nrchips; chip++) { - /* - * There was already a version of the table, reuse the page - * This applies for absolute placement too, as we have the - * page nr. in td->pages. - */ - if (td->pages[chip] != -1) { - page = td->pages[chip]; - goto write; + int block; + + block = get_bbt_block(this, td, md, chip); + if (block < 0) { + pr_err("No space left to write bad block table\n"); + res = block; + goto outerr; } /* - * Automatic placement of the bad block table. Search direction - * top -> down? + * get_bbt_block() returns a block number, shift the value to + * get a page number. */ - if (td->options & NAND_BBT_LASTBLOCK) { - startblock = numblocks * (chip + 1) - 1; - dir = -1; - } else { - startblock = chip * numblocks; - dir = 1; - } - - for (i = 0; i < td->maxblocks; i++) { - int block = startblock + dir * i; - /* Check, if the block is bad */ - switch (bbt_get_entry(this, block)) { - case BBT_BLOCK_WORN: - case BBT_BLOCK_FACTORY_BAD: - continue; - } - page = block << - (this->bbt_erase_shift - this->page_shift); - /* Check, if the block is used by the mirror table */ - if (!md || md->pages[chip] != page) - goto write; - } - pr_err("No space left to write bad block table\n"); - return -ENOSPC; - write: + page = block << (this->bbt_erase_shift - this->page_shift); /* Set up shift count and masks for the flash table */ bits = td->options & NAND_BBT_NRBITS_MSK; -- cgit v1.2.3-59-g8ed1b From 10ffd570f11701972aff2a6f91f3d253d6f0e7ee Mon Sep 17 00:00:00 2001 From: Kyle Roeschley Date: Mon, 15 Aug 2016 18:22:02 -0500 Subject: mtd: nand_bbt: scan for next free bbt block if writing bbt fails If erasing or writing the BBT fails, we should mark the current BBT block as bad and use the BBT descriptor to scan for the next available unused block in the BBT. We should only return a failure if there isn't any space left. Signed-off-by: Kyle Roeschley Suggested-by: Jeff Westfahl Tested-by: Kyle Roeschley Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_bbt.c | 51 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index 9fbd66482403..7695efea65f2 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -667,6 +667,37 @@ static int get_bbt_block(struct nand_chip *this, struct nand_bbt_descr *td, return -ENOSPC; } +/** + * mark_bbt_block_bad - Mark one of the block reserved for BBT bad + * @this: the NAND device + * @td: the BBT description + * @chip: the CHIP selector + * @block: the BBT block to mark + * + * Blocks reserved for BBT can become bad. This functions is an helper to mark + * such blocks as bad. It takes care of updating the in-memory BBT, marking the + * block as bad using a bad block marker and invalidating the associated + * td->pages[] entry. + */ +static void mark_bbt_block_bad(struct nand_chip *this, + struct nand_bbt_descr *td, + int chip, int block) +{ + struct mtd_info *mtd = nand_to_mtd(this); + loff_t to; + int res; + + bbt_mark_entry(this, block, BBT_BLOCK_WORN); + + to = (loff_t)block << this->bbt_erase_shift; + res = this->block_markbad(mtd, to); + if (res) + pr_warn("nand_bbt: error %d while marking block %d bad\n", + res, block); + + td->pages[chip] = -1; +} + /** * write_bbt - [GENERIC] (Re)write the bad block table * @mtd: MTD device structure @@ -715,7 +746,7 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, } /* Loop through the chips */ - for (; chip < nrchips; chip++) { + while (chip < nrchips) { int block; block = get_bbt_block(this, td, md, chip); @@ -825,20 +856,28 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, einfo.addr = to; einfo.len = 1 << this->bbt_erase_shift; res = nand_erase_nand(mtd, &einfo, 1); - if (res < 0) - goto outerr; + if (res < 0) { + pr_warn("nand_bbt: error while erasing BBT block %d\n", + res); + mark_bbt_block_bad(this, td, chip, block); + continue; + } res = scan_write_bbt(mtd, to, len, buf, td->options & NAND_BBT_NO_OOB ? NULL : &buf[len]); - if (res < 0) - goto outerr; + if (res < 0) { + pr_warn("nand_bbt: error while writing BBT block %d\n", + res); + mark_bbt_block_bad(this, td, chip, block); + continue; + } pr_info("Bad block table written to 0x%012llx, version 0x%02X\n", (unsigned long long)to, td->version[chip]); /* Mark it as used */ - td->pages[chip] = page; + td->pages[chip++] = page; } return 0; -- cgit v1.2.3-59-g8ed1b From eab7fdc7bb84040537e4c6da5ad1b55a4bfec6e1 Mon Sep 17 00:00:00 2001 From: Ray Jui Date: Wed, 20 Jul 2016 14:53:50 -0700 Subject: mtd: brcmnand: iProc big endian and ONFI support This patch adds big endian and ONFI support for various iProc based SoCs that use the core brcmstb NAND controller This patch was originally implemented by Prafulla Kota and fully tested on iProc based NS2 SVK Signed-off-by: Prafulla Kota Signed-off-by: Ray Jui Reviewed-by: Kamal Dasu Acked-by: Kamal Dasu Signed-off-by: Boris Brezillon --- drivers/mtd/nand/brcmnand/brcmnand.c | 12 ++++++------ drivers/mtd/nand/brcmnand/brcmnand.h | 13 ++++++++----- drivers/mtd/nand/brcmnand/iproc_nand.c | 18 ++++++++++++++---- 3 files changed, 28 insertions(+), 15 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c index 82ec36bb8296..9d2424bfdbf5 100644 --- a/drivers/mtd/nand/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/brcmnand/brcmnand.c @@ -1336,7 +1336,7 @@ static void brcmnand_cmdfunc(struct mtd_info *mtd, unsigned command, u32 *flash_cache = (u32 *)ctrl->flash_cache; int i; - brcmnand_soc_data_bus_prepare(ctrl->soc); + brcmnand_soc_data_bus_prepare(ctrl->soc, true); /* * Must cache the FLASH_CACHE now, since changes in @@ -1349,7 +1349,7 @@ static void brcmnand_cmdfunc(struct mtd_info *mtd, unsigned command, */ flash_cache[i] = be32_to_cpu(brcmnand_read_fc(ctrl, i)); - brcmnand_soc_data_bus_unprepare(ctrl->soc); + brcmnand_soc_data_bus_unprepare(ctrl->soc, true); /* Cleanup from HW quirk: restore SECTOR_SIZE_1K */ if (host->hwcfg.sector_size_1k) @@ -1565,12 +1565,12 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip, brcmnand_waitfunc(mtd, chip); if (likely(buf)) { - brcmnand_soc_data_bus_prepare(ctrl->soc); + brcmnand_soc_data_bus_prepare(ctrl->soc, false); for (j = 0; j < FC_WORDS; j++, buf++) *buf = brcmnand_read_fc(ctrl, j); - brcmnand_soc_data_bus_unprepare(ctrl->soc); + brcmnand_soc_data_bus_unprepare(ctrl->soc, false); } if (oob) @@ -1815,12 +1815,12 @@ static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip, (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); if (buf) { - brcmnand_soc_data_bus_prepare(ctrl->soc); + brcmnand_soc_data_bus_prepare(ctrl->soc, false); for (j = 0; j < FC_WORDS; j++, buf++) brcmnand_write_fc(ctrl, j, *buf); - brcmnand_soc_data_bus_unprepare(ctrl->soc); + brcmnand_soc_data_bus_unprepare(ctrl->soc, false); } else if (oob) { for (j = 0; j < FC_WORDS; j++) brcmnand_write_fc(ctrl, j, 0xffffffff); diff --git a/drivers/mtd/nand/brcmnand/brcmnand.h b/drivers/mtd/nand/brcmnand/brcmnand.h index ef5eabba88e5..5c44cd4aba87 100644 --- a/drivers/mtd/nand/brcmnand/brcmnand.h +++ b/drivers/mtd/nand/brcmnand/brcmnand.h @@ -23,19 +23,22 @@ struct dev_pm_ops; struct brcmnand_soc { bool (*ctlrdy_ack)(struct brcmnand_soc *soc); void (*ctlrdy_set_enabled)(struct brcmnand_soc *soc, bool en); - void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare); + void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare, + bool is_param); }; -static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc) +static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc, + bool is_param) { if (soc && soc->prepare_data_bus) - soc->prepare_data_bus(soc, true); + soc->prepare_data_bus(soc, true, is_param); } -static inline void brcmnand_soc_data_bus_unprepare(struct brcmnand_soc *soc) +static inline void brcmnand_soc_data_bus_unprepare(struct brcmnand_soc *soc, + bool is_param) { if (soc && soc->prepare_data_bus) - soc->prepare_data_bus(soc, false); + soc->prepare_data_bus(soc, false, is_param); } static inline u32 brcmnand_readl(void __iomem *addr) diff --git a/drivers/mtd/nand/brcmnand/iproc_nand.c b/drivers/mtd/nand/brcmnand/iproc_nand.c index 585596c549b2..4c6ae113664d 100644 --- a/drivers/mtd/nand/brcmnand/iproc_nand.c +++ b/drivers/mtd/nand/brcmnand/iproc_nand.c @@ -74,7 +74,8 @@ static void iproc_nand_intc_set(struct brcmnand_soc *soc, bool en) spin_unlock_irqrestore(&priv->idm_lock, flags); } -static void iproc_nand_apb_access(struct brcmnand_soc *soc, bool prepare) +static void iproc_nand_apb_access(struct brcmnand_soc *soc, bool prepare, + bool is_param) { struct iproc_nand_soc *priv = container_of(soc, struct iproc_nand_soc, soc); @@ -86,10 +87,19 @@ static void iproc_nand_apb_access(struct brcmnand_soc *soc, bool prepare) val = brcmnand_readl(mmio); - if (prepare) - val |= IPROC_NAND_APB_LE_MODE; - else + /* + * In the case of BE or when dealing with NAND data, alway configure + * the APB bus to LE mode before accessing the FIFO and back to BE mode + * after the access is done + */ + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) || !is_param) { + if (prepare) + val |= IPROC_NAND_APB_LE_MODE; + else + val &= ~IPROC_NAND_APB_LE_MODE; + } else { /* when in LE accessing the parameter page, keep APB in BE */ val &= ~IPROC_NAND_APB_LE_MODE; + } brcmnand_writel(val, mmio); -- cgit v1.2.3-59-g8ed1b From e13f880b901ed4891ab1f07aa38c2932c566f525 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Wed, 24 Aug 2016 12:25:00 +0300 Subject: mtd: nand: Allow MTD_NAND_OMAP2 to be usable on Keystone devices Some Keystone devices (e.g. K2G) include a OMAP NAND IP. Allow the NAND driver to be usable for both Keystone and OMAP devices. Signed-off-by: Roger Quadros Signed-off-by: Boris Brezillon --- drivers/mtd/nand/Kconfig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index b2ba9ef553bd..7b7a887b4709 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -88,11 +88,11 @@ config MTD_NAND_AMS_DELTA Support for NAND flash on Amstrad E3 (Delta). config MTD_NAND_OMAP2 - tristate "NAND Flash device on OMAP2, OMAP3 and OMAP4" - depends on ARCH_OMAP2PLUS + tristate "NAND Flash device on OMAP2, OMAP3, OMAP4 and Keystone" + depends on (ARCH_OMAP2PLUS || ARCH_KEYSTONE) help - Support for NAND flash on Texas Instruments OMAP2, OMAP3 and OMAP4 - platforms. + Support for NAND flash on Texas Instruments OMAP2, OMAP3, OMAP4 + and Keystone platforms. config MTD_NAND_OMAP_BCH depends on MTD_NAND_OMAP2 -- cgit v1.2.3-59-g8ed1b From 2f94abfe35b210e7711af9202a3dcfc9e779219a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:45 +0200 Subject: mtd: nand: Create a NAND reset function When NAND devices are resetted some initialization may have to be done, like for example they have to be configured for the timing mode that shall be used. To get a common place where this initialization can be implemented create a nand_reset() function. This currently only issues a NAND_CMD_RESET to the NAND device. The places issuing this command manually are replaced with a call to nand_reset(). Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 25 ++++++++++++++++++++----- include/linux/mtd/nand.h | 4 ++++ 2 files changed, 24 insertions(+), 5 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 108adefcc8cc..1a6310573ab4 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -947,6 +947,21 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) return status; } +/** + * nand_reset - Reset and initialize a NAND device + * @chip: The NAND chip + * + * Returns 0 for success or negative error code otherwise + */ +int nand_reset(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + return 0; +} + /** * __nand_unlock - [REPLACEABLE] unlocks specified locked blocks * @mtd: mtd info @@ -1025,7 +1040,7 @@ int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) * some operation can also clear the bit 7 of status register * eg. erase/program a locked block */ - chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + nand_reset(chip); /* Check, if it is write protected */ if (nand_check_wp(mtd)) { @@ -1084,7 +1099,7 @@ int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) * some operation can also clear the bit 7 of status register * eg. erase/program a locked block */ - chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + nand_reset(chip); /* Check, if it is write protected */ if (nand_check_wp(mtd)) { @@ -2782,7 +2797,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, * if we don't do this. I have no clue why, but I seem to have 'fixed' * it in the doc2000 driver in August 1999. dwmw2. */ - chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + nand_reset(chip); /* Check, if it is write protected */ if (nand_check_wp(mtd)) { @@ -3822,7 +3837,7 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx) * after power-up. */ - chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + nand_reset(chip); /* Send the command for reading device ID */ chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); @@ -4163,7 +4178,7 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, for (i = 1; i < maxchips; i++) { chip->select_chip(mtd, i); /* See comment in nand_get_flash_type for reset */ - chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + nand_reset(chip); /* Send the command for reading device ID */ chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* Read manufacturer and device IDs */ diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 28c1833ad708..73ccbf6e057c 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -1100,4 +1100,8 @@ int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page); /* Default read_oob syndrome implementation */ int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, int page); + +/* Reset and initialize a NAND device */ +int nand_reset(struct nand_chip *chip); + #endif /* __LINUX_MTD_NAND_H */ -- cgit v1.2.3-59-g8ed1b From b1dd3ca203fccd111926c3f6ac59bf903ec62b05 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:47 +0200 Subject: mtd: nand: convert ONFI mode into data interface struct nand_data_interface is the designated type to pass to the NAND drivers to configure the timing. To simplify further patches convert the onfi_sdr_timings array from type struct nand_sdr_timings nand_data_interface. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_timings.c | 430 +++++++++++++++++++++------------------- 1 file changed, 224 insertions(+), 206 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c index 30119c588e0f..878fe0d5342c 100644 --- a/drivers/mtd/nand/nand_timings.c +++ b/drivers/mtd/nand/nand_timings.c @@ -13,228 +13,246 @@ #include #include -static const struct nand_sdr_timings onfi_sdr_timings[] = { +static const struct nand_data_interface onfi_sdr_timings[] = { /* Mode 0 */ { - .tADL_min = 400000, - .tALH_min = 20000, - .tALS_min = 50000, - .tAR_min = 25000, - .tCEA_max = 100000, - .tCEH_min = 20000, - .tCH_min = 20000, - .tCHZ_max = 100000, - .tCLH_min = 20000, - .tCLR_min = 20000, - .tCLS_min = 50000, - .tCOH_min = 0, - .tCS_min = 70000, - .tDH_min = 20000, - .tDS_min = 40000, - .tFEAT_max = 1000000, - .tIR_min = 10000, - .tITC_max = 1000000, - .tRC_min = 100000, - .tREA_max = 40000, - .tREH_min = 30000, - .tRHOH_min = 0, - .tRHW_min = 200000, - .tRHZ_max = 200000, - .tRLOH_min = 0, - .tRP_min = 50000, - .tRR_min = 40000, - .tRST_max = 250000000000ULL, - .tWB_max = 200000, - .tWC_min = 100000, - .tWH_min = 30000, - .tWHR_min = 120000, - .tWP_min = 50000, - .tWW_min = 100000, + .type = NAND_SDR_IFACE, + .timings.sdr = { + .tADL_min = 400000, + .tALH_min = 20000, + .tALS_min = 50000, + .tAR_min = 25000, + .tCEA_max = 100000, + .tCEH_min = 20000, + .tCH_min = 20000, + .tCHZ_max = 100000, + .tCLH_min = 20000, + .tCLR_min = 20000, + .tCLS_min = 50000, + .tCOH_min = 0, + .tCS_min = 70000, + .tDH_min = 20000, + .tDS_min = 40000, + .tFEAT_max = 1000000, + .tIR_min = 10000, + .tITC_max = 1000000, + .tRC_min = 100000, + .tREA_max = 40000, + .tREH_min = 30000, + .tRHOH_min = 0, + .tRHW_min = 200000, + .tRHZ_max = 200000, + .tRLOH_min = 0, + .tRP_min = 50000, + .tRR_min = 40000, + .tRST_max = 250000000000ULL, + .tWB_max = 200000, + .tWC_min = 100000, + .tWH_min = 30000, + .tWHR_min = 120000, + .tWP_min = 50000, + .tWW_min = 100000, + }, }, /* Mode 1 */ { - .tADL_min = 400000, - .tALH_min = 10000, - .tALS_min = 25000, - .tAR_min = 10000, - .tCEA_max = 45000, - .tCEH_min = 20000, - .tCH_min = 10000, - .tCHZ_max = 50000, - .tCLH_min = 10000, - .tCLR_min = 10000, - .tCLS_min = 25000, - .tCOH_min = 15000, - .tCS_min = 35000, - .tDH_min = 10000, - .tDS_min = 20000, - .tFEAT_max = 1000000, - .tIR_min = 0, - .tITC_max = 1000000, - .tRC_min = 50000, - .tREA_max = 30000, - .tREH_min = 15000, - .tRHOH_min = 15000, - .tRHW_min = 100000, - .tRHZ_max = 100000, - .tRLOH_min = 0, - .tRP_min = 25000, - .tRR_min = 20000, - .tRST_max = 500000000, - .tWB_max = 100000, - .tWC_min = 45000, - .tWH_min = 15000, - .tWHR_min = 80000, - .tWP_min = 25000, - .tWW_min = 100000, + .type = NAND_SDR_IFACE, + .timings.sdr = { + .tADL_min = 400000, + .tALH_min = 10000, + .tALS_min = 25000, + .tAR_min = 10000, + .tCEA_max = 45000, + .tCEH_min = 20000, + .tCH_min = 10000, + .tCHZ_max = 50000, + .tCLH_min = 10000, + .tCLR_min = 10000, + .tCLS_min = 25000, + .tCOH_min = 15000, + .tCS_min = 35000, + .tDH_min = 10000, + .tDS_min = 20000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 50000, + .tREA_max = 30000, + .tREH_min = 15000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 0, + .tRP_min = 25000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 45000, + .tWH_min = 15000, + .tWHR_min = 80000, + .tWP_min = 25000, + .tWW_min = 100000, + }, }, /* Mode 2 */ { - .tADL_min = 400000, - .tALH_min = 10000, - .tALS_min = 15000, - .tAR_min = 10000, - .tCEA_max = 30000, - .tCEH_min = 20000, - .tCH_min = 10000, - .tCHZ_max = 50000, - .tCLH_min = 10000, - .tCLR_min = 10000, - .tCLS_min = 15000, - .tCOH_min = 15000, - .tCS_min = 25000, - .tDH_min = 5000, - .tDS_min = 15000, - .tFEAT_max = 1000000, - .tIR_min = 0, - .tITC_max = 1000000, - .tRC_min = 35000, - .tREA_max = 25000, - .tREH_min = 15000, - .tRHOH_min = 15000, - .tRHW_min = 100000, - .tRHZ_max = 100000, - .tRLOH_min = 0, - .tRR_min = 20000, - .tRST_max = 500000000, - .tWB_max = 100000, - .tRP_min = 17000, - .tWC_min = 35000, - .tWH_min = 15000, - .tWHR_min = 80000, - .tWP_min = 17000, - .tWW_min = 100000, + .type = NAND_SDR_IFACE, + .timings.sdr = { + .tADL_min = 400000, + .tALH_min = 10000, + .tALS_min = 15000, + .tAR_min = 10000, + .tCEA_max = 30000, + .tCEH_min = 20000, + .tCH_min = 10000, + .tCHZ_max = 50000, + .tCLH_min = 10000, + .tCLR_min = 10000, + .tCLS_min = 15000, + .tCOH_min = 15000, + .tCS_min = 25000, + .tDH_min = 5000, + .tDS_min = 15000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 35000, + .tREA_max = 25000, + .tREH_min = 15000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 0, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tRP_min = 17000, + .tWC_min = 35000, + .tWH_min = 15000, + .tWHR_min = 80000, + .tWP_min = 17000, + .tWW_min = 100000, + }, }, /* Mode 3 */ { - .tADL_min = 400000, - .tALH_min = 5000, - .tALS_min = 10000, - .tAR_min = 10000, - .tCEA_max = 25000, - .tCEH_min = 20000, - .tCH_min = 5000, - .tCHZ_max = 50000, - .tCLH_min = 5000, - .tCLR_min = 10000, - .tCLS_min = 10000, - .tCOH_min = 15000, - .tCS_min = 25000, - .tDH_min = 5000, - .tDS_min = 10000, - .tFEAT_max = 1000000, - .tIR_min = 0, - .tITC_max = 1000000, - .tRC_min = 30000, - .tREA_max = 20000, - .tREH_min = 10000, - .tRHOH_min = 15000, - .tRHW_min = 100000, - .tRHZ_max = 100000, - .tRLOH_min = 0, - .tRP_min = 15000, - .tRR_min = 20000, - .tRST_max = 500000000, - .tWB_max = 100000, - .tWC_min = 30000, - .tWH_min = 10000, - .tWHR_min = 80000, - .tWP_min = 15000, - .tWW_min = 100000, + .type = NAND_SDR_IFACE, + .timings.sdr = { + .tADL_min = 400000, + .tALH_min = 5000, + .tALS_min = 10000, + .tAR_min = 10000, + .tCEA_max = 25000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCHZ_max = 50000, + .tCLH_min = 5000, + .tCLR_min = 10000, + .tCLS_min = 10000, + .tCOH_min = 15000, + .tCS_min = 25000, + .tDH_min = 5000, + .tDS_min = 10000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 30000, + .tREA_max = 20000, + .tREH_min = 10000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 0, + .tRP_min = 15000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 30000, + .tWH_min = 10000, + .tWHR_min = 80000, + .tWP_min = 15000, + .tWW_min = 100000, + }, }, /* Mode 4 */ { - .tADL_min = 400000, - .tALH_min = 5000, - .tALS_min = 10000, - .tAR_min = 10000, - .tCEA_max = 25000, - .tCEH_min = 20000, - .tCH_min = 5000, - .tCHZ_max = 30000, - .tCLH_min = 5000, - .tCLR_min = 10000, - .tCLS_min = 10000, - .tCOH_min = 15000, - .tCS_min = 20000, - .tDH_min = 5000, - .tDS_min = 10000, - .tFEAT_max = 1000000, - .tIR_min = 0, - .tITC_max = 1000000, - .tRC_min = 25000, - .tREA_max = 20000, - .tREH_min = 10000, - .tRHOH_min = 15000, - .tRHW_min = 100000, - .tRHZ_max = 100000, - .tRLOH_min = 5000, - .tRP_min = 12000, - .tRR_min = 20000, - .tRST_max = 500000000, - .tWB_max = 100000, - .tWC_min = 25000, - .tWH_min = 10000, - .tWHR_min = 80000, - .tWP_min = 12000, - .tWW_min = 100000, + .type = NAND_SDR_IFACE, + .timings.sdr = { + .tADL_min = 400000, + .tALH_min = 5000, + .tALS_min = 10000, + .tAR_min = 10000, + .tCEA_max = 25000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCHZ_max = 30000, + .tCLH_min = 5000, + .tCLR_min = 10000, + .tCLS_min = 10000, + .tCOH_min = 15000, + .tCS_min = 20000, + .tDH_min = 5000, + .tDS_min = 10000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 25000, + .tREA_max = 20000, + .tREH_min = 10000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 5000, + .tRP_min = 12000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 25000, + .tWH_min = 10000, + .tWHR_min = 80000, + .tWP_min = 12000, + .tWW_min = 100000, + }, }, /* Mode 5 */ { - .tADL_min = 400000, - .tALH_min = 5000, - .tALS_min = 10000, - .tAR_min = 10000, - .tCEA_max = 25000, - .tCEH_min = 20000, - .tCH_min = 5000, - .tCHZ_max = 30000, - .tCLH_min = 5000, - .tCLR_min = 10000, - .tCLS_min = 10000, - .tCOH_min = 15000, - .tCS_min = 15000, - .tDH_min = 5000, - .tDS_min = 7000, - .tFEAT_max = 1000000, - .tIR_min = 0, - .tITC_max = 1000000, - .tRC_min = 20000, - .tREA_max = 16000, - .tREH_min = 7000, - .tRHOH_min = 15000, - .tRHW_min = 100000, - .tRHZ_max = 100000, - .tRLOH_min = 5000, - .tRP_min = 10000, - .tRR_min = 20000, - .tRST_max = 500000000, - .tWB_max = 100000, - .tWC_min = 20000, - .tWH_min = 7000, - .tWHR_min = 80000, - .tWP_min = 10000, - .tWW_min = 100000, + .type = NAND_SDR_IFACE, + .timings.sdr = { + .tADL_min = 400000, + .tALH_min = 5000, + .tALS_min = 10000, + .tAR_min = 10000, + .tCEA_max = 25000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCHZ_max = 30000, + .tCLH_min = 5000, + .tCLR_min = 10000, + .tCLS_min = 10000, + .tCOH_min = 15000, + .tCS_min = 15000, + .tDH_min = 5000, + .tDS_min = 7000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 20000, + .tREA_max = 16000, + .tREH_min = 7000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 5000, + .tRP_min = 10000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 20000, + .tWH_min = 7000, + .tWHR_min = 80000, + .tWP_min = 10000, + .tWW_min = 100000, + }, }, }; @@ -248,6 +266,6 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode) if (mode < 0 || mode >= ARRAY_SIZE(onfi_sdr_timings)) return ERR_PTR(-EINVAL); - return &onfi_sdr_timings[mode]; + return &onfi_sdr_timings[mode].timings.sdr; } EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings); -- cgit v1.2.3-59-g8ed1b From b88730ada99bfe243862add360720a3550b0edbf Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:48 +0200 Subject: mtd: nand: Add function to convert ONFI mode to data_interface onfi_init_data_interface() initializes a data interface with values from a given ONFI mode. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_timings.c | 29 +++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 5 +++++ 2 files changed, 34 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c index 878fe0d5342c..7441d68b78d5 100644 --- a/drivers/mtd/nand/nand_timings.c +++ b/drivers/mtd/nand/nand_timings.c @@ -269,3 +269,32 @@ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode) return &onfi_sdr_timings[mode].timings.sdr; } EXPORT_SYMBOL(onfi_async_timing_mode_to_sdr_timings); + +/** + * onfi_init_data_interface - [NAND Interface] Initialize a data interface from + * given ONFI mode + * @iface: The data interface to be initialized + * @mode: The ONFI timing mode + */ +int onfi_init_data_interface(struct nand_chip *chip, + struct nand_data_interface *iface, + enum nand_data_interface_type type, + int timing_mode) +{ + if (type != NAND_SDR_IFACE) + return -EINVAL; + + if (timing_mode < 0 || timing_mode >= ARRAY_SIZE(onfi_sdr_timings)) + return -EINVAL; + + *iface = onfi_sdr_timings[timing_mode]; + + /* + * TODO: initialize timings that cannot be deduced from timing mode: + * tR, tPROG, tCCS, ... + * These information are part of the ONFI parameter page. + */ + + return 0; +} +EXPORT_SYMBOL(onfi_init_data_interface); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index a625e960c0c3..1f34c04fe16c 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -1112,6 +1112,11 @@ static inline int onfi_get_sync_timing_mode(struct nand_chip *chip) return le16_to_cpu(chip->onfi_params.src_sync_timing_mode); } +int onfi_init_data_interface(struct nand_chip *chip, + struct nand_data_interface *iface, + enum nand_data_interface_type type, + int timing_mode); + /* * Check if it is a SLC nand. * The !nand_is_slc() can be used to check the MLC/TLC nand chips. -- cgit v1.2.3-59-g8ed1b From 6e1f9708dbf3c50a8da93c1952a01a7a2acb5e66 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:49 +0200 Subject: mtd: nand: Expose data interface for ONFI mode 0 The nand layer will need ONFI mode 0 to use it as timing mode before and right after reset. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_timings.c | 11 +++++++++++ include/linux/mtd/nand.h | 2 ++ 2 files changed, 13 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/nand_timings.c index 7441d68b78d5..13a587407be3 100644 --- a/drivers/mtd/nand/nand_timings.c +++ b/drivers/mtd/nand/nand_timings.c @@ -298,3 +298,14 @@ int onfi_init_data_interface(struct nand_chip *chip, return 0; } EXPORT_SYMBOL(onfi_init_data_interface); + +/** + * nand_get_default_data_interface - [NAND Interface] Retrieve NAND + * data interface for mode 0. This is used as default timing after + * reset. + */ +const struct nand_data_interface *nand_get_default_data_interface(void) +{ + return &onfi_sdr_timings[0]; +} +EXPORT_SYMBOL(nand_get_default_data_interface); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 1f34c04fe16c..0c9412c2d80b 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -1154,6 +1154,8 @@ static inline int jedec_feature(struct nand_chip *chip) /* get timing characteristics from ONFI timing mode. */ const struct nand_sdr_timings *onfi_async_timing_mode_to_sdr_timings(int mode); +/* get data interface from ONFI timing mode 0, used after reset. */ +const struct nand_data_interface *nand_get_default_data_interface(void); int nand_check_erased_ecc_chunk(void *data, int datalen, void *ecc, int ecclen, -- cgit v1.2.3-59-g8ed1b From d8e725dd831186a3595036b2b1df9f68cbc6efa3 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 15 Sep 2016 10:32:50 +0200 Subject: mtd: nand: automate NAND timings selection The NAND framework provides several helpers to query timing modes supported by a NAND chip, but this implies that all NAND controller drivers have to implement the same timings selection dance. Also currently NAND devices can be resetted at arbitrary places which also resets the timing for ONFI chips to timing mode 0. Provide a common logic to select the best timings based on ONFI or ->onfi_timing_mode_default information. Hook this into nand_reset() to make sure the new timing is applied each time during a reset. NAND controller willing to support timings adjustment should just implement the ->setup_data_interface() method. Signed-off-by: Boris Brezillon Signed-off-by: Sascha Hauer --- drivers/mtd/nand/nand_base.c | 157 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 14 ++-- 2 files changed, 167 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 1a6310573ab4..f39775b05779 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -947,6 +947,148 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) return status; } +/** + * nand_reset_data_interface - Reset data interface and timings + * @chip: The NAND chip + * + * 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) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_data_interface *conf; + int ret; + + if (!chip->setup_data_interface) + return 0; + + /* + * The ONFI specification says: + * " + * To transition from NV-DDR or NV-DDR2 to the SDR data + * interface, the host shall use the Reset (FFh) command + * using SDR timing mode 0. A device in any timing mode is + * required to recognize Reset (FFh) command issued in SDR + * timing mode 0. + * " + * + * Configure the data interface in SDR mode and set the + * timings to timing mode 0. + */ + + conf = nand_get_default_data_interface(); + ret = chip->setup_data_interface(mtd, conf, false); + if (ret) + pr_err("Failed to configure data interface to SDR timing mode 0\n"); + + return ret; +} + +/** + * nand_setup_data_interface - Setup the best data interface and timings + * @chip: The NAND chip + * + * Find and configure the best data interface and NAND timings supported by + * the chip and the driver. + * First tries to retrieve supported timing modes from ONFI information, + * and if the NAND chip does not support ONFI, relies on the + * ->onfi_timing_mode_default specified in the nand_ids table. + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_setup_data_interface(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + if (!chip->setup_data_interface || !chip->data_interface) + return 0; + + /* + * Ensure the timing mode has been changed on the chip side + * before changing timings on the controller side. + */ + if (chip->onfi_version) { + u8 tmode_param[ONFI_SUBFEATURE_PARAM_LEN] = { + chip->onfi_timing_mode_default, + }; + + ret = chip->onfi_set_features(mtd, chip, + ONFI_FEATURE_ADDR_TIMING_MODE, + tmode_param); + if (ret) + goto err; + } + + ret = chip->setup_data_interface(mtd, chip->data_interface, false); +err: + return ret; +} + +/** + * nand_init_data_interface - find the best data interface and timings + * @chip: The NAND chip + * + * Find the best data interface and NAND timings supported by the chip + * and the driver. + * First tries to retrieve supported timing modes from ONFI information, + * and if the NAND chip does not support ONFI, relies on the + * ->onfi_timing_mode_default specified in the nand_ids table. After this + * function nand_chip->data_interface is initialized with the best timing mode + * available. + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_init_data_interface(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int modes, mode, ret; + + if (!chip->setup_data_interface) + return 0; + + /* + * First try to identify the best timings from ONFI parameters and + * if the NAND does not support ONFI, fallback to the default ONFI + * timing mode. + */ + modes = onfi_get_async_timing_mode(chip); + if (modes == ONFI_TIMING_MODE_UNKNOWN) { + if (!chip->onfi_timing_mode_default) + return 0; + + modes = GENMASK(chip->onfi_timing_mode_default, 0); + } + + chip->data_interface = kzalloc(sizeof(*chip->data_interface), + GFP_KERNEL); + if (!chip->data_interface) + return -ENOMEM; + + for (mode = fls(modes) - 1; mode >= 0; mode--) { + ret = onfi_init_data_interface(chip, chip->data_interface, + NAND_SDR_IFACE, mode); + if (ret) + continue; + + ret = chip->setup_data_interface(mtd, chip->data_interface, + true); + if (!ret) { + chip->onfi_timing_mode_default = mode; + break; + } + } + + return 0; +} + +static void nand_release_data_interface(struct nand_chip *chip) +{ + kfree(chip->data_interface); +} + /** * nand_reset - Reset and initialize a NAND device * @chip: The NAND chip @@ -956,9 +1098,18 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) int nand_reset(struct nand_chip *chip) { struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nand_reset_data_interface(chip); + if (ret) + return ret; chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + ret = nand_setup_data_interface(chip); + if (ret) + return ret; + return 0; } @@ -4172,6 +4323,10 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, return PTR_ERR(type); } + ret = nand_init_data_interface(chip); + if (ret) + return ret; + chip->select_chip(mtd, -1); /* Check for a chip array */ @@ -4631,6 +4786,8 @@ void nand_release(struct mtd_info *mtd) mtd_device_unregister(mtd); + nand_release_data_interface(chip); + /* Free bad block table memory */ kfree(chip->bbt); if (!(chip->options & NAND_OWN_BUFFERS)) diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 0c9412c2d80b..d3e3f8d03336 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -751,10 +751,9 @@ nand_get_sdr_timings(const struct nand_data_interface *conf) * also from the datasheet. It is the recommended ECC step * size, if known; if unknown, set to zero. * @onfi_timing_mode_default: [INTERN] default ONFI timing mode. This field is - * either deduced from the datasheet if the NAND - * chip is not ONFI compliant or set to 0 if it is - * (an ONFI chip is always configured in mode 0 - * after a NAND reset) + * set to the actually used ONFI mode if the chip is + * ONFI compliant or deduced from the datasheet if + * the NAND chip is not ONFI compliant. * @numchips: [INTERN] number of physical chips * @chipsize: [INTERN] the size of one chip for multichip arrays * @pagemask: [INTERN] page number mask = number of (pages / chip) - 1 @@ -774,6 +773,7 @@ nand_get_sdr_timings(const struct nand_data_interface *conf) * @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 * @bbt: [INTERN] bad block table pointer * @bbt_td: [REPLACEABLE] bad block table descriptor for flash * lookup. @@ -820,6 +820,10 @@ 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 chip_delay; unsigned int options; @@ -849,6 +853,8 @@ struct nand_chip { struct nand_jedec_params jedec_params; }; + struct nand_data_interface *data_interface; + int read_retries; flstate_t state; -- cgit v1.2.3-59-g8ed1b From 907f45fb9951b0440d832098baeae9162604eb98 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:51 +0200 Subject: mtd: nand: sunxi: switch from manual to automated timing config The NAND framework is now able to select the best NAND timings for us. All we have to do is implement a ->setup_data_interface() function to apply those timings and remove the timing selection code from the sunxi driver. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/sunxi_nand.c | 76 ++++++++----------------------------------- 1 file changed, 14 insertions(+), 62 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c index 8b5dadc9cd26..2032244138c1 100644 --- a/drivers/mtd/nand/sunxi_nand.c +++ b/drivers/mtd/nand/sunxi_nand.c @@ -1572,14 +1572,22 @@ 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_nand_chip_set_timings(struct sunxi_nand_chip *chip, - const struct nand_sdr_timings *timings) +static int sunxi_nfc_setup_data_interface(struct mtd_info *mtd, + const struct nand_data_interface *conf, + bool check_only) { + struct nand_chip *nand = mtd_to_nand(mtd); + struct sunxi_nand_chip *chip = to_sunxi_nand(nand); struct sunxi_nfc *nfc = to_sunxi_nfc(chip->nand.controller); + const struct nand_sdr_timings *timings; u32 min_clk_period = 0; s32 tWB, tADL, tWHR, tRHW, tCAD; long real_clk_rate; + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + /* T1 <=> tCLS */ if (timings->tCLS_min > min_clk_period) min_clk_period = timings->tCLS_min; @@ -1679,6 +1687,9 @@ static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip, return tRHW; } + if (check_only) + return 0; + /* * TODO: according to ONFI specs this value only applies for DDR NAND, * but Allwinner seems to set this to 0x7. Mimic them for now. @@ -1712,44 +1723,6 @@ static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip, return 0; } -static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip, - struct device_node *np) -{ - struct mtd_info *mtd = nand_to_mtd(&chip->nand); - const struct nand_sdr_timings *timings; - int ret; - int mode; - - mode = onfi_get_async_timing_mode(&chip->nand); - if (mode == ONFI_TIMING_MODE_UNKNOWN) { - mode = chip->nand.onfi_timing_mode_default; - } else { - uint8_t feature[ONFI_SUBFEATURE_PARAM_LEN] = {}; - int i; - - mode = fls(mode) - 1; - if (mode < 0) - mode = 0; - - feature[0] = mode; - for (i = 0; i < chip->nsels; i++) { - chip->nand.select_chip(mtd, i); - ret = chip->nand.onfi_set_features(mtd, &chip->nand, - ONFI_FEATURE_ADDR_TIMING_MODE, - feature); - chip->nand.select_chip(mtd, -1); - if (ret) - return ret; - } - } - - timings = onfi_async_timing_mode_to_sdr_timings(mode); - if (IS_ERR(timings)) - return PTR_ERR(timings); - - return sunxi_nand_chip_set_timings(chip, timings); -} - static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion) { @@ -1975,7 +1948,6 @@ static int sunxi_nand_ecc_init(struct mtd_info *mtd, struct nand_ecc_ctrl *ecc, static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, struct device_node *np) { - const struct nand_sdr_timings *timings; struct sunxi_nand_chip *chip; struct mtd_info *mtd; struct nand_chip *nand; @@ -2065,25 +2037,11 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, nand->read_buf = sunxi_nfc_read_buf; nand->write_buf = sunxi_nfc_write_buf; nand->read_byte = sunxi_nfc_read_byte; + nand->setup_data_interface = sunxi_nfc_setup_data_interface; mtd = nand_to_mtd(nand); mtd->dev.parent = dev; - timings = onfi_async_timing_mode_to_sdr_timings(0); - if (IS_ERR(timings)) { - ret = PTR_ERR(timings); - dev_err(dev, - "could not retrieve timings for ONFI mode 0: %d\n", - ret); - return ret; - } - - ret = sunxi_nand_chip_set_timings(chip, timings); - if (ret) { - dev_err(dev, "could not configure chip timings: %d\n", ret); - return ret; - } - ret = nand_scan_ident(mtd, nsels, NULL); if (ret) return ret; @@ -2096,12 +2054,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, nand->options |= NAND_SUBPAGE_READ; - ret = sunxi_nand_chip_init_timings(chip, np); - if (ret) { - dev_err(dev, "could not configure chip timings: %d\n", ret); - return ret; - } - ret = sunxi_nand_ecc_init(mtd, &nand->ecc, np); if (ret) { dev_err(dev, "ECC init failed: %d\n", ret); -- cgit v1.2.3-59-g8ed1b From 4123ea34aac754cdbd0a5e482c29be433c311d67 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:52 +0200 Subject: mtd: nand: mxc: implement onfi get/set features To be able to support different ONFI timing modes we have to implement the onfi_set_features and onfi_get_features. Tested on an i.MX25 SoC. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/mxc_nand.c | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 5173fadc9a4e..8bd8108066ea 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -1239,6 +1239,59 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command, } } +static int mxc_nand_onfi_set_features(struct mtd_info *mtd, + struct nand_chip *chip, int addr, + u8 *subfeature_param) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + int i; + + if (!chip->onfi_version || + !(le16_to_cpu(chip->onfi_params.opt_cmd) + & ONFI_OPT_CMD_SET_GET_FEATURES)) + return -EINVAL; + + host->buf_start = 0; + + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + chip->write_byte(mtd, subfeature_param[i]); + + memcpy32_toio(host->main_area0, host->data_buf, mtd->writesize); + host->devtype_data->send_cmd(host, NAND_CMD_SET_FEATURES, false); + mxc_do_addr_cycle(mtd, addr, -1); + host->devtype_data->send_page(mtd, NFC_INPUT); + + return 0; +} + +static int mxc_nand_onfi_get_features(struct mtd_info *mtd, + struct nand_chip *chip, int addr, + u8 *subfeature_param) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + int i; + + if (!chip->onfi_version || + !(le16_to_cpu(chip->onfi_params.opt_cmd) + & ONFI_OPT_CMD_SET_GET_FEATURES)) + return -EINVAL; + + *(uint32_t *)host->main_area0 = 0xdeadbeef; + + host->devtype_data->send_cmd(host, NAND_CMD_GET_FEATURES, false); + mxc_do_addr_cycle(mtd, addr, -1); + host->devtype_data->send_page(mtd, NFC_OUTPUT); + memcpy32_fromio(host->data_buf, host->main_area0, 512); + host->buf_start = 0; + + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + *subfeature_param++ = chip->read_byte(mtd); + + return 0; +} + /* * The generic flash bbt decriptors overlap with our ecc * hardware, so define some i.MX specific ones. @@ -1513,6 +1566,8 @@ static int mxcnd_probe(struct platform_device *pdev) this->read_word = mxc_nand_read_word; this->write_buf = mxc_nand_write_buf; this->read_buf = mxc_nand_read_buf; + this->onfi_set_features = mxc_nand_onfi_set_features; + this->onfi_get_features = mxc_nand_onfi_get_features; host->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(host->clk)) -- cgit v1.2.3-59-g8ed1b From 8283079696aba905367297cf80287980eb34c14c Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Sep 2016 10:32:53 +0200 Subject: mtd: nand: mxc: Add timing setup for v2 controllers So far we relied on reset default or the bootloader to configure a suitable clk rate for the Nand controller. This works but we can optimize the timing for better performance. This sets the clk rate for v2 controllers (i.MX25/35) based on the timing mode read from the ONFI parameter page. This may also enable the symmetric mode (aks EDO mode) if necessary which reads one word per clock cycle. Tested on an i.MX25 with a Micron MT29F4G08ABBDAHC attached. Signed-off-by: Sascha Hauer Signed-off-by: Boris Brezillon --- drivers/mtd/nand/mxc_nand.c | 84 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 8bd8108066ea..75c0c0eb52ae 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -152,6 +152,9 @@ 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); /* * On i.MX21 the CONFIG2:INT bit cannot be read if interrupts are masked @@ -1012,6 +1015,82 @@ 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) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + int tRC_min_ns, tRC_ps, ret; + unsigned long rate, rate_round; + const struct nand_sdr_timings *timings; + u16 config1; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + + config1 = readw(NFC_V1_V2_CONFIG1); + + tRC_min_ns = timings->tRC_min / 1000; + rate = 1000000000 / tRC_min_ns; + + /* + * For tRC < 30ns we have to use EDO mode. In this case the controller + * does one access per clock cycle. Otherwise the controller does one + * access in two clock cycles, thus we have to double the rate to the + * controller. + */ + if (tRC_min_ns < 30) { + rate_round = clk_round_rate(host->clk, rate); + config1 |= NFC_V2_CONFIG1_ONE_CYCLE; + tRC_ps = 1000000000 / (rate_round / 1000); + } else { + rate *= 2; + rate_round = clk_round_rate(host->clk, rate); + config1 &= ~NFC_V2_CONFIG1_ONE_CYCLE; + tRC_ps = 1000000000 / (rate_round / 1000 / 2); + } + + /* + * The timing values compared against are from the i.MX25 Automotive + * datasheet, Table 50. NFC Timing Parameters + */ + if (timings->tCLS_min > tRC_ps - 1000 || + timings->tCLH_min > tRC_ps - 2000 || + timings->tCS_min > tRC_ps - 1000 || + timings->tCH_min > tRC_ps - 2000 || + timings->tWP_min > tRC_ps - 1500 || + timings->tALS_min > tRC_ps || + timings->tALH_min > tRC_ps - 3000 || + timings->tDS_min > tRC_ps || + timings->tDH_min > tRC_ps - 5000 || + timings->tWC_min > 2 * tRC_ps || + timings->tWH_min > tRC_ps - 2500 || + timings->tRR_min > 6 * tRC_ps || + timings->tRP_min > 3 * tRC_ps / 2 || + timings->tRC_min > 2 * tRC_ps || + timings->tREH_min > (tRC_ps / 2) - 2500) { + dev_dbg(host->dev, "Timing out of bounds\n"); + return -EINVAL; + } + + if (check_only) + return 0; + + ret = clk_set_rate(host->clk, rate); + if (ret) + return ret; + + writew(config1, NFC_V1_V2_CONFIG1); + + dev_dbg(host->dev, "Setting rate to %ldHz, %s mode\n", rate_round, + config1 & NFC_V2_CONFIG1_ONE_CYCLE ? "One cycle (EDO)" : + "normal"); + + return 0; +} + static void preset_v2(struct mtd_info *mtd) { struct nand_chip *nand_chip = mtd_to_nand(mtd); @@ -1278,8 +1357,6 @@ static int mxc_nand_onfi_get_features(struct mtd_info *mtd, & ONFI_OPT_CMD_SET_GET_FEATURES)) return -EINVAL; - *(uint32_t *)host->main_area0 = 0xdeadbeef; - host->devtype_data->send_cmd(host, NAND_CMD_GET_FEATURES, false); mxc_do_addr_cycle(mtd, addr, -1); host->devtype_data->send_page(mtd, NFC_OUTPUT); @@ -1380,6 +1457,7 @@ static const struct mxc_nand_devtype_data imx25_nand_devtype_data = { .ooblayout = &mxc_v2_ooblayout_ops, .select_chip = mxc_nand_select_chip_v2, .correct_data = mxc_nand_correct_data_v2_v3, + .setup_data_interface = mxc_nand_v2_setup_data_interface, .irqpending_quirk = 0, .needs_ip = 0, .regs_offset = 0x1e00, @@ -1588,6 +1666,8 @@ static int mxcnd_probe(struct platform_device *pdev) if (err < 0) return err; + this->setup_data_interface = host->devtype_data->setup_data_interface; + if (host->devtype_data->needs_ip) { res = platform_get_resource(pdev, IORESOURCE_MEM, 0); host->regs_ip = devm_ioremap_resource(&pdev->dev, res); -- cgit v1.2.3-59-g8ed1b From ba78ee00e1ff84de9b3ad33edbd3ec599099ee82 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Wed, 8 Jun 2016 17:04:22 +0200 Subject: mtd: nand: Add an option to maximize the ECC strength The generic NAND DT bindings allows one to tweak the ECC strength and step size to their need. It can be used to lower the ECC strength to match a bootloader/firmware config, but might also be used to get a better reliability. In the latter case, the user might want to use the maximum ECC strength without having to explicitly calculate the exact value (this value not only depends on the OOB size, but also on the NAND controller, and can be tricky to extract). Add a generic 'nand-ecc-maximize' DT property and the associated NAND_ECC_MAXIMIZE flag, to let ECC controller drivers select the best ECC strength and step-size on their own. Signed-off-by: Boris Brezillon Acked-by: Rob Herring --- Documentation/devicetree/bindings/mtd/nand.txt | 9 +++++++++ drivers/mtd/nand/nand_base.c | 3 +++ include/linux/mtd/nand.h | 1 + 3 files changed, 13 insertions(+) (limited to 'drivers/mtd') diff --git a/Documentation/devicetree/bindings/mtd/nand.txt b/Documentation/devicetree/bindings/mtd/nand.txt index 3733300de8dd..b05601600083 100644 --- a/Documentation/devicetree/bindings/mtd/nand.txt +++ b/Documentation/devicetree/bindings/mtd/nand.txt @@ -35,6 +35,15 @@ Optional NAND chip properties: - nand-ecc-step-size: integer representing the number of data bytes that are covered by a single ECC step. +- nand-ecc-maximize: boolean used to specify that you want to maximize ECC + strength. The maximum ECC strength is both controller and + chip dependent. The controller side has to select the ECC + config providing the best strength and taking the OOB area + size constraint into account. + This is particularly useful when only the in-band area is + used by the upper layers, and you want to make your NAND + as reliable as possible. + The ECC strength and ECC step size properties define the correction capability of a controller. Together, they say a controller can correct "{strength} bit errors per {size} bytes". diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index f39775b05779..6669dfcf9e79 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -4272,6 +4272,9 @@ static int nand_dt_init(struct nand_chip *chip) if (ecc_step > 0) chip->ecc.size = ecc_step; + if (of_property_read_bool(dn, "nand-ecc-maximize")) + chip->ecc.options |= NAND_ECC_MAXIMIZE; + return 0; } diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index d3e3f8d03336..331caf987b16 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -141,6 +141,7 @@ enum nand_ecc_algo { * pages and you want to rely on the default implementation. */ #define NAND_ECC_GENERIC_ERASED_CHECK BIT(0) +#define NAND_ECC_MAXIMIZE BIT(1) /* Bit mask for flags passed to do_nand_read_ecc */ #define NAND_GET_DEVICE 0x80 -- cgit v1.2.3-59-g8ed1b From 8bbba48124f2ace8f8bf382539d7115f0f307eeb Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Wed, 8 Jun 2016 17:04:23 +0200 Subject: mtd: nand: Support maximizing ECC when using software BCH Add support for ECC maximization when software BCH with nand_ooblayout_lp_ops layout is used. Other cases should be handled by the NAND controller driver. Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 6669dfcf9e79..a74d9e4bd8b2 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -4396,6 +4396,7 @@ static int nand_set_ecc_soft_ops(struct mtd_info *mtd) ecc->write_page_raw = nand_write_page_raw; ecc->read_oob = nand_read_oob_std; ecc->write_oob = nand_write_oob_std; + /* * Board driver should supply ecc.size and ecc.strength * values to select how many bits are correctable. @@ -4418,6 +4419,25 @@ static int nand_set_ecc_soft_ops(struct mtd_info *mtd) } mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops); + + } + + /* + * We can only maximize ECC config when the default layout is + * used, otherwise we don't know how many bytes can really be + * used. + */ + if (mtd->ooblayout == &nand_ooblayout_lp_ops && + ecc->options & NAND_ECC_MAXIMIZE) { + int steps, bytes; + + /* Always prefer 1k blocks over 512bytes ones */ + ecc->size = 1024; + steps = mtd->writesize / ecc->size; + + /* Reserve 2 bytes for the BBM */ + bytes = (mtd->oobsize - 2) / steps; + ecc->strength = bytes * 8 / fls(8 * ecc->size); } /* See nand_bch_init() for details. */ -- cgit v1.2.3-59-g8ed1b From 4796d8655915b8dce265318542a40d920aad91a3 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Wed, 8 Jun 2016 17:04:24 +0200 Subject: mtd: nand: sunxi: Support ECC maximization Setup the maximum ECC config when NAND_ECC_MAXIMIZE is set. Signed-off-by: Boris Brezillon --- drivers/mtd/nand/sunxi_nand.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c index 2032244138c1..8b8470c4e6d0 100644 --- a/drivers/mtd/nand/sunxi_nand.c +++ b/drivers/mtd/nand/sunxi_nand.c @@ -1787,6 +1787,35 @@ static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd, int ret; int i; + if (ecc->options & NAND_ECC_MAXIMIZE) { + int bytes; + + ecc->size = 1024; + nsectors = mtd->writesize / ecc->size; + + /* Reserve 2 bytes for the BBM */ + bytes = (mtd->oobsize - 2) / nsectors; + + /* 4 non-ECC bytes are added before each ECC bytes section */ + bytes -= 4; + + /* and bytes has to be even. */ + if (bytes % 2) + bytes--; + + ecc->strength = bytes * 8 / fls(8 * ecc->size); + + for (i = 0; i < ARRAY_SIZE(strengths); i++) { + if (strengths[i] > ecc->strength) + break; + } + + if (!i) + ecc->strength = 0; + else + ecc->strength = strengths[i - 1]; + } + if (ecc->size != 512 && ecc->size != 1024) return -EINVAL; -- cgit v1.2.3-59-g8ed1b From fde85cfd2d079bdd733d7b07d9a5775a6f70f458 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Wed, 15 Jun 2016 13:09:51 +0200 Subject: mtd: nand: Fix nand_command_lp() for 8bits opcodes 8 bits opcodes should be followed by a single address cycle. Make the 2nd address cycle dependent of !nand_opcode_8bits(command). Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index a74d9e4bd8b2..c06146941e16 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -745,7 +745,10 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, column >>= 1; chip->cmd_ctrl(mtd, column, ctrl); ctrl &= ~NAND_CTRL_CHANGE; - chip->cmd_ctrl(mtd, column >> 8, ctrl); + + /* Only ouput a single addr cycle for 8bits opcodes. */ + if (!nand_opcode_8bits(command)) + chip->cmd_ctrl(mtd, column >> 8, ctrl); } if (page_addr != -1) { chip->cmd_ctrl(mtd, page_addr, ctrl); -- cgit v1.2.3-59-g8ed1b From ba52b4dd653088cb936b94c447f0a4dd20814629 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sat, 17 Sep 2016 19:44:43 +0200 Subject: mtd: nand: mxc: Test CONFIG_OF instead of CONFIG_OF_MTD We are about to drop the OF_MTD Kconfig option. Test CONFIG_OF activation instead of CONFIG_OF_MTD. Signed-off-by: Boris Brezillon --- drivers/mtd/nand/mxc_nand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 75c0c0eb52ae..d68314c8c399 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -1565,7 +1565,7 @@ static const struct platform_device_id mxcnd_devtype[] = { }; MODULE_DEVICE_TABLE(platform, mxcnd_devtype); -#ifdef CONFIG_OF_MTD +#ifdef CONFIG_OF static const struct of_device_id mxcnd_dt_ids[] = { { .compatible = "fsl,imx21-nand", -- cgit v1.2.3-59-g8ed1b From d44154f969a44269a9288c274c1c2fd9e85df8a5 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Wed, 21 Sep 2016 11:44:41 +0200 Subject: mtd: nand: Provide nand_cleanup() function to free NAND related resources Provide a nand_cleanup() function to free all nand related resources without unregistering the mtd device. This should allow drivers to call mtd_device_unregister() and handle its return value and still being able to cleanup all nand related resources. Signed-off-by: Richard Weinberger Signed-off-by: Daniel Walter Signed-off-by: Boris Brezillon --- drivers/mtd/nand/nand_base.c | 22 +++++++++++++++------- include/linux/mtd/nand.h | 5 ++++- 2 files changed, 19 insertions(+), 8 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index c06146941e16..7a00245ebada 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -4799,19 +4799,15 @@ int nand_scan(struct mtd_info *mtd, int maxchips) EXPORT_SYMBOL(nand_scan); /** - * nand_release - [NAND Interface] Free resources held by the NAND device - * @mtd: MTD device structure + * nand_cleanup - [NAND Interface] Free resources held by the NAND device + * @chip: NAND chip object */ -void nand_release(struct mtd_info *mtd) +void nand_cleanup(struct nand_chip *chip) { - struct nand_chip *chip = mtd_to_nand(mtd); - if (chip->ecc.mode == NAND_ECC_SOFT && chip->ecc.algo == NAND_ECC_BCH) nand_bch_free((struct nand_bch_control *)chip->ecc.priv); - mtd_device_unregister(mtd); - nand_release_data_interface(chip); /* Free bad block table memory */ @@ -4824,6 +4820,18 @@ void nand_release(struct mtd_info *mtd) & NAND_BBT_DYNAMICSTRUCT) kfree(chip->badblock_pattern); } +EXPORT_SYMBOL_GPL(nand_cleanup); + +/** + * nand_release - [NAND Interface] Unregister the MTD device and free resources + * held by the NAND device + * @mtd: MTD device structure + */ +void nand_release(struct mtd_info *mtd) +{ + mtd_device_unregister(mtd); + nand_cleanup(mtd_to_nand(mtd)); +} EXPORT_SYMBOL_GPL(nand_release); MODULE_LICENSE("GPL"); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 331caf987b16..c5d3d5024fc8 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -38,7 +38,7 @@ int nand_scan_ident(struct mtd_info *mtd, int max_chips, struct nand_flash_dev *table); int nand_scan_tail(struct mtd_info *mtd); -/* Free resources held by the NAND device */ +/* Unregister the MTD device and free resources held by the NAND device */ void nand_release(struct mtd_info *mtd); /* Internal helper for board drivers which need to override command function */ @@ -1186,4 +1186,7 @@ int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, /* Reset and initialize a NAND device */ int nand_reset(struct nand_chip *chip); +/* Free resources held by the NAND device */ +void nand_cleanup(struct nand_chip *chip); + #endif /* __LINUX_MTD_NAND_H */ -- cgit v1.2.3-59-g8ed1b From 5e149073ba427f99c5e751e558670d034a278f77 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Wed, 21 Sep 2016 11:43:56 +0200 Subject: mtdpart: Propagate _get/put_device() If the master device has callbacks for _get/put_device() and this MTD has slaves a get_mtd_device() call on paritions will never issue the registered callbacks. Fix this by propagating _get/put_device() down. Reviewed-by: Boris Brezillon Signed-off-by: Richard Weinberger Signed-off-by: Brian Norris --- drivers/mtd/mtdpart.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 1f13e32556f8..ec852fa9200f 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -317,6 +317,18 @@ static int part_block_markbad(struct mtd_info *mtd, loff_t ofs) return res; } +static int part_get_device(struct mtd_info *mtd) +{ + struct mtd_part *part = mtd_to_part(mtd); + return part->master->_get_device(part->master); +} + +static void part_put_device(struct mtd_info *mtd) +{ + struct mtd_part *part = mtd_to_part(mtd); + part->master->_put_device(part->master); +} + static int part_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion) { @@ -463,6 +475,12 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, slave->mtd._block_isbad = part_block_isbad; if (master->_block_markbad) slave->mtd._block_markbad = part_block_markbad; + + if (master->_get_device) + slave->mtd._get_device = part_get_device; + if (master->_put_device) + slave->mtd._put_device = part_put_device; + slave->mtd._erase = part_erase; slave->master = master; slave->offset = part->offset; -- cgit v1.2.3-59-g8ed1b From f5b88de284932def6850e976c18d25940c1b2c3d Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Mon, 3 Oct 2016 09:49:35 -0700 Subject: mtd: nand: fix trivial spelling error Introduced by commit fde85cfd2d07 ("mtd: nand: Fix nand_command_lp() for 8bits opcodes") and I didn't have the heart to have Boris rewrite his pull request just for that. Anyway, there's some value in having stable commit hashes. Signed-off-by: Brian Norris --- drivers/mtd/nand/nand_base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 7a00245ebada..e5718e5ecf92 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -746,7 +746,7 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, chip->cmd_ctrl(mtd, column, ctrl); ctrl &= ~NAND_CTRL_CHANGE; - /* Only ouput a single addr cycle for 8bits opcodes. */ + /* Only output a single addr cycle for 8bits opcodes. */ if (!nand_opcode_8bits(command)) chip->cmd_ctrl(mtd, column >> 8, ctrl); } -- cgit v1.2.3-59-g8ed1b