From 03299d054047c32d864abe2e640533c55acda6c7 Mon Sep 17 00:00:00 2001 From: Wei Li Date: Wed, 7 Apr 2021 18:05:26 +0800 Subject: mtd: rawnand: hisi504: Remove redundant dev_err call in probe There is a error message within devm_ioremap_resource already, so remove the dev_err call to avoid redundant error message. Reported-by: Hulk Robot Signed-off-by: Wei Li Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210407100526.3278741-1-liwei391@huawei.com --- drivers/mtd/nand/raw/hisi504_nand.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/hisi504_nand.c b/drivers/mtd/nand/raw/hisi504_nand.c index 8b2122ce6ec3..78c4e05434e2 100644 --- a/drivers/mtd/nand/raw/hisi504_nand.c +++ b/drivers/mtd/nand/raw/hisi504_nand.c @@ -761,10 +761,8 @@ static int hisi_nfc_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 1); host->mmio = devm_ioremap_resource(dev, res); - if (IS_ERR(host->mmio)) { - dev_err(dev, "devm_ioremap_resource[1] fail\n"); + if (IS_ERR(host->mmio)) return PTR_ERR(host->mmio); - } mtd->name = "hisi_nand"; mtd->dev.parent = &pdev->dev; -- cgit v1.2.3-59-g8ed1b From e101bd30456111d46bf4c54df122314ce96e4180 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Thu, 8 Apr 2021 19:15:12 +0800 Subject: mtd: rawnand: mtk: remove redundant dev_err call in mtk_ecc_probe() There is a error message within devm_ioremap_resource already, so remove the dev_err call to avoid redundant error message. Reported-by: Hulk Robot Signed-off-by: Yu Kuai Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210408111514.1011020-2-yukuai3@huawei.com --- drivers/mtd/nand/raw/mtk_ecc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/mtk_ecc.c b/drivers/mtd/nand/raw/mtk_ecc.c index 75f1fa3d4d35..c437d97debb8 100644 --- a/drivers/mtd/nand/raw/mtk_ecc.c +++ b/drivers/mtd/nand/raw/mtk_ecc.c @@ -515,10 +515,8 @@ static int mtk_ecc_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ecc->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(ecc->regs)) { - dev_err(dev, "failed to map regs: %ld\n", PTR_ERR(ecc->regs)); + if (IS_ERR(ecc->regs)) return PTR_ERR(ecc->regs); - } ecc->clk = devm_clk_get(dev, NULL); if (IS_ERR(ecc->clk)) { -- cgit v1.2.3-59-g8ed1b From c054de10ae5d6d8b3f417cb8d29e67a7cd686f36 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Thu, 8 Apr 2021 19:15:13 +0800 Subject: mtd: plat-ram: remove redundant dev_err call in platram_probe() There is a error message within devm_ioremap_resource already, so remove the dev_err call to avoid redundant error message. Reported-by: Hulk Robot Signed-off-by: Yu Kuai Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210408111514.1011020-3-yukuai3@huawei.com --- drivers/mtd/maps/plat-ram.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/maps/plat-ram.c b/drivers/mtd/maps/plat-ram.c index 0bec7c791d17..cedd8ef9a6bf 100644 --- a/drivers/mtd/maps/plat-ram.c +++ b/drivers/mtd/maps/plat-ram.c @@ -127,7 +127,6 @@ static int platram_probe(struct platform_device *pdev) info->map.virt = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(info->map.virt)) { err = PTR_ERR(info->map.virt); - dev_err(&pdev->dev, "failed to ioremap() region\n"); goto exit_free; } -- cgit v1.2.3-59-g8ed1b From da1e6fe563e62801fa033255f68c0bb9bf8c2c69 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Thu, 8 Apr 2021 21:38:12 +0800 Subject: mtd: phram: Fix error return code in phram_setup() Return a negative error code from the error handling case instead of 0, as done elsewhere in this function. Reported-by: Hulk Robot Signed-off-by: Yu Kuai Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210408133812.1209798-1-yukuai3@huawei.com --- drivers/mtd/devices/phram.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c index 5b04ae6c3057..6ed6c51fac69 100644 --- a/drivers/mtd/devices/phram.c +++ b/drivers/mtd/devices/phram.c @@ -270,6 +270,7 @@ static int phram_setup(const char *val) if (len == 0 || erasesize == 0 || erasesize > len || erasesize > UINT_MAX || rem) { parse_err("illegal erasesize or len\n"); + ret = -EINVAL; goto error; } -- cgit v1.2.3-59-g8ed1b From e4f3c9118f27b3b400db9993dd8bfd028d3b86ca Mon Sep 17 00:00:00 2001 From: Zhang Xiaoxu Date: Thu, 8 Apr 2021 21:07:39 -0400 Subject: mtd: mtd_oobtest: fix error return code in mtd_oobtest_init() Fix to return a negative error code from the error handling case instead of 0, as done elsewhere in this function. Reported-by: Hulk Robot Signed-off-by: Zhang Xiaoxu Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210409010739.1021001-1-zhangxiaoxu5@huawei.com --- drivers/mtd/tests/oobtest.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/tests/oobtest.c b/drivers/mtd/tests/oobtest.c index c71daa89bfce..590d619d2760 100644 --- a/drivers/mtd/tests/oobtest.c +++ b/drivers/mtd/tests/oobtest.c @@ -701,6 +701,7 @@ static int __init mtd_oobtest_init(void) (long long)addr); errcnt += 1; if (errcnt > 1000) { + err = -EINVAL; pr_err("error: too many errors\n"); goto out; } -- cgit v1.2.3-59-g8ed1b From 5b2fbe0ca0e8a88f8da3c96cd6628561852be655 Mon Sep 17 00:00:00 2001 From: Tian Tao Date: Mon, 12 Apr 2021 17:35:43 +0800 Subject: mtd: core: Convert sysfs sprintf/snprintf family to sysfs_emit Use sysfs_emit instead of snprintf to avoid buf overrun,because in sysfs_emit it strictly checks whether buf is null or buf whether pagesize aligned, otherwise it returns an error. Signed-off-by: Tian Tao Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1618220144-33839-2-git-send-email-tiantao6@hisilicon.com --- drivers/mtd/mtdcore.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 9aaeadd53eb4..1fb43eea3599 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -131,7 +131,7 @@ static ssize_t mtd_type_show(struct device *dev, type = "unknown"; } - return snprintf(buf, PAGE_SIZE, "%s\n", type); + return sysfs_emit(buf, "%s\n", type); } static DEVICE_ATTR(type, S_IRUGO, mtd_type_show, NULL); @@ -140,7 +140,7 @@ static ssize_t mtd_flags_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "0x%lx\n", (unsigned long)mtd->flags); + return sysfs_emit(buf, "0x%lx\n", (unsigned long)mtd->flags); } static DEVICE_ATTR(flags, S_IRUGO, mtd_flags_show, NULL); @@ -149,8 +149,7 @@ static ssize_t mtd_size_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%llu\n", - (unsigned long long)mtd->size); + return sysfs_emit(buf, "%llu\n", (unsigned long long)mtd->size); } static DEVICE_ATTR(size, S_IRUGO, mtd_size_show, NULL); @@ -159,7 +158,7 @@ static ssize_t mtd_erasesize_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->erasesize); + return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->erasesize); } static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL); @@ -168,7 +167,7 @@ static ssize_t mtd_writesize_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->writesize); + return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->writesize); } static DEVICE_ATTR(writesize, S_IRUGO, mtd_writesize_show, NULL); @@ -178,7 +177,7 @@ static ssize_t mtd_subpagesize_show(struct device *dev, struct mtd_info *mtd = dev_get_drvdata(dev); unsigned int subpagesize = mtd->writesize >> mtd->subpage_sft; - return snprintf(buf, PAGE_SIZE, "%u\n", subpagesize); + return sysfs_emit(buf, "%u\n", subpagesize); } static DEVICE_ATTR(subpagesize, S_IRUGO, mtd_subpagesize_show, NULL); @@ -187,7 +186,7 @@ static ssize_t mtd_oobsize_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->oobsize); + return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->oobsize); } static DEVICE_ATTR(oobsize, S_IRUGO, mtd_oobsize_show, NULL); @@ -196,7 +195,7 @@ static ssize_t mtd_oobavail_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", mtd->oobavail); + return sysfs_emit(buf, "%u\n", mtd->oobavail); } static DEVICE_ATTR(oobavail, S_IRUGO, mtd_oobavail_show, NULL); @@ -205,7 +204,7 @@ static ssize_t mtd_numeraseregions_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", mtd->numeraseregions); + return sysfs_emit(buf, "%u\n", mtd->numeraseregions); } static DEVICE_ATTR(numeraseregions, S_IRUGO, mtd_numeraseregions_show, NULL); @@ -215,7 +214,7 @@ static ssize_t mtd_name_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%s\n", mtd->name); + return sysfs_emit(buf, "%s\n", mtd->name); } static DEVICE_ATTR(name, S_IRUGO, mtd_name_show, NULL); @@ -224,7 +223,7 @@ static ssize_t mtd_ecc_strength_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", mtd->ecc_strength); + return sysfs_emit(buf, "%u\n", mtd->ecc_strength); } static DEVICE_ATTR(ecc_strength, S_IRUGO, mtd_ecc_strength_show, NULL); @@ -234,7 +233,7 @@ static ssize_t mtd_bitflip_threshold_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", mtd->bitflip_threshold); + return sysfs_emit(buf, "%u\n", mtd->bitflip_threshold); } static ssize_t mtd_bitflip_threshold_store(struct device *dev, @@ -261,7 +260,7 @@ static ssize_t mtd_ecc_step_size_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%u\n", mtd->ecc_step_size); + return sysfs_emit(buf, "%u\n", mtd->ecc_step_size); } static DEVICE_ATTR(ecc_step_size, S_IRUGO, mtd_ecc_step_size_show, NULL); @@ -272,7 +271,7 @@ static ssize_t mtd_ecc_stats_corrected_show(struct device *dev, struct mtd_info *mtd = dev_get_drvdata(dev); struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; - return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->corrected); + return sysfs_emit(buf, "%u\n", ecc_stats->corrected); } static DEVICE_ATTR(corrected_bits, S_IRUGO, mtd_ecc_stats_corrected_show, NULL); @@ -283,7 +282,7 @@ static ssize_t mtd_ecc_stats_errors_show(struct device *dev, struct mtd_info *mtd = dev_get_drvdata(dev); struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; - return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->failed); + return sysfs_emit(buf, "%u\n", ecc_stats->failed); } static DEVICE_ATTR(ecc_failures, S_IRUGO, mtd_ecc_stats_errors_show, NULL); @@ -293,7 +292,7 @@ static ssize_t mtd_badblocks_show(struct device *dev, struct mtd_info *mtd = dev_get_drvdata(dev); struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; - return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->badblocks); + return sysfs_emit(buf, "%u\n", ecc_stats->badblocks); } static DEVICE_ATTR(bad_blocks, S_IRUGO, mtd_badblocks_show, NULL); @@ -303,7 +302,7 @@ static ssize_t mtd_bbtblocks_show(struct device *dev, struct mtd_info *mtd = dev_get_drvdata(dev); struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; - return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->bbtblocks); + return sysfs_emit(buf, "%u\n", ecc_stats->bbtblocks); } static DEVICE_ATTR(bbt_blocks, S_IRUGO, mtd_bbtblocks_show, NULL); -- cgit v1.2.3-59-g8ed1b From ce675043fa12dfc50276e00f31da61e14846b178 Mon Sep 17 00:00:00 2001 From: Tian Tao Date: Mon, 12 Apr 2021 17:35:44 +0800 Subject: mtd: mtdpart: Convert sysfs sprintf/snprintf family to sysfs_emit Use sysfs_emit instead of snprintf to avoid buf overrun,because in sysfs_emit it strictly checks whether buf is null or buf whether pagesize aligned, otherwise it returns an error. Signed-off-by: Tian Tao Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1618220144-33839-3-git-send-email-tiantao6@hisilicon.com --- drivers/mtd/mtdpart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 665fd9020b76..66a67c6e3cbc 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -217,7 +217,7 @@ static ssize_t mtd_partition_offset_show(struct device *dev, { struct mtd_info *mtd = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%lld\n", mtd->part.offset); + return sysfs_emit(buf, "%lld\n", mtd->part.offset); } static DEVICE_ATTR(offset, S_IRUGO, mtd_partition_offset_show, NULL); -- cgit v1.2.3-59-g8ed1b From 4d888eceb725216305eee595ff5d068112cf7c15 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 20 Apr 2021 11:44:51 +0300 Subject: mtd: rawnand: silence static checker warning in nand_setup_interface() Smatch complains that the error code is not set on this error path: drivers/mtd/nand/raw/nand_base.c:842 nand_setup_interface() warn: missing error code 'ret' But actually returning success is intentional because the NAND chip will still work in mode 0. This patch adds a "ret = 0;" assignment to make the intent more clear and to silence the static checker warning. It doesn't affect the compiled code because GCC optimises the assignment away. Signed-off-by: Dan Carpenter Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/YH6Ugwz3gcga+q8X@mwanda --- drivers/mtd/nand/raw/nand_base.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index fb072c444495..e29e5b3d31bf 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -880,6 +880,7 @@ static int nand_setup_interface(struct nand_chip *chip, int chipnr) if (tmode_param[0] != chip->best_interface_config->timings.mode) { pr_warn("timing mode %d not acknowledged by the NAND chip\n", chip->best_interface_config->timings.mode); + ret = 0; goto err_reset_chip; } -- cgit v1.2.3-59-g8ed1b From 21020becdf0ce946d3fc2c4bf4da33453affcd5a Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Wed, 21 Apr 2021 21:31:09 +0300 Subject: mtd: rawnand: qcom: allow override of partition parser Commit 82bfd11f1b03 ("mtd: rawnand: qcom: Add support for Qcom SMEM parser") made qcomsmem the only parser for qcom_nandc partitions. This might be problematic for systems with both SPI and NAND MTD devices. When booting from SPI flash the partition table should not apply to the NAND flash. Prepend cmdlinepart and ofpart parsers to allow override of the partition parser. Cc: Manivannan Sadhasivam Signed-off-by: Baruch Siach Reviewed-by: Manivannan Sadhasivam Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/cf470f05af514acd5bd94548804ffa96b966a0a8.1619029869.git.baruch@tkos.co.il --- drivers/mtd/nand/raw/qcom_nandc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index a64fb6ce915d..ef90b669a27d 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -2882,7 +2882,7 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc) return 0; } -static const char * const probes[] = { "qcomsmem", NULL }; +static const char * const probes[] = { "cmdlinepart", "ofpart", "qcomsmem", NULL }; static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc, struct qcom_nand_host *host, -- cgit v1.2.3-59-g8ed1b From 16a7af2ecc2217ba8c317fdd2ca177bbc7b0a8d2 Mon Sep 17 00:00:00 2001 From: Tian Tao Date: Wed, 14 Apr 2021 09:31:14 +0800 Subject: mtd: nftlcore: remove set but rewrite variables The value of lastEUN is not used at line 622, and will rewrite a new value at line 541. so just remote the line at 622. Signed-off-by: Tian Tao Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1618363874-25113-1-git-send-email-tiantao6@hisilicon.com --- drivers/mtd/nftlcore.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c index bcd0094f172d..913db0dd6a8d 100644 --- a/drivers/mtd/nftlcore.c +++ b/drivers/mtd/nftlcore.c @@ -619,7 +619,6 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) return BLOCK_NIL; } //printk("Restarting scan\n"); - lastEUN = BLOCK_NIL; continue; } -- cgit v1.2.3-59-g8ed1b From d7f7e04f8b67571a4bf5a0dcd4f9da4214f5262c Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 18 Apr 2021 23:46:15 +0200 Subject: mtd: parsers: trx: Allow to specify brcm, trx-magic in DT Buffalo uses a different TRX magic for every device, to be able to use this trx parser, make it possible to specify the TRX magic in device tree. If no TRX magic is specified in device tree, the standard value will be used. This value should only be specified if a vendor chooses to use a non standard TRX magic. Signed-off-by: Hauke Mehrtens Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210418214616.239574-3-hauke@hauke-m.de --- drivers/mtd/parsers/parser_trx.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/parsers/parser_trx.c b/drivers/mtd/parsers/parser_trx.c index 8541182134d4..4814cf218e17 100644 --- a/drivers/mtd/parsers/parser_trx.c +++ b/drivers/mtd/parsers/parser_trx.c @@ -51,13 +51,20 @@ static int parser_trx_parse(struct mtd_info *mtd, const struct mtd_partition **pparts, struct mtd_part_parser_data *data) { + struct device_node *np = mtd_get_of_node(mtd); struct mtd_partition *parts; struct mtd_partition *part; struct trx_header trx; size_t bytes_read; uint8_t curr_part = 0, i = 0; + uint32_t trx_magic = TRX_MAGIC; int err; + /* Get different magic from device tree if specified */ + err = of_property_read_u32(np, "brcm,trx-magic", &trx_magic); + if (err != 0 && err != -EINVAL) + pr_err("failed to parse \"brcm,trx-magic\" DT attribute, using default: %d\n", err); + parts = kcalloc(TRX_PARSER_MAX_PARTS, sizeof(struct mtd_partition), GFP_KERNEL); if (!parts) @@ -70,7 +77,7 @@ static int parser_trx_parse(struct mtd_info *mtd, return err; } - if (trx.magic != TRX_MAGIC) { + if (trx.magic != trx_magic) { kfree(parts); return -ENOENT; } -- cgit v1.2.3-59-g8ed1b From 81bb218c829246962a6327c64eec18ddcc049936 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 18 Apr 2021 23:46:16 +0200 Subject: mtd: parsers: trx: Allow to use TRX parser on Mediatek SoCs Buffalo uses the TRX partition format also on Mediatek MT7622 SoCs. Signed-off-by: Hauke Mehrtens Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210418214616.239574-4-hauke@hauke-m.de --- drivers/mtd/parsers/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/parsers/Kconfig b/drivers/mtd/parsers/Kconfig index 9babe678c41b..337ea8b9a4c3 100644 --- a/drivers/mtd/parsers/Kconfig +++ b/drivers/mtd/parsers/Kconfig @@ -115,7 +115,7 @@ config MTD_AFS_PARTS config MTD_PARSER_TRX tristate "Parser for TRX format partitions" - depends on MTD && (BCM47XX || ARCH_BCM_5301X || COMPILE_TEST) + depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || COMPILE_TEST) help TRX is a firmware format used by Broadcom on their devices. It may contain up to 3/4 partitions (depending on the version). -- cgit v1.2.3-59-g8ed1b From 4b361cfa862479fbb1d14ddf01de4dbc7146dcc5 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Sat, 24 Apr 2021 13:06:08 +0200 Subject: mtd: core: add OTP nvmem provider support Flash OTP regions can already be read via user space. Some boards have their serial number or MAC addresses stored in the OTP regions. Add support for them being a (read-only) nvmem provider. The API to read the OTP data is already in place. It distinguishes between factory and user OTP, thus there are up to two different providers. Signed-off-by: Michael Walle Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210424110608.15748-6-michael@walle.cc --- drivers/mtd/mtdcore.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/mtd.h | 2 + 2 files changed, 150 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 1fb43eea3599..3f2e20e22501 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -776,6 +776,146 @@ static void mtd_set_dev_defaults(struct mtd_info *mtd) mutex_init(&mtd->master.chrdev_lock); } +static ssize_t mtd_otp_size(struct mtd_info *mtd, bool is_user) +{ + struct otp_info *info = kmalloc(PAGE_SIZE, GFP_KERNEL); + ssize_t size = 0; + unsigned int i; + size_t retlen; + int ret; + + if (is_user) + ret = mtd_get_user_prot_info(mtd, PAGE_SIZE, &retlen, info); + else + ret = mtd_get_fact_prot_info(mtd, PAGE_SIZE, &retlen, info); + if (ret) + goto err; + + for (i = 0; i < retlen / sizeof(*info); i++) { + size += info->length; + info++; + } + + kfree(info); + return size; + +err: + kfree(info); + return ret; +} + +static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd, + const char *compatible, + int size, + nvmem_reg_read_t reg_read) +{ + struct nvmem_device *nvmem = NULL; + struct nvmem_config config = {}; + struct device_node *np; + + /* DT binding is optional */ + np = of_get_compatible_child(mtd->dev.of_node, compatible); + + /* OTP nvmem will be registered on the physical device */ + config.dev = mtd->dev.parent; + /* just reuse the compatible as name */ + config.name = compatible; + config.id = NVMEM_DEVID_NONE; + config.owner = THIS_MODULE; + config.type = NVMEM_TYPE_OTP; + config.root_only = true; + config.reg_read = reg_read; + config.size = size; + config.of_node = np; + config.priv = mtd; + + nvmem = nvmem_register(&config); + /* Just ignore if there is no NVMEM support in the kernel */ + if (IS_ERR(nvmem) && PTR_ERR(nvmem) == -EOPNOTSUPP) + nvmem = NULL; + + of_node_put(np); + + return nvmem; +} + +static int mtd_nvmem_user_otp_reg_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct mtd_info *mtd = priv; + size_t retlen; + int ret; + + ret = mtd_read_user_prot_reg(mtd, offset, bytes, &retlen, val); + if (ret) + return ret; + + return retlen == bytes ? 0 : -EIO; +} + +static int mtd_nvmem_fact_otp_reg_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct mtd_info *mtd = priv; + size_t retlen; + int ret; + + ret = mtd_read_fact_prot_reg(mtd, offset, bytes, &retlen, val); + if (ret) + return ret; + + return retlen == bytes ? 0 : -EIO; +} + +static int mtd_otp_nvmem_add(struct mtd_info *mtd) +{ + struct nvmem_device *nvmem; + ssize_t size; + int err; + + if (mtd->_get_user_prot_info && mtd->_read_user_prot_reg) { + size = mtd_otp_size(mtd, true); + if (size < 0) + return size; + + if (size > 0) { + nvmem = mtd_otp_nvmem_register(mtd, "user-otp", size, + mtd_nvmem_user_otp_reg_read); + if (IS_ERR(nvmem)) { + dev_err(&mtd->dev, "Failed to register OTP NVMEM device\n"); + return PTR_ERR(nvmem); + } + mtd->otp_user_nvmem = nvmem; + } + } + + if (mtd->_get_fact_prot_info && mtd->_read_fact_prot_reg) { + size = mtd_otp_size(mtd, false); + if (size < 0) { + err = size; + goto err; + } + + if (size > 0) { + nvmem = mtd_otp_nvmem_register(mtd, "factory-otp", size, + mtd_nvmem_fact_otp_reg_read); + if (IS_ERR(nvmem)) { + dev_err(&mtd->dev, "Failed to register OTP NVMEM device\n"); + err = PTR_ERR(nvmem); + goto err; + } + mtd->otp_factory_nvmem = nvmem; + } + } + + return 0; + +err: + if (mtd->otp_user_nvmem) + nvmem_unregister(mtd->otp_user_nvmem); + return err; +} + /** * mtd_device_parse_register - parse partitions and register an MTD device. * @@ -851,6 +991,8 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, register_reboot_notifier(&mtd->reboot_notifier); } + ret = mtd_otp_nvmem_add(mtd); + out: if (ret && device_is_registered(&mtd->dev)) del_mtd_device(mtd); @@ -872,6 +1014,12 @@ int mtd_device_unregister(struct mtd_info *master) if (master->_reboot) unregister_reboot_notifier(&master->reboot_notifier); + if (master->otp_user_nvmem) + nvmem_unregister(master->otp_user_nvmem); + + if (master->otp_factory_nvmem) + nvmem_unregister(master->otp_factory_nvmem); + err = del_mtd_partitions(master); if (err) return err; diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index a89955f3cbc8..88227044fc86 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -380,6 +380,8 @@ struct mtd_info { int usecount; struct mtd_debug_info dbg; struct nvmem_device *nvmem; + struct nvmem_device *otp_user_nvmem; + struct nvmem_device *otp_factory_nvmem; /* * Parent device from the MTD partition point of view. -- cgit v1.2.3-59-g8ed1b From 6500dc2bde937fb124a399211686e04b8ef9d44e Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Sun, 25 Apr 2021 18:28:18 +0800 Subject: mtd: mtd_oobtest: Remove redundant assignment to err Variable err is set to zero but this value is never read as it is overwritten with a new value later on, hence it is a redundant assignment and can be removed. Cleans up the following clang-analyzer warning: drivers/mtd/tests/oobtest.c:626:4: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores]. drivers/mtd/tests/oobtest.c:603:4: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores]. drivers/mtd/tests/oobtest.c:579:4: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores]. drivers/mtd/tests/oobtest.c:556:4: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores]. drivers/mtd/tests/oobtest.c:532:3: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores]. drivers/mtd/tests/oobtest.c:509:3: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores]. Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1619346498-43617-1-git-send-email-jiapeng.chong@linux.alibaba.com --- drivers/mtd/tests/oobtest.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/tests/oobtest.c b/drivers/mtd/tests/oobtest.c index 590d619d2760..532997e10e29 100644 --- a/drivers/mtd/tests/oobtest.c +++ b/drivers/mtd/tests/oobtest.c @@ -506,7 +506,6 @@ static int __init mtd_oobtest_init(void) err = mtd_write_oob(mtd, addr0, &ops); if (err) { pr_info("error occurred as expected\n"); - err = 0; } else { pr_err("error: can write past end of OOB\n"); errcnt += 1; @@ -529,7 +528,6 @@ static int __init mtd_oobtest_init(void) if (err) { pr_info("error occurred as expected\n"); - err = 0; } else { pr_err("error: can read past end of OOB\n"); errcnt += 1; @@ -553,7 +551,6 @@ static int __init mtd_oobtest_init(void) err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops); if (err) { pr_info("error occurred as expected\n"); - err = 0; } else { pr_err("error: wrote past end of device\n"); errcnt += 1; @@ -576,7 +573,6 @@ static int __init mtd_oobtest_init(void) if (err) { pr_info("error occurred as expected\n"); - err = 0; } else { pr_err("error: read past end of device\n"); errcnt += 1; @@ -600,7 +596,6 @@ static int __init mtd_oobtest_init(void) err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops); if (err) { pr_info("error occurred as expected\n"); - err = 0; } else { pr_err("error: wrote past end of device\n"); errcnt += 1; @@ -623,7 +618,6 @@ static int __init mtd_oobtest_init(void) if (err) { pr_info("error occurred as expected\n"); - err = 0; } else { pr_err("error: read past end of device\n"); errcnt += 1; -- cgit v1.2.3-59-g8ed1b From 9c5b19c2eea8cb8d5784dedce8cac07e9a20198e Mon Sep 17 00:00:00 2001 From: Yang Li Date: Mon, 26 Apr 2021 17:32:23 +0800 Subject: mtd: *nftl: return -ENOMEM when kmalloc failed The driver is using -1 instead of the -ENOMEM defined macro to specify that a buffer allocation failed. Using the correct error code is more intuitive Smatch tool warning: drivers/mtd/inftlmount.c:333 check_free_sectors() warn: returning -1 instead of -ENOMEM is sloppy drivers/mtd/nftlmount.c:272 check_free_sectors() warn: returning -1 instead of -ENOMEM is sloppy No functional change, just more standardized. Reported-by: Abaci Robot Signed-off-by: Yang Li [: Fixed the title] Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1619429543-52234-1-git-send-email-yang.lee@linux.alibaba.com --- drivers/mtd/inftlmount.c | 2 +- drivers/mtd/nftlmount.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c index af16d3485de0..39d024118610 100644 --- a/drivers/mtd/inftlmount.c +++ b/drivers/mtd/inftlmount.c @@ -330,7 +330,7 @@ static int check_free_sectors(struct INFTLrecord *inftl, unsigned int address, buf = kmalloc(SECTORSIZE + mtd->oobsize, GFP_KERNEL); if (!buf) - return -1; + return -ENOMEM; ret = -1; for (i = 0; i < len; i += SECTORSIZE) { diff --git a/drivers/mtd/nftlmount.c b/drivers/mtd/nftlmount.c index 444a77bb7692..fd331bc15871 100644 --- a/drivers/mtd/nftlmount.c +++ b/drivers/mtd/nftlmount.c @@ -269,7 +269,7 @@ static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int buf = kmalloc(SECTORSIZE + mtd->oobsize, GFP_KERNEL); if (!buf) - return -1; + return -ENOMEM; ret = -1; for (i = 0; i < len; i += SECTORSIZE) { -- cgit v1.2.3-59-g8ed1b From 063deb31ae902b510e57af17a45151baf5057a61 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Wed, 28 Apr 2021 18:02:22 +0800 Subject: mtd: tests: Remove redundant assignment to err Variable 'err' is set to 0 but this value is never read as it is overwritten with a new value later on, hence it is a redundant assignment and can be removed. Cleans up the following clang-analyzer warning: drivers/mtd/tests/torturetest.c:233:2: warning: Value stored to 'err' is never read [clang-analyzer-deadcode.DeadStores] Reported-by: Abaci Robot Signed-off-by: Yang Li Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1619604142-119891-1-git-send-email-yang.lee@linux.alibaba.com --- drivers/mtd/tests/torturetest.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/tests/torturetest.c b/drivers/mtd/tests/torturetest.c index 6787ac5471a9..841689b4d86d 100644 --- a/drivers/mtd/tests/torturetest.c +++ b/drivers/mtd/tests/torturetest.c @@ -230,8 +230,6 @@ static int __init tort_init(void) if (!bad_ebs) goto out_check_buf; - err = 0; - /* Initialize patterns */ memset(patt_FF, 0xFF, mtd->erasesize); for (i = 0; i < mtd->erasesize / pgsize; i++) { -- cgit v1.2.3-59-g8ed1b From feb05fae4df10f6b9bf720662623efc88334c095 Mon Sep 17 00:00:00 2001 From: Petr Malat Date: Fri, 30 Apr 2021 08:50:57 +0200 Subject: mtd: Create partname and partid debug files for child MTDs Partname and partid are set by the upper driver (spi-nor) on the master MTD. If this MTD is partitioned and CONFIG_MTD_PARTITIONED_MASTER is disabled, the master MTD is not instantiated and partname and partid aren't available to the userspace. Always read the partname and partid from the master MTD, they describe the HW, which can't differ between master and its children. Signed-off-by: Petr Malat Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210430065057.32018-1-oss@malat.biz --- drivers/mtd/mtdcore.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 3f2e20e22501..3ae261661eea 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -360,6 +360,7 @@ static struct dentry *dfs_dir_mtd; static void mtd_debugfs_populate(struct mtd_info *mtd) { + struct mtd_info *master = mtd_get_master(mtd); struct device *dev = &mtd->dev; struct dentry *root; @@ -369,12 +370,12 @@ static void mtd_debugfs_populate(struct mtd_info *mtd) root = debugfs_create_dir(dev_name(dev), dfs_dir_mtd); mtd->dbg.dfs_dir = root; - if (mtd->dbg.partid) - debugfs_create_file("partid", 0400, root, mtd, + if (master->dbg.partid) + debugfs_create_file("partid", 0400, root, master, &mtd_partid_debug_fops); - if (mtd->dbg.partname) - debugfs_create_file("partname", 0400, root, mtd, + if (master->dbg.partname) + debugfs_create_file("partname", 0400, root, master, &mtd_partname_debug_fops); } -- cgit v1.2.3-59-g8ed1b From cc9d663a00a00b4a3d54875e024b79142416caf6 Mon Sep 17 00:00:00 2001 From: Shubhankar Kuranagatti Date: Tue, 4 May 2021 11:50:59 +0530 Subject: drivers: mtd: sm_ftl: Fix alignment of block comment A star has been added to subsequent line of block comment The closing */ has been shifted to new line This is done to maintain code uniformity Signed-off-by: Shubhankar Kuranagatti [: Fixed the title] Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210504062059.mywqzwveyjfawreg@kewl-virtual-machine --- drivers/mtd/sm_ftl.c | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c index 4d1ae25507ab..0cff2cda1b5a 100644 --- a/drivers/mtd/sm_ftl.c +++ b/drivers/mtd/sm_ftl.c @@ -265,7 +265,8 @@ static int sm_read_sector(struct sm_ftl *ftl, again: if (try++) { /* Avoid infinite recursion on CIS reads, sm_recheck_media - won't help anyway */ + * won't help anyway + */ if (zone == 0 && block == ftl->cis_block && boffset == ftl->cis_boffset) return ret; @@ -276,7 +277,8 @@ again: } /* Unfortunately, oob read will _always_ succeed, - despite card removal..... */ + * despite card removal..... + */ ret = mtd_read_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops); /* Test for unknown errors */ @@ -411,9 +413,10 @@ restart: /* If write fails. try to erase the block */ /* This is safe, because we never write in blocks - that contain valuable data. - This is intended to repair block that are marked - as erased, but that isn't fully erased*/ + * that contain valuable data. + * This is intended to repair block that are marked + * as erased, but that isn't fully erased + */ if (sm_erase_block(ftl, zone, block, 0)) return -EIO; @@ -448,7 +451,8 @@ static void sm_mark_block_bad(struct sm_ftl *ftl, int zone, int block) /* We aren't checking the return value, because we don't care */ /* This also fails on fake xD cards, but I guess these won't expose - any bad blocks till fail completely */ + * any bad blocks till fail completely + */ for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE) sm_write_sector(ftl, zone, block, boffset, NULL, &oob); } @@ -505,7 +509,8 @@ static int sm_check_block(struct sm_ftl *ftl, int zone, int block) /* First just check that block doesn't look fishy */ /* Only blocks that are valid or are sliced in two parts, are - accepted */ + * accepted + */ for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE) { @@ -554,7 +559,8 @@ static const uint8_t cis_signature[] = { 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20 }; /* Find out media parameters. - * This ideally has to be based on nand id, but for now device size is enough */ + * This ideally has to be based on nand id, but for now device size is enough + */ static int sm_get_media_info(struct sm_ftl *ftl, struct mtd_info *mtd) { int i; @@ -607,7 +613,8 @@ static int sm_get_media_info(struct sm_ftl *ftl, struct mtd_info *mtd) } /* Minimum xD size is 16MiB. Also, all xD cards have standard zone - sizes. SmartMedia cards exist up to 128 MiB and have same layout*/ + * sizes. SmartMedia cards exist up to 128 MiB and have same layout + */ if (size_in_megs >= 16) { ftl->zone_count = size_in_megs / 16; ftl->zone_size = 1024; @@ -782,7 +789,8 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num) } /* Test to see if block is erased. It is enough to test - first sector, because erase happens in one shot */ + * first sector, because erase happens in one shot + */ if (sm_block_erased(&oob)) { kfifo_in(&zone->free_sectors, (unsigned char *)&block, 2); @@ -792,7 +800,8 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num) /* If block is marked as bad, skip it */ /* This assumes we can trust first sector*/ /* However the way the block valid status is defined, ensures - very low probability of failure here */ + * very low probability of failure here + */ if (!sm_block_valid(&oob)) { dbg("PH %04d <-> ", block); continue; @@ -803,7 +812,8 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num) /* Invalid LBA means that block is damaged. */ /* We can try to erase it, or mark it as bad, but - lets leave that to recovery application */ + * lets leave that to recovery application + */ if (lba == -2 || lba >= ftl->max_lba) { dbg("PH %04d <-> LBA %04d(bad)", block, lba); continue; @@ -811,7 +821,8 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num) /* If there is no collision, - just put the sector in the FTL table */ + * just put the sector in the FTL table + */ if (zone->lba_to_phys_table[lba] < 0) { dbg_verbose("PH %04d <-> LBA %04d", block, lba); zone->lba_to_phys_table[lba] = block; @@ -834,9 +845,9 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num) } /* If both blocks are valid and share same LBA, it means that - they hold different versions of same data. It not - known which is more recent, thus just erase one of them - */ + * they hold different versions of same data. It not + * known which is more recent, thus just erase one of them + */ sm_printk("both blocks are valid, erasing the later"); sm_erase_block(ftl, zone_num, block, 1); } @@ -845,7 +856,8 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num) zone->initialized = 1; /* No free sectors, means that the zone is heavily damaged, write won't - work, but it can still can be (partially) read */ + * work, but it can still can be (partially) read + */ if (!kfifo_len(&zone->free_sectors)) { sm_printk("no free blocks in zone %d", zone_num); return 0; @@ -952,8 +964,9 @@ restart: /* If there are no spare blocks, */ /* we could still continue by erasing/writing the current block, - but for such worn out media it doesn't worth the trouble, - and the dangers */ + * but for such worn out media it doesn't worth the trouble, + * and the dangers + */ if (kfifo_out(&zone->free_sectors, (unsigned char *)&write_sector, 2) != 2) { dbg("no free sectors for write!"); -- cgit v1.2.3-59-g8ed1b From fd0d8d85f7230052e638a56d1bfea170c488e6bc Mon Sep 17 00:00:00 2001 From: "Doyle, Patrick" Date: Tue, 6 Apr 2021 10:47:08 +0900 Subject: mtd: nand: bbt: Fix corner case in bad block table handling In the unlikely event that both blocks 10 and 11 are marked as bad (on a 32 bit machine), then the process of marking block 10 as bad stomps on cached entry for block 11. There are (of course) other examples. Signed-off-by: Patrick Doyle Reviewed-by: Richard Weinberger Signed-off-by: Yoshio Furuyama [: Fixed the title] Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/774a92693f311e7de01e5935e720a179fb1b2468.1616635406.git.ytc-mb-yfuruyama7@kioxia.com --- drivers/mtd/nand/bbt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/bbt.c b/drivers/mtd/nand/bbt.c index 044adf913854..64af6898131d 100644 --- a/drivers/mtd/nand/bbt.c +++ b/drivers/mtd/nand/bbt.c @@ -123,7 +123,7 @@ int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry, unsigned int rbits = bits_per_block + offs - BITS_PER_LONG; pos[1] &= ~GENMASK(rbits - 1, 0); - pos[1] |= val >> rbits; + pos[1] |= val >> (bits_per_block - rbits); } return 0; -- cgit v1.2.3-59-g8ed1b From bc8e157fdb466536557b97b6c0df6d7b46a2b91b Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Tue, 18 May 2021 19:55:03 +0100 Subject: mtd: core: Fix freeing of otp_info buffer Commit 4b361cfa8624 ("mtd: core: add OTP nvmem provider support") is causing the following panic ... ------------[ cut here ]------------ kernel BUG at /local/workdir/tegra/linux_next/kernel/mm/slab.c:2730! Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM Modules linked in: CPU: 3 PID: 1 Comm: swapper/0 Not tainted 5.13.0-rc2-next-20210518 #1 Hardware name: NVIDIA Tegra SoC (Flattened Device Tree) PC is at ___cache_free+0x3f8/0x51c ... [] (___cache_free) from [] (kfree+0xac/0x1bc) [] (kfree) from [] (mtd_otp_size+0xc4/0x108) [] (mtd_otp_size) from [] (mtd_device_parse_register+0xe4/0x2b4) [] (mtd_device_parse_register) from [] (spi_nor_probe+0x210/0x2c0) [] (spi_nor_probe) from [] (spi_probe+0x88/0xac) [] (spi_probe) from [] (really_probe+0x214/0x3a4) [] (really_probe) from [] (driver_probe_device+0x68/0xc0) [] (driver_probe_device) from [] (bus_for_each_drv+0x5c/0xbc) [] (bus_for_each_drv) from [] (__device_attach+0xe4/0x150) [] (__device_attach) from [] (bus_probe_device+0x84/0x8c) [] (bus_probe_device) from [] (device_add+0x48c/0x868) [] (device_add) from [] (spi_add_device+0xa0/0x168) [] (spi_add_device) from [] (spi_register_controller+0x8b8/0xb38) [] (spi_register_controller) from [] (devm_spi_register_controller+0x14/0x50) [] (devm_spi_register_controller) from [] (tegra_spi_probe+0x33c/0x450) [] (tegra_spi_probe) from [] (platform_probe+0x5c/0xb8) [] (platform_probe) from [] (really_probe+0x214/0x3a4) [] (really_probe) from [] (driver_probe_device+0x68/0xc0) [] (driver_probe_device) from [] (device_driver_attach+0x58/0x60) [] (device_driver_attach) from [] (__driver_attach+0x80/0xc8) [] (__driver_attach) from [] (bus_for_each_dev+0x78/0xb8) [] (bus_for_each_dev) from [] (bus_add_driver+0x164/0x1e8) [] (bus_add_driver) from [] (driver_register+0x7c/0x114) [] (driver_register) from [] (do_one_initcall+0x50/0x2b0) [] (do_one_initcall) from [] (kernel_init_freeable+0x1a8/0x1fc) [] (kernel_init_freeable) from [] (kernel_init+0x8/0x118) [] (kernel_init) from [] (ret_from_fork+0x14/0x24) ... ---[ end trace 0f652dd222de75d7 ]--- In the function mtd_otp_size() a buffer is allocated by calling kmalloc() and a pointer to the buffer is stored in a variable 'info'. The pointer 'info' may then be incremented depending on the length returned from mtd_get_user/fact_prot_info(). If 'info' is incremented, when kfree() is called to free the buffer the above panic occurs because we are no longer passing the original address of the buffer allocated. Fix this by indexing through the buffer allocated to avoid incrementing the pointer. Fixes: 4b361cfa8624 ("mtd: core: add OTP nvmem provider support") Signed-off-by: Jon Hunter Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210518185503.162787-1-jonathanh@nvidia.com --- drivers/mtd/mtdcore.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 3ae261661eea..ffa46ccea0cf 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -792,10 +792,8 @@ static ssize_t mtd_otp_size(struct mtd_info *mtd, bool is_user) if (ret) goto err; - for (i = 0; i < retlen / sizeof(*info); i++) { - size += info->length; - info++; - } + for (i = 0; i < retlen / sizeof(*info); i++) + size += info[i].length; kfree(info); return size; -- cgit v1.2.3-59-g8ed1b From c3c8c051df3ee5042dd91593593a8b0e008f4c85 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 14 May 2021 17:27:15 +0300 Subject: mtd: core: Potential NULL dereference in mtd_otp_size() If kmalloc() fails then it could lead to a NULL dereference. Check and return -ENOMEM on error. Fixes: 4b361cfa8624 ("mtd: core: add OTP nvmem provider support") Signed-off-by: Dan Carpenter Reviewed-by: Michael Walle Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/YJ6Iw3iNvGycAWV6@mwanda --- drivers/mtd/mtdcore.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index ffa46ccea0cf..ce514305f8f7 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -779,12 +779,16 @@ static void mtd_set_dev_defaults(struct mtd_info *mtd) static ssize_t mtd_otp_size(struct mtd_info *mtd, bool is_user) { - struct otp_info *info = kmalloc(PAGE_SIZE, GFP_KERNEL); + struct otp_info *info; ssize_t size = 0; unsigned int i; size_t retlen; int ret; + info = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!info) + return -ENOMEM; + if (is_user) ret = mtd_get_user_prot_info(mtd, PAGE_SIZE, &retlen, info); else -- cgit v1.2.3-59-g8ed1b From 961965c45c706175b24227868b1c12d72775e446 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:29 +0200 Subject: mtd: rawnand: Add a helper to clarify the interface configuration Name it nand_interface_is_sdr() which will make even more sense when nand_interface_is_nvddr() will be introduced. Use it when relevant. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-2-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/atmel/nand-controller.c | 2 +- include/linux/mtd/rawnand.h | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index 8aab1017b460..53b3a11b9952 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -1246,7 +1246,7 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand, nc = to_nand_controller(nand->base.controller); /* DDR interface not supported. */ - if (conf->type != NAND_SDR_IFACE) + if (!nand_interface_is_sdr(conf)) return -ENOTSUPP; /* diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index 29df2f43dcb5..39b31f8e03b7 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -496,6 +496,15 @@ struct nand_interface_config { } timings; }; +/** + * nand_interface_is_sdr - get the interface type + * @conf: The data interface + */ +static bool nand_interface_is_sdr(const struct nand_interface_config *conf) +{ + return conf->type == NAND_SDR_IFACE; +} + /** * nand_get_sdr_timings - get SDR timing from data interface * @conf: The data interface @@ -503,7 +512,7 @@ struct nand_interface_config { static inline const struct nand_sdr_timings * nand_get_sdr_timings(const struct nand_interface_config *conf) { - if (conf->type != NAND_SDR_IFACE) + if (!nand_interface_is_sdr(conf)) return ERR_PTR(-EINVAL); return &conf->timings.sdr; -- cgit v1.2.3-59-g8ed1b From 4dd7ef970bee8a93e1817ec028a7e26aef046d0d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:30 +0200 Subject: mtd: rawnand: arasan: Check the proposed data interface is supported Check the data interface is supported in ->setup_interface() before acknowledging the timings. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-3-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 549aac00228e..65a52bb2731e 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -861,6 +861,11 @@ static int anfc_setup_interface(struct nand_chip *chip, int target, struct anand *anand = to_anand(chip); struct arasan_nfc *nfc = to_anfc(chip->controller); struct device_node *np = nfc->dev->of_node; + const struct nand_sdr_timings *sdr; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); if (target < 0) return 0; -- cgit v1.2.3-59-g8ed1b From ed2a491037116387f109e851a2b46adcb5feca3b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:31 +0200 Subject: mtd: rawnand: atmel: Check the proposed data interface is supported Check the data interface is supported in ->setup_interface() before acknowledging the timings. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-4-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/atmel/nand-controller.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index 53b3a11b9952..6e15f424b071 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -1524,8 +1524,13 @@ static int atmel_nand_setup_interface(struct nand_chip *chip, int csline, const struct nand_interface_config *conf) { struct atmel_nand *nand = to_atmel_nand(chip); + const struct nand_sdr_timings *sdr; struct atmel_nand_controller *nc; + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + nc = to_nand_controller(nand->base.controller); if (csline >= nand->numcs || -- cgit v1.2.3-59-g8ed1b From dbb7b2e07564443c2d357398e83e27c2fa5a89ed Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:34 +0200 Subject: mtd: rawnand: Use more recent ONFI specification wording In particular, first ONFI specifications referred to SDR modes as asynchronous modes, which is not the term we usually have in mind. The spec has then been updated, so do the same here in the NAND subsystem to avoid any possible confusion. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-7-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/cadence-nand-controller.c | 6 +++--- drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h | 2 +- drivers/mtd/nand/raw/nand_base.c | 2 +- drivers/mtd/nand/raw/nand_onfi.c | 2 +- include/linux/mtd/onfi.h | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index b46786cd53e0..7eec60ea9056 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -2348,9 +2348,9 @@ cadence_nand_setup_interface(struct nand_chip *chip, int chipnr, * for tRP and tRH timings. If it is NOT possible to sample data * with optimal tRP/tRH settings, the parameters will be extended. * If clk_period is 50ns (the lowest value) this condition is met - * for asynchronous timing modes 1, 2, 3, 4 and 5. - * If clk_period is 20ns the condition is met only - * for asynchronous timing mode 5. + * for SDR timing modes 1, 2, 3, 4 and 5. + * If clk_period is 20ns the condition is met only for SDR timing + * mode 5. */ if (sdr->tRC_min <= clk_period && sdr->tRP_min <= (clk_period / 2) && diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h index fdc5ed7de083..5e1c3ddae5f8 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h @@ -79,7 +79,7 @@ enum gpmi_type { struct gpmi_devdata { enum gpmi_type type; int bch_max_ecc_strength; - int max_chain_delay; /* See the async EDO mode */ + int max_chain_delay; /* See the SDR EDO mode */ const char * const *clks; const int clks_count; }; diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index e29e5b3d31bf..f4b287b706f8 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -936,7 +936,7 @@ int nand_choose_best_sdr_timings(struct nand_chip *chip, /* Fallback to slower modes */ best_mode = iface->timings.mode; } else if (chip->parameters.onfi) { - best_mode = fls(chip->parameters.onfi->async_timing_mode) - 1; + best_mode = fls(chip->parameters.onfi->sdr_timing_modes) - 1; } for (mode = best_mode; mode >= 0; mode--) { diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c index 45649e03797d..02303455c34f 100644 --- a/drivers/mtd/nand/raw/nand_onfi.c +++ b/drivers/mtd/nand/raw/nand_onfi.c @@ -315,7 +315,7 @@ int nand_onfi_detect(struct nand_chip *chip) onfi->tBERS = le16_to_cpu(p->t_bers); onfi->tR = le16_to_cpu(p->t_r); onfi->tCCS = le16_to_cpu(p->t_ccs); - onfi->async_timing_mode = le16_to_cpu(p->async_timing_mode); + onfi->sdr_timing_modes = le16_to_cpu(p->sdr_timing_modes); onfi->vendor_revision = le16_to_cpu(p->vendor_revision); memcpy(onfi->vendor, p->vendor, sizeof(p->vendor)); chip->parameters.onfi = onfi; diff --git a/include/linux/mtd/onfi.h b/include/linux/mtd/onfi.h index cf14474bc454..2ade5632dc5b 100644 --- a/include/linux/mtd/onfi.h +++ b/include/linux/mtd/onfi.h @@ -93,7 +93,7 @@ struct nand_onfi_params { /* electrical parameter block */ u8 io_pin_capacitance_max; - __le16 async_timing_mode; + __le16 sdr_timing_modes; __le16 program_cache_timing_mode; __le16 t_prog; __le16 t_bers; @@ -160,7 +160,7 @@ struct onfi_ext_param_page { * @tBERS: Block erase time * @tR: Page read time * @tCCS: Change column setup time - * @async_timing_mode: Supported asynchronous timing mode + * @sdr_timing_modes: Supported asynchronous/SDR timing modes * @vendor_revision: Vendor specific revision number * @vendor: Vendor specific data */ @@ -170,7 +170,7 @@ struct onfi_params { u16 tBERS; u16 tR; u16 tCCS; - u16 async_timing_mode; + u16 sdr_timing_modes; u16 vendor_revision; u8 vendor[88]; }; -- cgit v1.2.3-59-g8ed1b From 1666b815ad1a5b6373e950da5002ac46521a9b28 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:36 +0200 Subject: mtd: rawnand: Add NV-DDR timings Create the relevant ONFI NV-DDR timings structure and fill it with default values from the ONFI specification. Add the relevant structure entries and helpers. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-9-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_timings.c | 255 ++++++++++++++++++++++++++++++++++++ include/linux/mtd/rawnand.h | 112 ++++++++++++++++ 2 files changed, 367 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_timings.c b/drivers/mtd/nand/raw/nand_timings.c index 94d832646487..481b56d5f60d 100644 --- a/drivers/mtd/nand/raw/nand_timings.c +++ b/drivers/mtd/nand/raw/nand_timings.c @@ -292,6 +292,261 @@ static const struct nand_interface_config onfi_sdr_timings[] = { }, }; +static const struct nand_interface_config onfi_nvddr_timings[] = { + /* Mode 0 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 0, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 10000, + .tCALH_min = 10000, + .tCALS_min = 10000, + .tCAS_min = 10000, + .tCEH_min = 20000, + .tCH_min = 10000, + .tCK_min = 50000, + .tCS_min = 35000, + .tDH_min = 5000, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 5000, + .tDS_min = 5000, + .tDSC_min = 50000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 6000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 1 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 1, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 5000, + .tCALH_min = 5000, + .tCALS_min = 5000, + .tCAS_min = 5000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCK_min = 30000, + .tCS_min = 25000, + .tDH_min = 2500, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 2500, + .tDS_min = 3000, + .tDSC_min = 30000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 3000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 2 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 2, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 4000, + .tCALH_min = 4000, + .tCALS_min = 4000, + .tCAS_min = 4000, + .tCEH_min = 20000, + .tCH_min = 4000, + .tCK_min = 20000, + .tCS_min = 15000, + .tDH_min = 1700, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 1700, + .tDS_min = 2000, + .tDSC_min = 20000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 2000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 3 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 3, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 3000, + .tCALH_min = 3000, + .tCALS_min = 3000, + .tCAS_min = 3000, + .tCEH_min = 20000, + .tCH_min = 3000, + .tCK_min = 15000, + .tCS_min = 15000, + .tDH_min = 1300, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 1300, + .tDS_min = 1500, + .tDSC_min = 15000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 1500, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 4 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 4, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 2500, + .tCALH_min = 2500, + .tCALS_min = 2500, + .tCAS_min = 2500, + .tCEH_min = 20000, + .tCH_min = 2500, + .tCK_min = 12000, + .tCS_min = 15000, + .tDH_min = 1100, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 1000, + .tDS_min = 1100, + .tDSC_min = 12000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 1200, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 5 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 5, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 2000, + .tCALH_min = 2000, + .tCALS_min = 2000, + .tCAS_min = 2000, + .tCEH_min = 20000, + .tCH_min = 2000, + .tCK_min = 10000, + .tCS_min = 15000, + .tDH_min = 900, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 850, + .tDS_min = 900, + .tDSC_min = 10000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 1000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, +}; + /* All NAND chips share the same reset data interface: SDR mode 0 */ const struct nand_interface_config *nand_get_reset_interface_config(void) { diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index 24aee0af5421..a53a1543d1d4 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -471,12 +471,100 @@ struct nand_sdr_timings { u32 tWW_min; }; +/** + * struct nand_nvddr_timings - NV-DDR NAND chip timings + * + * This struct defines the timing requirements of a NV-DDR NAND data interface. + * These information can be found in every NAND datasheets and the timings + * meaning are described in the ONFI specifications: + * https://media-www.micron.com/-/media/client/onfi/specs/onfi_4_1_gold.pdf + * (chapter 4.18.2 NV-DDR) + * + * All these timings are expressed in picoseconds. + * + * @tBERS_max: Block erase time + * @tCCS_min: Change column setup time + * @tPROG_max: Page program time + * @tR_max: Page read time + * @tAC_min: Access window of DQ[7:0] from CLK + * @tAC_max: Access window of DQ[7:0] from CLK + * @tADL_min: ALE to data loading time + * @tCAD_min: Command, Address, Data delay + * @tCAH_min: Command/Address DQ hold time + * @tCALH_min: W/R_n, CLE and ALE hold time + * @tCALS_min: W/R_n, CLE and ALE setup time + * @tCAS_min: Command/address DQ setup time + * @tCEH_min: CE# high hold time + * @tCH_min: CE# hold time + * @tCK_min: Average clock cycle time + * @tCS_min: CE# setup time + * @tDH_min: Data hold time + * @tDQSCK_min: Start of the access window of DQS from CLK + * @tDQSCK_max: End of the access window of DQS from CLK + * @tDQSD_min: Min W/R_n low to DQS/DQ driven by device + * @tDQSD_max: Max W/R_n low to DQS/DQ driven by device + * @tDQSHZ_max: W/R_n high to DQS/DQ tri-state by device + * @tDQSQ_max: DQS-DQ skew, DQS to last DQ valid, per access + * @tDS_min: Data setup time + * @tDSC_min: DQS cycle time + * @tFEAT_max: Busy time for Set Features and Get Features + * @tITC_max: Interface and Timing Mode Change time + * @tQHS_max: Data hold skew factor + * @tRHW_min: Data output cycle to command, address, or data input cycle + * @tRR_min: Ready to RE# low (data only) + * @tRST_max: Device reset time, measured from the falling edge of R/B# to the + * rising edge of R/B#. + * @tWB_max: WE# high to SR[6] low + * @tWHR_min: WE# high to RE# low + * @tWRCK_min: W/R_n low to data output cycle + * @tWW_min: WP# transition to WE# low + */ +struct nand_nvddr_timings { + u64 tBERS_max; + u32 tCCS_min; + u64 tPROG_max; + u64 tR_max; + u32 tAC_min; + u32 tAC_max; + u32 tADL_min; + u32 tCAD_min; + u32 tCAH_min; + u32 tCALH_min; + u32 tCALS_min; + u32 tCAS_min; + u32 tCEH_min; + u32 tCH_min; + u32 tCK_min; + u32 tCS_min; + u32 tDH_min; + u32 tDQSCK_min; + u32 tDQSCK_max; + u32 tDQSD_min; + u32 tDQSD_max; + u32 tDQSHZ_max; + u32 tDQSQ_max; + u32 tDS_min; + u32 tDSC_min; + u32 tFEAT_max; + u32 tITC_max; + u32 tQHS_max; + u32 tRHW_min; + u32 tRR_min; + u32 tRST_max; + u32 tWB_max; + u32 tWHR_min; + u32 tWRCK_min; + u32 tWW_min; +}; + /** * enum nand_interface_type - NAND interface type * @NAND_SDR_IFACE: Single Data Rate interface + * @NAND_NVDDR_IFACE: Double Data Rate interface */ enum nand_interface_type { NAND_SDR_IFACE, + NAND_NVDDR_IFACE, }; /** @@ -485,6 +573,7 @@ enum nand_interface_type { * @timings: The timing information * @timings.mode: Timing mode as defined in the specification * @timings.sdr: Use it when @type is %NAND_SDR_IFACE. + * @timings.nvddr: Use it when @type is %NAND_NVDDR_IFACE. */ struct nand_interface_config { enum nand_interface_type type; @@ -492,6 +581,7 @@ struct nand_interface_config { unsigned int mode; union { struct nand_sdr_timings sdr; + struct nand_nvddr_timings nvddr; }; } timings; }; @@ -505,6 +595,15 @@ static bool nand_interface_is_sdr(const struct nand_interface_config *conf) return conf->type == NAND_SDR_IFACE; } +/** + * nand_interface_is_nvddr - get the interface type + * @conf: The data interface + */ +static bool nand_interface_is_nvddr(const struct nand_interface_config *conf) +{ + return conf->type == NAND_NVDDR_IFACE; +} + /** * nand_get_sdr_timings - get SDR timing from data interface * @conf: The data interface @@ -518,6 +617,19 @@ nand_get_sdr_timings(const struct nand_interface_config *conf) return &conf->timings.sdr; } +/** + * nand_get_nvddr_timings - get NV-DDR timing from data interface + * @conf: The data interface + */ +static inline const struct nand_nvddr_timings * +nand_get_nvddr_timings(const struct nand_interface_config *conf) +{ + if (!nand_interface_is_nvddr(conf)) + return ERR_PTR(-EINVAL); + + return &conf->timings.nvddr; +} + /** * struct nand_op_cmd_instr - Definition of a command instruction * @opcode: the command to issue in one cycle -- cgit v1.2.3-59-g8ed1b From 9310668fb60a7ee76c4fdfd6388747a6f2beaf75 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:37 +0200 Subject: mtd: rawnand: Retrieve NV-DDR timing modes from the ONFI parameter page When parsing the ONFI parameter page, save the available NV-DDR timing modes in the core's dynamic ONFI structure. Once available to the rest of the core out of the ONFI driver, these values will then be used to derive the best timing mode. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-10-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_onfi.c | 2 ++ include/linux/mtd/onfi.h | 3 +++ 2 files changed, 5 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c index 02303455c34f..f7a4a0573fe7 100644 --- a/drivers/mtd/nand/raw/nand_onfi.c +++ b/drivers/mtd/nand/raw/nand_onfi.c @@ -316,6 +316,8 @@ int nand_onfi_detect(struct nand_chip *chip) onfi->tR = le16_to_cpu(p->t_r); onfi->tCCS = le16_to_cpu(p->t_ccs); onfi->sdr_timing_modes = le16_to_cpu(p->sdr_timing_modes); + if (p->features & ONFI_FEATURE_NV_DDR) + onfi->nvddr_timing_modes = p->nvddr_timing_modes; onfi->vendor_revision = le16_to_cpu(p->vendor_revision); memcpy(onfi->vendor, p->vendor, sizeof(p->vendor)); chip->parameters.onfi = onfi; diff --git a/include/linux/mtd/onfi.h b/include/linux/mtd/onfi.h index 319e1736851d..14e66a49557e 100644 --- a/include/linux/mtd/onfi.h +++ b/include/linux/mtd/onfi.h @@ -25,6 +25,7 @@ /* ONFI features */ #define ONFI_FEATURE_16_BIT_BUS BIT(0) +#define ONFI_FEATURE_NV_DDR BIT(5) #define ONFI_FEATURE_EXT_PARAM_PAGE BIT(7) /* ONFI timing mode, used in both asynchronous and synchronous mode */ @@ -162,6 +163,7 @@ struct onfi_ext_param_page { * @tR: Page read time * @tCCS: Change column setup time * @sdr_timing_modes: Supported asynchronous/SDR timing modes + * @nvddr_timing_modes: Supported source synchronous/NV-DDR timing modes * @vendor_revision: Vendor specific revision number * @vendor: Vendor specific data */ @@ -172,6 +174,7 @@ struct onfi_params { u16 tR; u16 tCCS; u16 sdr_timing_modes; + u16 nvddr_timing_modes; u16 vendor_revision; u8 vendor[88]; }; -- cgit v1.2.3-59-g8ed1b From 94c8ce8e3e96f549ff16381e82974c0af012a7f8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:38 +0200 Subject: mtd: rawnand: Add an indirection on onfi_fill_interface_config() This helper actually fills the interface configuration with SDR data. As part of the work to bring NV-DDR support, let's rename this helper onfi_fill_sdr_interface_config() and add a generic indirection to it. There are no functional changes here, but this will simplify a next change which adds onfi_fill_nvddr_interface_config() support. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-11-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_timings.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_timings.c b/drivers/mtd/nand/raw/nand_timings.c index 481b56d5f60d..fb483e696cb9 100644 --- a/drivers/mtd/nand/raw/nand_timings.c +++ b/drivers/mtd/nand/raw/nand_timings.c @@ -601,23 +601,18 @@ onfi_find_closest_sdr_mode(const struct nand_sdr_timings *spec_timings) } /** - * onfi_fill_interface_config - Initialize an interface config from a given - * ONFI mode + * onfi_fill_sdr_interface_config - Initialize a SDR interface config from a + * given ONFI mode * @chip: The NAND chip * @iface: The interface configuration to fill - * @type: The interface type * @timing_mode: The ONFI timing mode */ -void onfi_fill_interface_config(struct nand_chip *chip, - struct nand_interface_config *iface, - enum nand_interface_type type, - unsigned int timing_mode) +static void onfi_fill_sdr_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + unsigned int timing_mode) { struct onfi_params *onfi = chip->parameters.onfi; - if (WARN_ON(type != NAND_SDR_IFACE)) - return; - if (WARN_ON(timing_mode >= ARRAY_SIZE(onfi_sdr_timings))) return; @@ -640,3 +635,20 @@ void onfi_fill_interface_config(struct nand_chip *chip, timings->tCCS_min = 1000UL * onfi->tCCS; } } + +/** + * onfi_fill_interface_config - Initialize an interface config from a given + * ONFI mode + * @chip: The NAND chip + * @iface: The interface configuration to fill + * @type: The interface type + * @timing_mode: The ONFI timing mode + */ +void onfi_fill_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + enum nand_interface_type type, + unsigned int timing_mode) +{ + if (type == NAND_SDR_IFACE) + return onfi_fill_sdr_interface_config(chip, iface, timing_mode); +} -- cgit v1.2.3-59-g8ed1b From 45606518f961b9e7adddc017e7813fa9f92b43fb Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:39 +0200 Subject: mtd: rawnand: Add onfi_fill_nvddr_interface_config() helper Same logic as for the SDR path, let's create a onfi_fill_nvddr_interface_config() helper to fill an interface configuration structure with NV-DDR timings, given a specific ONFI mode. There is one additional thing to do compared to SDR mode: tCAD timing can be fast or slow and this depends on an ONFI parameter page bit. By default the slow value is declared in the timings structure definition, but this helper can shrink it down if necessary. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-12-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_onfi.c | 1 + drivers/mtd/nand/raw/nand_timings.c | 41 +++++++++++++++++++++++++++++++++++++ include/linux/mtd/onfi.h | 2 ++ 3 files changed, 44 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c index f7a4a0573fe7..8e4677f2ba76 100644 --- a/drivers/mtd/nand/raw/nand_onfi.c +++ b/drivers/mtd/nand/raw/nand_onfi.c @@ -315,6 +315,7 @@ int nand_onfi_detect(struct nand_chip *chip) onfi->tBERS = le16_to_cpu(p->t_bers); onfi->tR = le16_to_cpu(p->t_r); onfi->tCCS = le16_to_cpu(p->t_ccs); + onfi->fast_tCAD = p->nvddr_nvddr2_features & BIT(0); onfi->sdr_timing_modes = le16_to_cpu(p->sdr_timing_modes); if (p->features & ONFI_FEATURE_NV_DDR) onfi->nvddr_timing_modes = p->nvddr_timing_modes; diff --git a/drivers/mtd/nand/raw/nand_timings.c b/drivers/mtd/nand/raw/nand_timings.c index fb483e696cb9..8eaa132c3900 100644 --- a/drivers/mtd/nand/raw/nand_timings.c +++ b/drivers/mtd/nand/raw/nand_timings.c @@ -636,6 +636,45 @@ static void onfi_fill_sdr_interface_config(struct nand_chip *chip, } } +/** + * onfi_fill_nvddr_interface_config - Initialize a NVDDR interface config from a + * given ONFI mode + * @chip: The NAND chip + * @iface: The interface configuration to fill + * @timing_mode: The ONFI timing mode + */ +static void onfi_fill_nvddr_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + unsigned int timing_mode) +{ + struct onfi_params *onfi = chip->parameters.onfi; + + if (WARN_ON(timing_mode >= ARRAY_SIZE(onfi_nvddr_timings))) + return; + + *iface = onfi_nvddr_timings[timing_mode]; + + /* + * Initialize timings that cannot be deduced from timing mode: + * tPROG, tBERS, tR, tCCS and tCAD. + * These information are part of the ONFI parameter page. + */ + if (onfi) { + struct nand_nvddr_timings *timings = &iface->timings.nvddr; + + /* microseconds -> picoseconds */ + timings->tPROG_max = 1000000ULL * onfi->tPROG; + timings->tBERS_max = 1000000ULL * onfi->tBERS; + timings->tR_max = 1000000ULL * onfi->tR; + + /* nanoseconds -> picoseconds */ + timings->tCCS_min = 1000UL * onfi->tCCS; + + if (onfi->fast_tCAD) + timings->tCAD_min = 25000; + } +} + /** * onfi_fill_interface_config - Initialize an interface config from a given * ONFI mode @@ -651,4 +690,6 @@ void onfi_fill_interface_config(struct nand_chip *chip, { if (type == NAND_SDR_IFACE) return onfi_fill_sdr_interface_config(chip, iface, timing_mode); + else + return onfi_fill_nvddr_interface_config(chip, iface, timing_mode); } diff --git a/include/linux/mtd/onfi.h b/include/linux/mtd/onfi.h index 14e66a49557e..a9677bf1e47e 100644 --- a/include/linux/mtd/onfi.h +++ b/include/linux/mtd/onfi.h @@ -162,6 +162,7 @@ struct onfi_ext_param_page { * @tBERS: Block erase time * @tR: Page read time * @tCCS: Change column setup time + * @fast_tCAD: Command/Address/Data slow or fast delay (NV-DDR only) * @sdr_timing_modes: Supported asynchronous/SDR timing modes * @nvddr_timing_modes: Supported source synchronous/NV-DDR timing modes * @vendor_revision: Vendor specific revision number @@ -173,6 +174,7 @@ struct onfi_params { u16 tBERS; u16 tR; u16 tCCS; + bool fast_tCAD; u16 sdr_timing_modes; u16 nvddr_timing_modes; u16 vendor_revision; -- cgit v1.2.3-59-g8ed1b From fee9c6d8f098f7054f97ec1dbcfb42a2a3238f23 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:40 +0200 Subject: mtd: rawnand: Avoid accessing NV-DDR timings from legacy code Legacy code should not benefit from newer features, especially in helpers that have been deprecated for a very long time. People who want NV-DDR support must migrate their driver to the ->exec_op() API. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-13-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_legacy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_legacy.c b/drivers/mtd/nand/raw/nand_legacy.c index eccc18b266d5..743792edf98d 100644 --- a/drivers/mtd/nand/raw/nand_legacy.c +++ b/drivers/mtd/nand/raw/nand_legacy.c @@ -369,7 +369,7 @@ static void nand_ccs_delay(struct nand_chip *chip) * Wait tCCS_min if it is correctly defined, otherwise wait 500ns * (which should be safe for all NANDs). */ - if (nand_controller_can_setup_interface(chip)) + if (!IS_ERR(sdr) && nand_controller_can_setup_interface(chip)) ndelay(sdr->tCCS_min / 1000); else ndelay(500); -- cgit v1.2.3-59-g8ed1b From d7a773e8812bcf7a5412e4baebc6eb1c11242551 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:41 +0200 Subject: mtd: rawnand: Access SDR and NV-DDR timings through a common macro Most timings related to the bus timings are different between SDR and NV-DDR. However, we identified 9 individual timings which are more related to the NAND chip internals. These are common between the two interface types. Fortunately, only these common timings are being shared through the NAND core and its ->exec_op() interface, which allows the writing of a simple macro checking the interface type and depending on it, returning either the relevant SDR timing or the NV-DDR timing. This is the purpose of the NAND_COMMON_TIMING_PS() macro. As all this is evaluated at build time, one will immediately be notified in case a non common timing is being accessed through this macro. Two handy macros are also inserted at the same time, which use PSEC_TO_NSEC or PSEC_TO_MSEC so that it is very easy to return timings in milli-, nano- or pico-seconds, as usually requested by the internal API. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-14-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_base.c | 131 +++++++++++++++++++++------------------ include/linux/mtd/rawnand.h | 28 +++++++++ 2 files changed, 99 insertions(+), 60 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index f4b287b706f8..cdb3ddb34cd6 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -647,7 +647,7 @@ static int nand_block_checkbad(struct nand_chip *chip, loff_t ofs, int allowbbt) */ int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms) { - const struct nand_sdr_timings *timings; + const struct nand_interface_config *conf; u8 status = 0; int ret; @@ -655,8 +655,8 @@ int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms) return -ENOTSUPP; /* Wait tWB before polling the STATUS reg. */ - timings = nand_get_sdr_timings(nand_get_interface_config(chip)); - ndelay(PSEC_TO_NSEC(timings->tWB_max)); + conf = nand_get_interface_config(chip); + ndelay(NAND_COMMON_TIMING_NS(conf, tWB_max)); ret = nand_status_op(chip, NULL); if (ret) @@ -1047,15 +1047,15 @@ static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page, unsigned int offset_in_page, void *buf, unsigned int len) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct mtd_info *mtd = nand_to_mtd(chip); u8 addrs[4]; struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_READ0, 0), - NAND_OP_ADDR(3, addrs, PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tR_max), - PSEC_TO_NSEC(sdr->tRR_min)), + NAND_OP_ADDR(3, addrs, NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), NAND_OP_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1090,15 +1090,15 @@ static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page, unsigned int offset_in_page, void *buf, unsigned int len) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); u8 addrs[5]; struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_READ0, 0), NAND_OP_ADDR(4, addrs, 0), - NAND_OP_CMD(NAND_CMD_READSTART, PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tR_max), - PSEC_TO_NSEC(sdr->tRR_min)), + NAND_OP_CMD(NAND_CMD_READSTART, NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), NAND_OP_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1187,13 +1187,14 @@ int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf, return -EINVAL; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_PARAM, 0), - NAND_OP_ADDR(1, &page, PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tR_max), - PSEC_TO_NSEC(sdr->tRR_min)), + NAND_OP_ADDR(1, &page, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), NAND_OP_8BIT_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1242,14 +1243,14 @@ int nand_change_read_column_op(struct nand_chip *chip, return -ENOTSUPP; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); u8 addrs[2] = {}; struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_RNDOUT, 0), NAND_OP_ADDR(2, addrs, 0), NAND_OP_CMD(NAND_CMD_RNDOUTSTART, - PSEC_TO_NSEC(sdr->tCCS_min)), + NAND_COMMON_TIMING_NS(conf, tCCS_min)), NAND_OP_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1317,8 +1318,8 @@ static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page, unsigned int offset_in_page, const void *buf, unsigned int len, bool prog) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct mtd_info *mtd = nand_to_mtd(chip); u8 addrs[5] = {}; struct nand_op_instr instrs[] = { @@ -1329,10 +1330,11 @@ static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page, */ NAND_OP_CMD(NAND_CMD_READ0, 0), NAND_OP_CMD(NAND_CMD_SEQIN, 0), - NAND_OP_ADDR(0, addrs, PSEC_TO_NSEC(sdr->tADL_min)), + NAND_OP_ADDR(0, addrs, NAND_COMMON_TIMING_NS(conf, tADL_min)), NAND_OP_DATA_OUT(len, buf, 0), - NAND_OP_CMD(NAND_CMD_PAGEPROG, PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tPROG_max), 0), + NAND_OP_CMD(NAND_CMD_PAGEPROG, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tPROG_max), 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); int naddrs = nand_fill_column_cycles(chip, addrs, offset_in_page); @@ -1431,12 +1433,13 @@ int nand_prog_page_end_op(struct nand_chip *chip) u8 status; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_PAGEPROG, - PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tPROG_max), 0), + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tPROG_max), + 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1549,12 +1552,12 @@ int nand_change_write_column_op(struct nand_chip *chip, return -ENOTSUPP; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); u8 addrs[2]; struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_RNDIN, 0), - NAND_OP_ADDR(2, addrs, PSEC_TO_NSEC(sdr->tCCS_min)), + NAND_OP_ADDR(2, addrs, NAND_COMMON_TIMING_NS(conf, tCCS_min)), NAND_OP_DATA_OUT(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1604,11 +1607,12 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf, return -EINVAL; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_READID, 0), - NAND_OP_ADDR(1, &addr, PSEC_TO_NSEC(sdr->tADL_min)), + NAND_OP_ADDR(1, &addr, + NAND_COMMON_TIMING_NS(conf, tADL_min)), NAND_OP_8BIT_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1643,11 +1647,11 @@ EXPORT_SYMBOL_GPL(nand_readid_op); int nand_status_op(struct nand_chip *chip, u8 *status) { if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_STATUS, - PSEC_TO_NSEC(sdr->tADL_min)), + NAND_COMMON_TIMING_NS(conf, tADL_min)), NAND_OP_8BIT_DATA_IN(1, status, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1712,15 +1716,16 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock) u8 status; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); u8 addrs[3] = { page, page >> 8, page >> 16 }; struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_ERASE1, 0), NAND_OP_ADDR(2, addrs, 0), NAND_OP_CMD(NAND_CMD_ERASE2, - PSEC_TO_MSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tBERS_max), 0), + NAND_COMMON_TIMING_MS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tBERS_max), + 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1771,14 +1776,17 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature, int i, ret; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0), - NAND_OP_ADDR(1, &feature, PSEC_TO_NSEC(sdr->tADL_min)), + NAND_OP_ADDR(1, &feature, NAND_COMMON_TIMING_NS(conf, + tADL_min)), NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data, - PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tFEAT_max), 0), + NAND_COMMON_TIMING_NS(conf, + tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tFEAT_max), + 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -1818,13 +1826,14 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature, int i; if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0), - NAND_OP_ADDR(1, &feature, PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tFEAT_max), - PSEC_TO_NSEC(sdr->tRR_min)), + NAND_OP_ADDR(1, &feature, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tFEAT_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN, data, 0), }; @@ -1875,11 +1884,13 @@ static int nand_wait_rdy_op(struct nand_chip *chip, unsigned int timeout_ms, int nand_reset_op(struct nand_chip *chip) { if (nand_has_exec_op(chip)) { - const struct nand_sdr_timings *sdr = - nand_get_sdr_timings(nand_get_interface_config(chip)); + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { - NAND_OP_CMD(NAND_CMD_RESET, PSEC_TO_NSEC(sdr->tWB_max)), - NAND_OP_WAIT_RDY(PSEC_TO_MSEC(sdr->tRST_max), 0), + NAND_OP_CMD(NAND_CMD_RESET, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tRST_max), + 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); @@ -3137,13 +3148,13 @@ static int nand_setup_read_retry(struct nand_chip *chip, int retry_mode) static void nand_wait_readrdy(struct nand_chip *chip) { - const struct nand_sdr_timings *sdr; + const struct nand_interface_config *conf; if (!(chip->options & NAND_NEED_READRDY)) return; - sdr = nand_get_sdr_timings(nand_get_interface_config(chip)); - WARN_ON(nand_wait_rdy_op(chip, PSEC_TO_MSEC(sdr->tR_max), 0)); + conf = nand_get_interface_config(chip); + WARN_ON(nand_wait_rdy_op(chip, NAND_COMMON_TIMING_MS(conf, tR_max), 0)); } /** diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index a53a1543d1d4..89b9c52c7387 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -557,6 +557,34 @@ struct nand_nvddr_timings { u32 tWW_min; }; +/* + * While timings related to the data interface itself are mostly different + * between SDR and NV-DDR, timings related to the internal chip behavior are + * common. IOW, the following entries which describe the internal delays have + * the same definition and are shared in both SDR and NV-DDR timing structures: + * - tADL_min + * - tBERS_max + * - tCCS_min + * - tFEAT_max + * - tPROG_max + * - tR_max + * - tRR_min + * - tRST_max + * - tWB_max + * + * The below macros return the value of a given timing, no matter the interface. + */ +#define NAND_COMMON_TIMING_PS(conf, timing_name) \ + nand_interface_is_sdr(conf) ? \ + nand_get_sdr_timings(conf)->timing_name : \ + nand_get_nvddr_timings(conf)->timing_name + +#define NAND_COMMON_TIMING_MS(conf, timing_name) \ + PSEC_TO_MSEC(NAND_COMMON_TIMING_PS((conf), timing_name)) + +#define NAND_COMMON_TIMING_NS(conf, timing_name) \ + PSEC_TO_NSEC(NAND_COMMON_TIMING_PS((conf), timing_name)) + /** * enum nand_interface_type - NAND interface type * @NAND_SDR_IFACE: Single Data Rate interface -- cgit v1.2.3-59-g8ed1b From 09cdb237c3c8773921a7c265ce1cdd23818a3f14 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:42 +0200 Subject: mtd: rawnand: Handle the double bytes in NV-DDR mode As explained in chapter "NV-DDR / NV-DDR2 / NV-DDR3 and Repeat Bytes" of the ONFI specification, with some commands (mainly the commands which do not transfer actual data) the data bytes are repeated twice and it is the responsibility of the receiver to discard them properly. The concerned commands are: SET_FEATURES, READ_ID, GET_FEATURES, READ_STATUS, READ_STATUS_ENHANCED, ODT_CONFIGURE. Hence, in the NAND core we are only impacted by the implementation of READ_ID, GET_FEATURES and READ_STATUS. The logic is the same for all: 2/ Check if it is relevant to read all data bytes twice. 1/ Allocate a buffer with twice the requested size (may be done statically). 2/ Update the instruction structure to read these extra bytes in the allocated buffer. 3/ Copy the even bytes into the original buffer. The performance hit is negligible on such small data transfers anyway and we don't really care about performances at this stage anyway. 4/ Free the allocated buffer, if any. Note: nand_data_read_op() is also impacted because it is theoretically possible to run the command/address cycles first, and, as another operation, do the data transfers. In this case we can easily identify the impacted commands because the force_8bit flag will be set (due to the same reason: their data does not go through the same pipeline). Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-15-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_base.c | 93 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 8 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index cdb3ddb34cd6..b8a515a74379 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -1601,7 +1601,7 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf, unsigned int len) { unsigned int i; - u8 *id = buf; + u8 *id = buf, *ddrbuf = NULL; if (len && !buf) return -EINVAL; @@ -1616,12 +1616,31 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf, NAND_OP_8BIT_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* READ_ID data bytes are received twice in NV-DDR mode */ + if (len && nand_interface_is_nvddr(conf)) { + ddrbuf = kzalloc(len * 2, GFP_KERNEL); + if (!ddrbuf) + return -ENOMEM; + + instrs[2].ctx.data.len *= 2; + instrs[2].ctx.data.buf.in = ddrbuf; + } /* Drop the DATA_IN instruction if len is set to 0. */ if (!len) op.ninstrs--; - return nand_exec_op(chip, &op); + ret = nand_exec_op(chip, &op); + if (!ret && len && nand_interface_is_nvddr(conf)) { + for (i = 0; i < len; i++) + id[i] = ddrbuf[i * 2]; + } + + kfree(ddrbuf); + + return ret; } chip->legacy.cmdfunc(chip, NAND_CMD_READID, addr, -1); @@ -1649,17 +1668,29 @@ int nand_status_op(struct nand_chip *chip, u8 *status) if (nand_has_exec_op(chip)) { const struct nand_interface_config *conf = nand_get_interface_config(chip); + u8 ddrstatus[2]; struct nand_op_instr instrs[] = { NAND_OP_CMD(NAND_CMD_STATUS, NAND_COMMON_TIMING_NS(conf, tADL_min)), NAND_OP_8BIT_DATA_IN(1, status, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* The status data byte will be received twice in NV-DDR mode */ + if (status && nand_interface_is_nvddr(conf)) { + instrs[1].ctx.data.len *= 2; + instrs[1].ctx.data.buf.in = ddrstatus; + } if (!status) op.ninstrs--; - return nand_exec_op(chip, &op); + ret = nand_exec_op(chip, &op); + if (!ret && status && nand_interface_is_nvddr(conf)) + *status = ddrstatus[0]; + + return ret; } chip->legacy.cmdfunc(chip, NAND_CMD_STATUS, -1, -1); @@ -1822,7 +1853,7 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature, static int nand_get_features_op(struct nand_chip *chip, u8 feature, void *data) { - u8 *params = data; + u8 *params = data, ddrbuf[ONFI_SUBFEATURE_PARAM_LEN * 2]; int i; if (nand_has_exec_op(chip)) { @@ -1838,8 +1869,21 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature, data, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; - return nand_exec_op(chip, &op); + /* GET_FEATURE data bytes are received twice in NV-DDR mode */ + if (nand_interface_is_nvddr(conf)) { + instrs[3].ctx.data.len *= 2; + instrs[3].ctx.data.buf.in = ddrbuf; + } + + ret = nand_exec_op(chip, &op); + if (nand_interface_is_nvddr(conf)) { + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; i++) + params[i] = ddrbuf[i * 2]; + } + + return ret; } chip->legacy.cmdfunc(chip, NAND_CMD_GET_FEATURES, feature, -1); @@ -1925,17 +1969,50 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len, return -EINVAL; if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); struct nand_op_instr instrs[] = { NAND_OP_DATA_IN(len, buf, 0), }; struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + u8 *ddrbuf = NULL; + int ret, i; instrs[0].ctx.data.force_8bit = force_8bit; - if (check_only) - return nand_check_op(chip, &op); + /* + * Parameter payloads (ID, status, features, etc) do not go + * through the same pipeline as regular data, hence the + * force_8bit flag must be set and this also indicates that in + * case NV-DDR timings are being used the data will be received + * twice. + */ + if (force_8bit && nand_interface_is_nvddr(conf)) { + ddrbuf = kzalloc(len * 2, GFP_KERNEL); + if (!ddrbuf) + return -ENOMEM; - return nand_exec_op(chip, &op); + instrs[0].ctx.data.len *= 2; + instrs[0].ctx.data.buf.in = ddrbuf; + } + + if (check_only) { + ret = nand_check_op(chip, &op); + kfree(ddrbuf); + return ret; + } + + ret = nand_exec_op(chip, &op); + if (!ret && force_8bit && nand_interface_is_nvddr(conf)) { + u8 *dst = buf; + + for (i = 0; i < len; i++) + dst[i] = ddrbuf[i * 2]; + } + + kfree(ddrbuf); + + return ret; } if (check_only) -- cgit v1.2.3-59-g8ed1b From e32df79f19c756f072bd517ece65df1efd496f95 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:43 +0200 Subject: mtd: rawnand: Add a helper to find the closest ONFI NV-DDR mode Introduce a similar helper to onfi_find_closest_sdr_mode(), but for NV-DDR timings. It just takes a timing structure as parameter and returns the closest mode by comparing all minimum timings. This is useful for rigid controllers on which tuning the timings is not possible. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-16-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/internals.h | 2 ++ drivers/mtd/nand/raw/nand_timings.c | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h index 012876e14317..c06bc2f1aaa2 100644 --- a/drivers/mtd/nand/raw/internals.h +++ b/drivers/mtd/nand/raw/internals.h @@ -90,6 +90,8 @@ void onfi_fill_interface_config(struct nand_chip *chip, unsigned int timing_mode); unsigned int onfi_find_closest_sdr_mode(const struct nand_sdr_timings *spec_timings); +unsigned int +onfi_find_closest_nvddr_mode(const struct nand_nvddr_timings *spec_timings); int nand_choose_best_sdr_timings(struct nand_chip *chip, struct nand_interface_config *iface, struct nand_sdr_timings *spec_timings); diff --git a/drivers/mtd/nand/raw/nand_timings.c b/drivers/mtd/nand/raw/nand_timings.c index 8eaa132c3900..7b41afc372d2 100644 --- a/drivers/mtd/nand/raw/nand_timings.c +++ b/drivers/mtd/nand/raw/nand_timings.c @@ -601,6 +601,48 @@ onfi_find_closest_sdr_mode(const struct nand_sdr_timings *spec_timings) } /** + * onfi_find_closest_nvddr_mode - Derive the closest ONFI NVDDR timing mode + * given a set of timings + * @spec_timings: the timings to challenge + */ +unsigned int +onfi_find_closest_nvddr_mode(const struct nand_nvddr_timings *spec_timings) +{ + const struct nand_nvddr_timings *onfi_timings; + int mode; + + for (mode = ARRAY_SIZE(onfi_nvddr_timings) - 1; mode > 0; mode--) { + onfi_timings = &onfi_nvddr_timings[mode].timings.nvddr; + + if (spec_timings->tCCS_min <= onfi_timings->tCCS_min && + spec_timings->tAC_min <= onfi_timings->tAC_min && + spec_timings->tADL_min <= onfi_timings->tADL_min && + spec_timings->tCAD_min <= onfi_timings->tCAD_min && + spec_timings->tCAH_min <= onfi_timings->tCAH_min && + spec_timings->tCALH_min <= onfi_timings->tCALH_min && + spec_timings->tCALS_min <= onfi_timings->tCALS_min && + spec_timings->tCAS_min <= onfi_timings->tCAS_min && + spec_timings->tCEH_min <= onfi_timings->tCEH_min && + spec_timings->tCH_min <= onfi_timings->tCH_min && + spec_timings->tCK_min <= onfi_timings->tCK_min && + spec_timings->tCS_min <= onfi_timings->tCS_min && + spec_timings->tDH_min <= onfi_timings->tDH_min && + spec_timings->tDQSCK_min <= onfi_timings->tDQSCK_min && + spec_timings->tDQSD_min <= onfi_timings->tDQSD_min && + spec_timings->tDS_min <= onfi_timings->tDS_min && + spec_timings->tDSC_min <= onfi_timings->tDSC_min && + spec_timings->tRHW_min <= onfi_timings->tRHW_min && + spec_timings->tRR_min <= onfi_timings->tRR_min && + spec_timings->tWHR_min <= onfi_timings->tWHR_min && + spec_timings->tWRCK_min <= onfi_timings->tWRCK_min && + spec_timings->tWW_min <= onfi_timings->tWW_min) + return mode; + } + + return 0; +} + +/* * onfi_fill_sdr_interface_config - Initialize a SDR interface config from a * given ONFI mode * @chip: The NAND chip -- cgit v1.2.3-59-g8ed1b From f3fe156ede6db96a060cc98ff1bce1ee6417a68b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:44 +0200 Subject: mtd: rawnand: Support enabling NV-DDR through SET_FEATURES Until now the parameter of the ADDR_TIMING_MODE feature was just the ONFI timing mode (from 0 to 5) because we were only supporting the SDR data interface. In the same byte, bits 4 and 5 indicate which data interface is being configured so use them to set the right mode and also read them back to ensure the right timing has been setup on the chip's side. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-17-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_base.c | 18 +++++++++++++----- include/linux/mtd/onfi.h | 5 +++++ 2 files changed, 18 insertions(+), 5 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index b8a515a74379..cfe8257bf175 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -832,7 +832,7 @@ static int nand_reset_interface(struct nand_chip *chip, int chipnr) static int nand_setup_interface(struct nand_chip *chip, int chipnr) { const struct nand_controller_ops *ops = chip->controller->ops; - u8 tmode_param[ONFI_SUBFEATURE_PARAM_LEN] = { }; + u8 tmode_param[ONFI_SUBFEATURE_PARAM_LEN] = { }, request; int ret; if (!nand_controller_can_setup_interface(chip)) @@ -848,7 +848,12 @@ static int nand_setup_interface(struct nand_chip *chip, int chipnr) if (!chip->best_interface_config) return 0; - tmode_param[0] = chip->best_interface_config->timings.mode; + request = chip->best_interface_config->timings.mode; + if (nand_interface_is_sdr(chip->best_interface_config)) + request |= ONFI_DATA_INTERFACE_SDR; + else + request |= ONFI_DATA_INTERFACE_NVDDR; + tmode_param[0] = request; /* Change the mode on the chip side (if supported by the NAND chip) */ if (nand_supports_set_features(chip, ONFI_FEATURE_ADDR_TIMING_MODE)) { @@ -877,10 +882,13 @@ static int nand_setup_interface(struct nand_chip *chip, int chipnr) if (ret) goto err_reset_chip; - if (tmode_param[0] != chip->best_interface_config->timings.mode) { - pr_warn("timing mode %d not acknowledged by the NAND chip\n", + if (request != tmode_param[0]) { + pr_warn("%s timing mode %d not acknowledged by the NAND chip\n", + nand_interface_is_nvddr(chip->best_interface_config) ? "NV-DDR" : "SDR", chip->best_interface_config->timings.mode); - ret = 0; + pr_debug("NAND chip would work in %s timing mode %d\n", + tmode_param[0] & ONFI_DATA_INTERFACE_NVDDR ? "NV-DDR" : "SDR", + (unsigned int)ONFI_TIMING_MODE_PARAM(tmode_param[0])); goto err_reset_chip; } diff --git a/include/linux/mtd/onfi.h b/include/linux/mtd/onfi.h index a9677bf1e47e..a7376f9beddf 100644 --- a/include/linux/mtd/onfi.h +++ b/include/linux/mtd/onfi.h @@ -11,6 +11,7 @@ #define __LINUX_MTD_ONFI_H #include +#include /* ONFI version bits */ #define ONFI_VERSION_1_0 BIT(1) @@ -29,6 +30,9 @@ #define ONFI_FEATURE_EXT_PARAM_PAGE BIT(7) /* ONFI timing mode, used in both asynchronous and synchronous mode */ +#define ONFI_DATA_INTERFACE_SDR 0 +#define ONFI_DATA_INTERFACE_NVDDR BIT(4) +#define ONFI_DATA_INTERFACE_NVDDR2 BIT(5) #define ONFI_TIMING_MODE_0 BIT(0) #define ONFI_TIMING_MODE_1 BIT(1) #define ONFI_TIMING_MODE_2 BIT(2) @@ -36,6 +40,7 @@ #define ONFI_TIMING_MODE_4 BIT(4) #define ONFI_TIMING_MODE_5 BIT(5) #define ONFI_TIMING_MODE_UNKNOWN BIT(6) +#define ONFI_TIMING_MODE_PARAM(x) FIELD_GET(GENMASK(3, 0), (x)) /* ONFI feature number/address */ #define ONFI_FEATURE_NUMBER 256 -- cgit v1.2.3-59-g8ed1b From 9d3194bf2aef81c04177ab6bbe50406aa8d550dc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:45 +0200 Subject: mtd: rawnand: Allow SDR timings to be nacked This should never happen in theory and is probably a controller driver bug. Anyway it's probably better to bail out at this point if this happens rather than continuing the boot process. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-18-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_base.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index cfe8257bf175..e20551cb3ce5 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -952,13 +952,13 @@ int nand_choose_best_sdr_timings(struct nand_chip *chip, ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, iface); - if (!ret) + if (!ret) { + chip->best_interface_config = iface; break; + } } - chip->best_interface_config = iface; - - return 0; + return ret; } /** -- cgit v1.2.3-59-g8ed1b From a9ecc8c814e9600836e00cb592f1cb5378393126 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:46 +0200 Subject: mtd: rawnand: Choose the best timings, NV-DDR included Now that the necessary peaces to support the NV-DDR interface type have been contributed, let's add the relevant logic to make use of it. In particular, the core does not choose the best SDR timings anymore but calls a more generic helper instead. This helper checks if NV-DDR is supported by trying to find the best NV-DDR supported mode through a logic very close to what is being done for SDR timings. If no NV-DDR mode in common between the NAND controller and the NAND chip is found, the core will fallback to SDR. Side note: theoretically, the data clock speed in NV-DDR mode 0 is slower than in SDR mode 5. In the situation where we would get a working NV-DDR mode 0, we could also try if SDR mode 5 is supported and eventually fallback to it in order to get the fastest possible throughput. However, in the field, it looks like most of the devices supporting NV-DDR avoid implementing the fastest SDR modes (like 4 and 5 EDO modes, which are a bit more complicated to handle than the other SDR modes). So, we will stick to the simplest logic: try NV-DDR otherwise fallback to SDR. If someone else experiences strong differences because of that we may still implement the logic defined above. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-19-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/internals.h | 3 ++ drivers/mtd/nand/raw/nand_base.c | 76 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h index c06bc2f1aaa2..7016e0f38398 100644 --- a/drivers/mtd/nand/raw/internals.h +++ b/drivers/mtd/nand/raw/internals.h @@ -95,6 +95,9 @@ onfi_find_closest_nvddr_mode(const struct nand_nvddr_timings *spec_timings); int nand_choose_best_sdr_timings(struct nand_chip *chip, struct nand_interface_config *iface, struct nand_sdr_timings *spec_timings); +int nand_choose_best_nvddr_timings(struct nand_chip *chip, + struct nand_interface_config *iface, + struct nand_nvddr_timings *spec_timings); const struct nand_interface_config *nand_get_reset_interface_config(void); int nand_get_features(struct nand_chip *chip, int addr, u8 *subfeature_param); int nand_set_features(struct nand_chip *chip, int addr, u8 *subfeature_param); diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index e20551cb3ce5..22974a53077f 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -961,6 +961,80 @@ int nand_choose_best_sdr_timings(struct nand_chip *chip, return ret; } +/** + * nand_choose_best_nvddr_timings - Pick up the best NVDDR timings that both the + * NAND controller and the NAND chip support + * @chip: the NAND chip + * @iface: the interface configuration (can eventually be updated) + * @spec_timings: specific timings, when not fitting the ONFI specification + * + * If specific timings are provided, use them. Otherwise, retrieve supported + * timing modes from ONFI information. + */ +int nand_choose_best_nvddr_timings(struct nand_chip *chip, + struct nand_interface_config *iface, + struct nand_nvddr_timings *spec_timings) +{ + const struct nand_controller_ops *ops = chip->controller->ops; + int best_mode = 0, mode, ret; + + iface->type = NAND_NVDDR_IFACE; + + if (spec_timings) { + iface->timings.nvddr = *spec_timings; + iface->timings.mode = onfi_find_closest_nvddr_mode(spec_timings); + + /* Verify the controller supports the requested interface */ + ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, + iface); + if (!ret) { + chip->best_interface_config = iface; + return ret; + } + + /* Fallback to slower modes */ + best_mode = iface->timings.mode; + } else if (chip->parameters.onfi) { + best_mode = fls(chip->parameters.onfi->nvddr_timing_modes) - 1; + } + + for (mode = best_mode; mode >= 0; mode--) { + onfi_fill_interface_config(chip, iface, NAND_NVDDR_IFACE, mode); + + ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, + iface); + if (!ret) { + chip->best_interface_config = iface; + break; + } + } + + return ret; +} + +/** + * nand_choose_best_timings - Pick up the best NVDDR or SDR timings that both + * NAND controller and the NAND chip support + * @chip: the NAND chip + * @iface: the interface configuration (can eventually be updated) + * + * If specific timings are provided, use them. Otherwise, retrieve supported + * timing modes from ONFI information. + */ +static int nand_choose_best_timings(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + int ret; + + /* Try the fastest timings: NV-DDR */ + ret = nand_choose_best_nvddr_timings(chip, iface, NULL); + if (!ret) + return 0; + + /* Fallback to SDR timings otherwise */ + return nand_choose_best_sdr_timings(chip, iface, NULL); +} + /** * nand_choose_interface_config - find the best data interface and timings * @chip: The NAND chip @@ -989,7 +1063,7 @@ static int nand_choose_interface_config(struct nand_chip *chip) if (chip->ops.choose_interface_config) ret = chip->ops.choose_interface_config(chip, iface); else - ret = nand_choose_best_sdr_timings(chip, iface, NULL); + ret = nand_choose_best_timings(chip, iface); if (ret) kfree(iface); -- cgit v1.2.3-59-g8ed1b From 698ddeb89e01840dec05ffdb538468782e641a56 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:48 +0200 Subject: mtd: rawnand: arasan: Fix a macro parameter This macro is not yet being used so the compilers never complained about it. Fix the macro before using it. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-21-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 65a52bb2731e..d5fab3cbaa54 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -91,7 +91,7 @@ #define DATA_INTERFACE_REG 0x6C #define DIFACE_SDR_MODE(x) FIELD_PREP(GENMASK(2, 0), (x)) -#define DIFACE_DDR_MODE(x) FIELD_PREP(GENMASK(5, 3), (X)) +#define DIFACE_DDR_MODE(x) FIELD_PREP(GENMASK(5, 3), (x)) #define DIFACE_SDR 0 #define DIFACE_NVDDR BIT(9) -- cgit v1.2.3-59-g8ed1b From 10938a08fc4055667da7518685fbd8ea7d09de1b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:49 +0200 Subject: mtd: rawnand: arasan: Workaround a misbehaving prog type with NV-DDR As explained in the comment introduced above the fix, the Arasan controller driver starts an operation when the prog register is being written with a "type" specific to the action to perform. The prog type used until now to perform a CHANGE READ COLUMN with an SDR interface was the PAGE READ type (CMD + ADDR + CMD + DATA). Unfortunately, for an unknown reason (let's call this a silicon bug) any CHANGE READ COLUMN performed this way in NV-DDR mode will fail: the data ready flag will never be triggered, nor will be the transfer complete flag. Forcefully, this leads to a timeout situation which is not easy to handle. Fortunately, it was spotted that sending the same commands through a different prog register "type", CHANGE READ COLUMN ENHANCED, would work all the time (even though this particular command is not supported by the core and is only available in a limited set of devices - we only care about the controller configuration and not the actual command which is sent to the device). So let's use this type instead when a CHANGE READ COLUMN is requested. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-22-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index d5fab3cbaa54..307300eeb9cb 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -53,6 +53,7 @@ #define PROG_RST BIT(8) #define PROG_GET_FEATURE BIT(9) #define PROG_SET_FEATURE BIT(10) +#define PROG_CHG_RD_COL_ENH BIT(14) #define INTR_STS_EN_REG 0x14 #define INTR_SIG_EN_REG 0x18 @@ -622,7 +623,23 @@ static int anfc_param_read_type_exec(struct nand_chip *chip, static int anfc_data_read_type_exec(struct nand_chip *chip, const struct nand_subop *subop) { - return anfc_misc_data_type_exec(chip, subop, PROG_PGRD); + u32 prog_reg = PROG_PGRD; + + /* + * Experience shows that while in SDR mode sending a CHANGE READ COLUMN + * command through the READ PAGE "type" always works fine, when in + * NV-DDR mode the same command simply fails. However, it was also + * spotted that any CHANGE READ COLUMN command sent through the CHANGE + * READ COLUMN ENHANCED "type" would correctly work in both cases (SDR + * and NV-DDR). So, for simplicity, let's program the controller with + * the CHANGE READ COLUMN ENHANCED "type" whenever we are requested to + * perform a CHANGE READ COLUMN operation. + */ + if (subop->instrs[0].ctx.cmd.opcode == NAND_CMD_RNDOUT && + subop->instrs[2].ctx.cmd.opcode == NAND_CMD_RNDOUTSTART) + prog_reg = PROG_CHG_RD_COL_ENH; + + return anfc_misc_data_type_exec(chip, subop, prog_reg); } static int anfc_param_write_type_exec(struct nand_chip *chip, -- cgit v1.2.3-59-g8ed1b From 4edde60314587382e42141df2f41ca968dc20737 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 May 2021 23:37:50 +0200 Subject: mtd: rawnand: arasan: Support NV-DDR interface Add support for the NV-DDR interface. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-23-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 307300eeb9cb..8c7537129674 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -879,25 +879,38 @@ static int anfc_setup_interface(struct nand_chip *chip, int target, struct arasan_nfc *nfc = to_anfc(chip->controller); struct device_node *np = nfc->dev->of_node; const struct nand_sdr_timings *sdr; - - sdr = nand_get_sdr_timings(conf); - if (IS_ERR(sdr)) - return PTR_ERR(sdr); + const struct nand_nvddr_timings *nvddr; + + if (nand_interface_is_nvddr(conf)) { + nvddr = nand_get_nvddr_timings(conf); + if (IS_ERR(nvddr)) + return PTR_ERR(nvddr); + } else { + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + } if (target < 0) return 0; - anand->timings = DIFACE_SDR | DIFACE_SDR_MODE(conf->timings.mode); + if (nand_interface_is_sdr(conf)) + anand->timings = DIFACE_SDR | + DIFACE_SDR_MODE(conf->timings.mode); + else + anand->timings = DIFACE_NVDDR | + DIFACE_DDR_MODE(conf->timings.mode); + anand->clk = ANFC_XLNX_SDR_DFLT_CORE_CLK; /* * Due to a hardware bug in the ZynqMP SoC, SDR timing modes 0-1 work * with f > 90MHz (default clock is 100MHz) but signals are unstable * with higher modes. Hence we decrease a little bit the clock rate to - * 80MHz when using modes 2-5 with this SoC. + * 80MHz when using SDR modes 2-5 with this SoC. */ if (of_device_is_compatible(np, "xlnx,zynqmp-nand-controller") && - conf->timings.mode >= 2) + nand_interface_is_sdr(conf) && conf->timings.mode >= 2) anand->clk = ANFC_XLNX_SDR_HS_CORE_CLK; return 0; -- cgit v1.2.3-59-g8ed1b From 5c1ce1fb8a2d434a485175d6ae38aea40ebd5de4 Mon Sep 17 00:00:00 2001 From: Ding Senjie Date: Fri, 14 May 2021 20:44:51 +0800 Subject: mtd: devices: Remove superfluous "break" Remove superfluous "break", as there is a "return" before it. Signed-off-by: Ding Senjie Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210514124451.20352-1-dingsenjie@163.com --- drivers/mtd/devices/ms02-nv.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c index fb4a6aa24543..08f76ff839a7 100644 --- a/drivers/mtd/devices/ms02-nv.c +++ b/drivers/mtd/devices/ms02-nv.c @@ -286,7 +286,6 @@ static int __init ms02nv_init(void) break; default: return -ENODEV; - break; } for (i = 0; i < ARRAY_SIZE(ms02nv_addrs); i++) -- cgit v1.2.3-59-g8ed1b From 237960880960863fb41888763d635b384cffb104 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 20 May 2021 11:48:50 +0000 Subject: mtd: partitions: redboot: seek fis-index-block in the right node fis-index-block is seeked in the master node and not in the partitions node. For following binding and current usage, the driver need to check the partitions subnode. Fixes: c0e118c8a1a3 ("mtd: partitions: Add OF support to RedBoot partitions") Signed-off-by: Corentin Labbe Reviewed-by: Linus Walleij Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210520114851.1274609-1-clabbe@baylibre.com --- drivers/mtd/parsers/redboot.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/parsers/redboot.c b/drivers/mtd/parsers/redboot.c index 91146bdc4713..3ccd6363ee8c 100644 --- a/drivers/mtd/parsers/redboot.c +++ b/drivers/mtd/parsers/redboot.c @@ -45,6 +45,7 @@ static inline int redboot_checksum(struct fis_image_desc *img) static void parse_redboot_of(struct mtd_info *master) { struct device_node *np; + struct device_node *npart; u32 dirblock; int ret; @@ -52,7 +53,11 @@ static void parse_redboot_of(struct mtd_info *master) if (!np) return; - ret = of_property_read_u32(np, "fis-index-block", &dirblock); + npart = of_get_child_by_name(np, "partitions"); + if (!npart) + return; + + ret = of_property_read_u32(npart, "fis-index-block", &dirblock); if (ret) return; -- cgit v1.2.3-59-g8ed1b From eb1765c40530ccc8690b9dad88cec6aaa6bfb498 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 20 May 2021 11:48:51 +0000 Subject: mtd: partitions: redboot: fix style issues This patch fixes easy checkpatch issues. Signed-off-by: Corentin Labbe Reviewed-by: Linus Walleij Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210520114851.1274609-2-clabbe@baylibre.com --- drivers/mtd/parsers/redboot.c | 69 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 35 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/parsers/redboot.c b/drivers/mtd/parsers/redboot.c index 3ccd6363ee8c..feb44a573d44 100644 --- a/drivers/mtd/parsers/redboot.c +++ b/drivers/mtd/parsers/redboot.c @@ -17,15 +17,15 @@ #include struct fis_image_desc { - unsigned char name[16]; // Null terminated name - uint32_t flash_base; // Address within FLASH of image - uint32_t mem_base; // Address in memory where it executes - uint32_t size; // Length of image - uint32_t entry_point; // Execution entry point - uint32_t data_length; // Length of actual data - unsigned char _pad[256-(16+7*sizeof(uint32_t))]; - uint32_t desc_cksum; // Checksum over image descriptor - uint32_t file_cksum; // Checksum over image data + unsigned char name[16]; // Null terminated name + u32 flash_base; // Address within FLASH of image + u32 mem_base; // Address in memory where it executes + u32 size; // Length of image + u32 entry_point; // Execution entry point + u32 data_length; // Length of actual data + unsigned char _pad[256 - (16 + 7 * sizeof(u32))]; + u32 desc_cksum; // Checksum over image descriptor + u32 file_cksum; // Checksum over image data }; struct fis_list { @@ -90,12 +90,12 @@ static int parse_redboot_partitions(struct mtd_info *master, parse_redboot_of(master); - if ( directory < 0 ) { + if (directory < 0) { offset = master->size + directory * master->erasesize; while (mtd_block_isbad(master, offset)) { if (!offset) { - nogood: - printk(KERN_NOTICE "Failed to find a non-bad block to check for RedBoot partition table\n"); +nogood: + pr_notice("Failed to find a non-bad block to check for RedBoot partition table\n"); return -EIO; } offset -= master->erasesize; @@ -113,8 +113,8 @@ static int parse_redboot_partitions(struct mtd_info *master, if (!buf) return -ENOMEM; - printk(KERN_NOTICE "Searching for RedBoot partition table in %s at offset 0x%lx\n", - master->name, offset); + pr_notice("Searching for RedBoot partition table in %s at offset 0x%lx\n", + master->name, offset); ret = mtd_read(master, offset, master->erasesize, &retlen, (void *)buf); @@ -150,14 +150,13 @@ static int parse_redboot_partitions(struct mtd_info *master, && swab32(buf[i].size) < master->erasesize)) { int j; /* Update numslots based on actual FIS directory size */ - numslots = swab32(buf[i].size) / sizeof (struct fis_image_desc); + numslots = swab32(buf[i].size) / sizeof(struct fis_image_desc); for (j = 0; j < numslots; ++j) { - /* A single 0xff denotes a deleted entry. * Two of them in a row is the end of the table. */ if (buf[j].name[0] == 0xff) { - if (buf[j].name[1] == 0xff) { + if (buf[j].name[1] == 0xff) { break; } else { continue; @@ -184,8 +183,8 @@ static int parse_redboot_partitions(struct mtd_info *master, } if (i == numslots) { /* Didn't find it */ - printk(KERN_NOTICE "No RedBoot partition table detected in %s\n", - master->name); + pr_notice("No RedBoot partition table detected in %s\n", + master->name); ret = 0; goto out; } @@ -204,7 +203,7 @@ static int parse_redboot_partitions(struct mtd_info *master, break; new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL); - namelen += strlen(buf[i].name)+1; + namelen += strlen(buf[i].name) + 1; if (!new_fl) { ret = -ENOMEM; goto out; @@ -213,13 +212,13 @@ static int parse_redboot_partitions(struct mtd_info *master, if (data && data->origin) buf[i].flash_base -= data->origin; else - buf[i].flash_base &= master->size-1; + buf[i].flash_base &= master->size - 1; /* I'm sure the JFFS2 code has done me permanent damage. * I now think the following is _normal_ */ prev = &fl; - while(*prev && (*prev)->img->flash_base < new_fl->img->flash_base) + while (*prev && (*prev)->img->flash_base < new_fl->img->flash_base) prev = &(*prev)->next; new_fl->next = *prev; *prev = new_fl; @@ -239,7 +238,7 @@ static int parse_redboot_partitions(struct mtd_info *master, } } #endif - parts = kzalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL); + parts = kzalloc(sizeof(*parts) * nrparts + nulllen + namelen, GFP_KERNEL); if (!parts) { ret = -ENOMEM; @@ -248,23 +247,22 @@ static int parse_redboot_partitions(struct mtd_info *master, nullname = (char *)&parts[nrparts]; #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED - if (nulllen > 0) { + if (nulllen > 0) strcpy(nullname, nullstring); - } #endif names = nullname + nulllen; - i=0; + i = 0; #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED if (fl->img->flash_base) { - parts[0].name = nullname; - parts[0].size = fl->img->flash_base; - parts[0].offset = 0; + parts[0].name = nullname; + parts[0].size = fl->img->flash_base; + parts[0].offset = 0; i++; } #endif - for ( ; iimg->size; parts[i].offset = fl->img->flash_base; parts[i].name = names; @@ -272,17 +270,17 @@ static int parse_redboot_partitions(struct mtd_info *master, strcpy(names, fl->img->name); #ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY if (!memcmp(names, "RedBoot", 8) || - !memcmp(names, "RedBoot config", 15) || - !memcmp(names, "FIS directory", 14)) { + !memcmp(names, "RedBoot config", 15) || + !memcmp(names, "FIS directory", 14)) { parts[i].mask_flags = MTD_WRITEABLE; } #endif - names += strlen(names)+1; + names += strlen(names) + 1; #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED - if(fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) { + if (fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) { i++; - parts[i].offset = parts[i-1].size + parts[i-1].offset; + parts[i].offset = parts[i - 1].size + parts[i - 1].offset; parts[i].size = fl->next->img->flash_base - parts[i].offset; parts[i].name = nullname; } @@ -296,6 +294,7 @@ static int parse_redboot_partitions(struct mtd_info *master, out: while (fl) { struct fis_list *old = fl; + fl = fl->next; kfree(old); } -- cgit v1.2.3-59-g8ed1b From 10f3b4d79958d6f9f71588c6fa862159c83fa80f Mon Sep 17 00:00:00 2001 From: Ansuel Smith Date: Wed, 26 May 2021 01:09:31 +0200 Subject: mtd: parsers: qcom: Fix leaking of partition name Add cleanup function as the name variable for the partition name was allocaed but never freed after the use as the add mtd function duplicate the name and free the pparts struct as the partition name is assumed to be static. The leak was found using kmemleak. Fixes: 803eb124e1a6 ("mtd: parsers: Add Qcom SMEM parser") Signed-off-by: Ansuel Smith Reviewed-by: Bjorn Andersson Reviewed-by: Manivannan Sadhasivam Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210525230931.30013-1-ansuelsmth@gmail.com --- drivers/mtd/parsers/qcomsmempart.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/parsers/qcomsmempart.c b/drivers/mtd/parsers/qcomsmempart.c index d9083308f6ba..06a818cd2433 100644 --- a/drivers/mtd/parsers/qcomsmempart.c +++ b/drivers/mtd/parsers/qcomsmempart.c @@ -159,6 +159,15 @@ out_free_parts: return ret; } +static void parse_qcomsmem_cleanup(const struct mtd_partition *pparts, + int nr_parts) +{ + int i; + + for (i = 0; i < nr_parts; i++) + kfree(pparts[i].name); +} + static const struct of_device_id qcomsmem_of_match_table[] = { { .compatible = "qcom,smem-part" }, {}, @@ -167,6 +176,7 @@ MODULE_DEVICE_TABLE(of, qcomsmem_of_match_table); static struct mtd_part_parser mtd_parser_qcomsmem = { .parse_fn = parse_qcomsmem_part, + .cleanup = parse_qcomsmem_cleanup, .name = "qcomsmem", .of_match_table = qcomsmem_of_match_table, }; -- cgit v1.2.3-59-g8ed1b From b85c943d181ac58e3a34a5f79c73d421f4da7b00 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 26 May 2021 11:32:40 +0200 Subject: mtd: rawnand: Add a helper to parse the gpio-cs DT property New chips may feature a lot of CS because of their extended length. As many controllers have been designed a decade ago, they usually only feature just a couple. This does not mean that the entire range of these chips cannot be accessed: it is just a matter of adding more GPIO CS in the hardware design. A DT property has been added to describe the CS array: cs-gpios. Here is the code parsing it this new property, allocating what needs to be, requesting the GPIOs and returning an array with the additional available CS. The first entries of this array are left empty and are reserved for native CS. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210526093242.183847-3-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_base.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/mtd/rawnand.h | 4 ++++ 2 files changed, 43 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 22974a53077f..57a583149cc0 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include "internals.h" @@ -5249,6 +5250,44 @@ static int of_get_nand_secure_regions(struct nand_chip *chip) return 0; } +/** + * rawnand_dt_parse_gpio_cs - Parse the gpio-cs property of a controller + * @dev: Device that will be parsed. Also used for managed allocations. + * @cs_array: Array of GPIO desc pointers allocated on success + * @ncs_array: Number of entries in @cs_array updated on success. + * @return 0 on success, an error otherwise. + */ +int rawnand_dt_parse_gpio_cs(struct device *dev, struct gpio_desc ***cs_array, + unsigned int *ncs_array) +{ + struct device_node *np = dev->of_node; + struct gpio_desc **descs; + int ndescs, i; + + ndescs = of_gpio_named_count(np, "cs-gpios"); + if (ndescs < 0) { + dev_dbg(dev, "No valid cs-gpios property\n"); + return 0; + } + + descs = devm_kcalloc(dev, ndescs, sizeof(*descs), GFP_KERNEL); + if (!descs) + return -ENOMEM; + + for (i = 0; i < ndescs; i++) { + descs[i] = gpiod_get_index_optional(dev, "cs", i, + GPIOD_OUT_HIGH); + if (IS_ERR(descs[i])) + return PTR_ERR(descs[i]); + } + + *ncs_array = ndescs; + *cs_array = descs; + + return 0; +} +EXPORT_SYMBOL(rawnand_dt_parse_gpio_cs); + static int rawnand_dt_init(struct nand_chip *chip) { struct nand_device *nand = mtd_to_nanddev(nand_to_mtd(chip)); diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index d41d39360fff..b2f9dd3cbd69 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -1595,4 +1595,8 @@ static inline void *nand_get_data_buf(struct nand_chip *chip) return chip->data_buf; } +/* Parse the gpio-cs property */ +int rawnand_dt_parse_gpio_cs(struct device *dev, struct gpio_desc ***cs_array, + unsigned int *ncs_array); + #endif /* __LINUX_MTD_RAWNAND_H */ -- cgit v1.2.3-59-g8ed1b From b5437c7b682c9a505065b4ab4716cdc951dc3c7c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 26 May 2021 11:32:41 +0200 Subject: mtd: rawnand: arasan: Ensure proper configuration for the asserted target The controller being always asserting one CS or the other, there is no need to actually select the right target before doing a page read/write. However, the anfc_select_target() helper actually also changes the timing configuration and clock in the case were two different NAND chips with different timing requirements would be used. In this situation, we must ensure proper configuration of the controller by calling it. As a consequence of this change, the anfc_select_target() helper is being moved earlier in the driver. Fixes: 88ffef1b65cf ("mtd: rawnand: arasan: Support the hardware BCH ECC engine") Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210526093242.183847-4-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 90 +++++++++++++++++---------- 1 file changed, 57 insertions(+), 33 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 8c7537129674..3a52d77421d1 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -274,6 +274,37 @@ static int anfc_pkt_len_config(unsigned int len, unsigned int *steps, return 0; } +static int anfc_select_target(struct nand_chip *chip, int target) +{ + struct anand *anand = to_anand(chip); + struct arasan_nfc *nfc = to_anfc(chip->controller); + int ret; + + /* Update the controller timings and the potential ECC configuration */ + writel_relaxed(anand->timings, nfc->base + DATA_INTERFACE_REG); + + /* Update clock frequency */ + if (nfc->cur_clk != anand->clk) { + clk_disable_unprepare(nfc->controller_clk); + ret = clk_set_rate(nfc->controller_clk, anand->clk); + if (ret) { + dev_err(nfc->dev, "Failed to change clock rate\n"); + return ret; + } + + ret = clk_prepare_enable(nfc->controller_clk); + if (ret) { + dev_err(nfc->dev, + "Failed to re-enable the controller clock\n"); + return ret; + } + + nfc->cur_clk = anand->clk; + } + + return 0; +} + /* * When using the embedded hardware ECC engine, the controller is in charge of * feeding the engine with, first, the ECC residue present in the data array. @@ -402,6 +433,18 @@ static int anfc_read_page_hw_ecc(struct nand_chip *chip, u8 *buf, return 0; } +static int anfc_sel_read_page_hw_ecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + int ret; + + ret = anfc_select_target(chip, chip->cur_cs); + if (ret) + return ret; + + return anfc_read_page_hw_ecc(chip, buf, oob_required, page); +}; + static int anfc_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, int oob_required, int page) { @@ -462,6 +505,18 @@ static int anfc_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, return ret; } +static int anfc_sel_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + int ret; + + ret = anfc_select_target(chip, chip->cur_cs); + if (ret) + return ret; + + return anfc_write_page_hw_ecc(chip, buf, oob_required, page); +}; + /* NAND framework ->exec_op() hooks and related helpers */ static int anfc_parse_instructions(struct nand_chip *chip, const struct nand_subop *subop, @@ -770,37 +825,6 @@ static const struct nand_op_parser anfc_op_parser = NAND_OP_PARSER( NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), ); -static int anfc_select_target(struct nand_chip *chip, int target) -{ - struct anand *anand = to_anand(chip); - struct arasan_nfc *nfc = to_anfc(chip->controller); - int ret; - - /* Update the controller timings and the potential ECC configuration */ - writel_relaxed(anand->timings, nfc->base + DATA_INTERFACE_REG); - - /* Update clock frequency */ - if (nfc->cur_clk != anand->clk) { - clk_disable_unprepare(nfc->controller_clk); - ret = clk_set_rate(nfc->controller_clk, anand->clk); - if (ret) { - dev_err(nfc->dev, "Failed to change clock rate\n"); - return ret; - } - - ret = clk_prepare_enable(nfc->controller_clk); - if (ret) { - dev_err(nfc->dev, - "Failed to re-enable the controller clock\n"); - return ret; - } - - nfc->cur_clk = anand->clk; - } - - return 0; -} - static int anfc_check_op(struct nand_chip *chip, const struct nand_operation *op) { @@ -1042,8 +1066,8 @@ static int anfc_init_hw_ecc_controller(struct arasan_nfc *nfc, if (!anand->bch) return -EINVAL; - ecc->read_page = anfc_read_page_hw_ecc; - ecc->write_page = anfc_write_page_hw_ecc; + ecc->read_page = anfc_sel_read_page_hw_ecc; + ecc->write_page = anfc_sel_write_page_hw_ecc; return 0; } -- cgit v1.2.3-59-g8ed1b From acbd3d0945f9cca4622f45e477793c5922bd6605 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 26 May 2021 11:32:42 +0200 Subject: mtd: rawnand: arasan: Leverage additional GPIO CS Make use of the cs-gpios DT property as well as the core helper to parse it so that the Arasan controller driver can now assert many more chips than natively. The Arasan controller has an internal limitation: RB0 is tied to CS0 and RB1 is tied to CS1. Hence, it is possible to use external GPIOs as long as one or the other native CS is not used (or configured to be driven as a GPIO) and that all additional CS are physically wired on its corresponding RB line. Eg. CS0 is used as a native CS, CS1 is not used as native CS and may be used as a GPIO CS, CS2 is an additional GPIO CS. Then the target asserted by CS0 should also be wired to RB0, while the targets asserted by CS1 and CS2 should be wired to RB1. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210526093242.183847-5-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 148 ++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 23 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 3a52d77421d1..97e5a336a760 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,8 @@ #define ANFC_XLNX_SDR_DFLT_CORE_CLK 100000000 #define ANFC_XLNX_SDR_HS_CORE_CLK 80000000 +static struct gpio_desc *anfc_default_cs_array[2] = {NULL, NULL}; + /** * struct anfc_op - Defines how to execute an operation * @pkt_reg: Packet register @@ -138,7 +141,6 @@ struct anfc_op { * struct anand - Defines the NAND chip related information * @node: Used to store NAND chips into a list * @chip: NAND chip information structure - * @cs: Chip select line * @rb: Ready-busy line * @page_sz: Register value of the page_sz field to use * @clk: Expected clock frequency to use @@ -152,11 +154,13 @@ struct anfc_op { * @errloc: Array of errors located with soft BCH * @hw_ecc: Buffer to store syndromes computed by hardware * @bch: BCH structure + * @cs_idx: Array of chip-select for this device, values are indexes + * of the controller structure @gpio_cs array + * @ncs_idx: Size of the @cs_idx array */ struct anand { struct list_head node; struct nand_chip chip; - unsigned int cs; unsigned int rb; unsigned int page_sz; unsigned long clk; @@ -170,6 +174,8 @@ struct anand { unsigned int *errloc; u8 *hw_ecc; struct bch_control *bch; + int *cs_idx; + int ncs_idx; }; /** @@ -180,8 +186,14 @@ struct anand { * @bus_clk: Pointer to the flash clock * @controller: Base controller structure * @chips: List of all NAND chips attached to the controller - * @assigned_cs: Bitmask describing already assigned CS lines * @cur_clk: Current clock rate + * @cs_array: CS array. Native CS are left empty, the other cells are + * populated with their corresponding GPIO descriptor. + * @ncs: Size of @cs_array + * @cur_cs: Index in @cs_array of the currently in use CS + * @native_cs: Currently selected native CS + * @spare_cs: Native CS that is not wired (may be selected when a GPIO + * CS is in use) */ struct arasan_nfc { struct device *dev; @@ -190,8 +202,12 @@ struct arasan_nfc { struct clk *bus_clk; struct nand_controller controller; struct list_head chips; - unsigned long assigned_cs; unsigned int cur_clk; + struct gpio_desc **cs_array; + unsigned int ncs; + int cur_cs; + unsigned int native_cs; + unsigned int spare_cs; }; static struct anand *to_anand(struct nand_chip *nand) @@ -274,12 +290,46 @@ static int anfc_pkt_len_config(unsigned int len, unsigned int *steps, return 0; } +static bool anfc_is_gpio_cs(struct arasan_nfc *nfc, int nfc_cs) +{ + return nfc_cs >= 0 && nfc->cs_array[nfc_cs]; +} + +static int anfc_relative_to_absolute_cs(struct anand *anand, int num) +{ + return anand->cs_idx[num]; +} + +static void anfc_assert_cs(struct arasan_nfc *nfc, unsigned int nfc_cs_idx) +{ + /* CS did not change: do nothing */ + if (nfc->cur_cs == nfc_cs_idx) + return; + + /* Deassert the previous CS if it was a GPIO */ + if (anfc_is_gpio_cs(nfc, nfc->cur_cs)) + gpiod_set_value_cansleep(nfc->cs_array[nfc->cur_cs], 1); + + /* Assert the new one */ + if (anfc_is_gpio_cs(nfc, nfc_cs_idx)) { + nfc->native_cs = nfc->spare_cs; + gpiod_set_value_cansleep(nfc->cs_array[nfc_cs_idx], 0); + } else { + nfc->native_cs = nfc_cs_idx; + } + + nfc->cur_cs = nfc_cs_idx; +} + static int anfc_select_target(struct nand_chip *chip, int target) { struct anand *anand = to_anand(chip); struct arasan_nfc *nfc = to_anfc(chip->controller); + unsigned int nfc_cs_idx = anfc_relative_to_absolute_cs(anand, target); int ret; + anfc_assert_cs(nfc, nfc_cs_idx); + /* Update the controller timings and the potential ECC configuration */ writel_relaxed(anand->timings, nfc->base + DATA_INTERFACE_REG); @@ -347,7 +397,7 @@ static int anfc_read_page_hw_ecc(struct nand_chip *chip, u8 *buf, .addr2_reg = ((page >> 16) & 0xFF) | ADDR2_STRENGTH(anand->strength) | - ADDR2_CS(anand->cs), + ADDR2_CS(nfc->native_cs), .cmd_reg = CMD_1(NAND_CMD_READ0) | CMD_2(NAND_CMD_READSTART) | @@ -464,7 +514,7 @@ static int anfc_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, .addr2_reg = ((page >> 16) & 0xFF) | ADDR2_STRENGTH(anand->strength) | - ADDR2_CS(anand->cs), + ADDR2_CS(nfc->native_cs), .cmd_reg = CMD_1(NAND_CMD_SEQIN) | CMD_2(NAND_CMD_PAGEPROG) | @@ -522,6 +572,7 @@ static int anfc_parse_instructions(struct nand_chip *chip, const struct nand_subop *subop, struct anfc_op *nfc_op) { + struct arasan_nfc *nfc = to_anfc(chip->controller); struct anand *anand = to_anand(chip); const struct nand_op_instr *instr = NULL; bool first_cmd = true; @@ -529,7 +580,7 @@ static int anfc_parse_instructions(struct nand_chip *chip, int ret, i; memset(nfc_op, 0, sizeof(*nfc_op)); - nfc_op->addr2_reg = ADDR2_CS(anand->cs); + nfc_op->addr2_reg = ADDR2_CS(nfc->native_cs); nfc_op->cmd_reg = CMD_PAGE_SIZE(anand->page_sz); for (op_id = 0; op_id < subop->ninstrs; op_id++) { @@ -1153,37 +1204,43 @@ static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np) struct anand *anand; struct nand_chip *chip; struct mtd_info *mtd; - int cs, rb, ret; + int rb, ret, i; anand = devm_kzalloc(nfc->dev, sizeof(*anand), GFP_KERNEL); if (!anand) return -ENOMEM; - /* We do not support multiple CS per chip yet */ - if (of_property_count_elems_of_size(np, "reg", sizeof(u32)) != 1) { + /* Chip-select init */ + anand->ncs_idx = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (anand->ncs_idx <= 0 || anand->ncs_idx > nfc->ncs) { dev_err(nfc->dev, "Invalid reg property\n"); return -EINVAL; } - ret = of_property_read_u32(np, "reg", &cs); - if (ret) - return ret; + anand->cs_idx = devm_kcalloc(nfc->dev, anand->ncs_idx, + sizeof(*anand->cs_idx), GFP_KERNEL); + if (!anand->cs_idx) + return -ENOMEM; + + for (i = 0; i < anand->ncs_idx; i++) { + ret = of_property_read_u32_index(np, "reg", i, + &anand->cs_idx[i]); + if (ret) { + dev_err(nfc->dev, "invalid CS property: %d\n", ret); + return ret; + } + } + /* Ready-busy init */ ret = of_property_read_u32(np, "nand-rb", &rb); if (ret) return ret; - if (cs >= ANFC_MAX_CS || rb >= ANFC_MAX_CS) { - dev_err(nfc->dev, "Wrong CS %d or RB %d\n", cs, rb); - return -EINVAL; - } - - if (test_and_set_bit(cs, &nfc->assigned_cs)) { - dev_err(nfc->dev, "Already assigned CS %d\n", cs); + if (rb >= ANFC_MAX_CS) { + dev_err(nfc->dev, "Wrong RB %d\n", rb); return -EINVAL; } - anand->cs = cs; anand->rb = rb; chip = &anand->chip; @@ -1199,7 +1256,7 @@ static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np) return -EINVAL; } - ret = nand_scan(chip, 1); + ret = nand_scan(chip, anand->ncs_idx); if (ret) { dev_err(nfc->dev, "Scan operation failed\n"); return ret; @@ -1237,7 +1294,7 @@ static int anfc_chips_init(struct arasan_nfc *nfc) int nchips = of_get_child_count(np); int ret; - if (!nchips || nchips > ANFC_MAX_CS) { + if (!nchips) { dev_err(nfc->dev, "Incorrect number of NAND chips (%d)\n", nchips); return -EINVAL; @@ -1262,6 +1319,47 @@ static void anfc_reset(struct arasan_nfc *nfc) /* Enable interrupt status */ writel_relaxed(EVENT_MASK, nfc->base + INTR_STS_EN_REG); + + nfc->cur_cs = -1; +} + +static int anfc_parse_cs(struct arasan_nfc *nfc) +{ + int ret; + + /* Check the gpio-cs property */ + ret = rawnand_dt_parse_gpio_cs(nfc->dev, &nfc->cs_array, &nfc->ncs); + if (ret) + return ret; + + /* + * The controller native CS cannot be both disabled at the same time. + * Hence, only one native CS can be used if GPIO CS are needed, so that + * the other is selected when a non-native CS must be asserted (not + * wired physically or configured as GPIO instead of NAND CS). In this + * case, the "not" chosen CS is assigned to nfc->spare_cs and selected + * whenever a GPIO CS must be asserted. + */ + if (nfc->cs_array && nfc->ncs > 2) { + if (!nfc->cs_array[0] && !nfc->cs_array[1]) { + dev_err(nfc->dev, + "Assign a single native CS when using GPIOs\n"); + return -EINVAL; + } + + if (nfc->cs_array[0]) + nfc->spare_cs = 0; + else + nfc->spare_cs = 1; + } + + if (!nfc->cs_array) { + nfc->cs_array = anfc_default_cs_array; + nfc->ncs = ANFC_MAX_CS; + return 0; + } + + return 0; } static int anfc_probe(struct platform_device *pdev) @@ -1300,6 +1398,10 @@ static int anfc_probe(struct platform_device *pdev) if (ret) goto disable_controller_clk; + ret = anfc_parse_cs(nfc); + if (ret) + goto disable_bus_clk; + ret = anfc_chips_init(nfc); if (ret) goto disable_bus_clk; -- cgit v1.2.3-59-g8ed1b From 902f332e461ae53e04c53bde12be790bf4097240 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Mon, 10 May 2021 19:49:44 +0800 Subject: mtd: rawnand: qcom: Delete an unneeded bool conversion The result of an expression consisting of a single relational operator is already of the bool type and does not need to be evaluated explicitly. No functional change. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210510114944.3527-1-thunder.leizhen@huawei.com --- drivers/mtd/nand/raw/qcom_nandc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index ef90b669a27d..45de67aa86a4 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -1850,8 +1850,7 @@ static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf, * ERASED_CW bits are set. */ if (host->bch_enabled) { - erased = (erased_cw & ERASED_CW) == ERASED_CW ? - true : false; + erased = (erased_cw & ERASED_CW) == ERASED_CW; /* * For RS ECC, HW reports the erased CW by placing * special characters at certain offsets in the buffer. -- cgit v1.2.3-59-g8ed1b From c374839f9b4475173e536d1eaddff45cb481dbdf Mon Sep 17 00:00:00 2001 From: Jaime Liao Date: Thu, 20 May 2021 09:45:08 +0800 Subject: mtd: spinand: macronix: Add support for serial NAND flash Macronix NAND Flash devices are available in different configurations and densities. MX"35" means SPI NAND MX35"LF"/"UF" , LF means 3V and UF meands 1.8V MX35LF"2G" , 2G means 2Gbits MX35LF2G"E4"/"24"/"14", E4 means internal ECC and Quad I/O(x4) 24 means 8-bit ecc requirement and Quad I/O(x4) 14 means 4-bit ecc requirement and Quad I/O(x4) MX35LF2G14AC is 3V 2Gbit serial NAND flash device (without on-die ECC) https://www.mxic.com.tw/Lists/Datasheet/Attachments/7926/MX35LF2G14AC,%203V,%202Gb,%20v1.1.pdf MX35UF4G24AD is 1.8V 4Gbit serial NAND flash device (without on-die ECC) https://www.mxic.com.tw/Lists/Datasheet/Attachments/7980/MX35UF4G24AD,%201.8V,%204Gb,%20v0.00.pdf MX35UF4GE4AD/MX35UF2GE4AD are 1.8V 4G/2Gbit serial NAND flash device with 8-bit on-die ECC https://www.mxic.com.tw/Lists/Datasheet/Attachments/7983/MX35UF4GE4AD,%201.8V,%204Gb,%20v0.00.pdf MX35UF2GE4AC/MX35UF1GE4AC are 1.8V 2G/1Gbit serial NAND flash device with 8-bit on-die ECC https://www.mxic.com.tw/Lists/Datasheet/Attachments/7974/MX35UF2GE4AC,%201.8V,%202Gb,%20v1.0.pdf MX35UF2G14AC/MX35UF1G14AC are 1.8V 2G/1Gbit serial NAND flash device (without on-die ECC) https://www.mxic.com.tw/Lists/Datasheet/Attachments/7931/MX35UF2G14AC,%201.8V,%202Gb,%20v1.1.pdf Validated via normal(default) and QUAD mode by read, erase, read back, on Xilinx Zynq PicoZed FPGA board which included Macronix SPI Host(drivers/spi/spi-mxic.c). Signed-off-by: Jaime Liao Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1621475108-22523-1-git-send-email-jaimeliao@mxic.com.tw --- drivers/mtd/nand/spi/macronix.c | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 6701aaa21a49..a9890350db02 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -186,6 +186,118 @@ static const struct spinand_info macronix_spinand_table[] = { 0 /*SPINAND_HAS_QE_BIT*/, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, mx35lf1ge4ab_ecc_get_status)), + + SPINAND_INFO("MX35LF2G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x20), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF4G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb5), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF4GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa0), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa4), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2GE4AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x90), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x94), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1GE4AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + }; static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = { -- cgit v1.2.3-59-g8ed1b From 5c26d52c9e5c9a22d04b805470e1143716b69789 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 10 May 2021 17:13:43 +0300 Subject: mtd: spi-nor: nxp-spifi: Use SPI_MODE_X_MASK Use SPI_MODE_X_MASK instead of open coded variant. Signed-off-by: Andy Shevchenko Signed-off-by: Vignesh Raghavendra Reviewed-by: Michael Walle --- drivers/mtd/spi-nor/controllers/nxp-spifi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/controllers/nxp-spifi.c b/drivers/mtd/spi-nor/controllers/nxp-spifi.c index 5703e8313980..2635c80231bb 100644 --- a/drivers/mtd/spi-nor/controllers/nxp-spifi.c +++ b/drivers/mtd/spi-nor/controllers/nxp-spifi.c @@ -326,7 +326,7 @@ static int nxp_spifi_setup_flash(struct nxp_spifi *spifi, ctrl |= SPIFI_CTRL_DUAL; } - switch (mode & (SPI_CPHA | SPI_CPOL)) { + switch (mode & SPI_MODE_X_MASK) { case SPI_MODE_0: ctrl &= ~SPIFI_CTRL_MODE3; break; -- cgit v1.2.3-59-g8ed1b From ccfb7cf18f9680958e76991c8f15562ff42f4bc9 Mon Sep 17 00:00:00 2001 From: Pratyush Yadav Date: Tue, 11 May 2021 15:09:58 +0530 Subject: mtd: spi-nor: Add documentation for spi_nor_soft_reset() Document what the function does and that it should only be used when it is known that the device supports it. This will avoid unaware programmers thinking that they can arbitrarily use it to reset the device. Suggested-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Vignesh Raghavendra Reviewed-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index bd2c7717eb10..f6a6ef2d8bd8 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2839,6 +2839,21 @@ static int spi_nor_init(struct spi_nor *nor) return 0; } +/** + * spi_nor_soft_reset() - Perform a software reset + * @nor: pointer to 'struct spi_nor' + * + * Performs a "Soft Reset and Enter Default Protocol Mode" sequence which resets + * the device to its power-on-reset state. This is useful when the software has + * made some changes to device (volatile) registers and needs to reset it before + * shutting down, for example. + * + * Not every flash supports this sequence. The same set of opcodes might be used + * for some other operation on a flash that does not support this. Support for + * this sequence can be discovered via SFDP in the BFPT table. + * + * Return: 0 on success, -errno otherwise. + */ static void spi_nor_soft_reset(struct spi_nor *nor) { struct spi_mem_op op; -- cgit v1.2.3-59-g8ed1b From a6e2cd4dd28effab117ddce7a62c5a411b282d2e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Wed, 5 May 2021 22:00:17 +0200 Subject: mtd: spi-nor: otp: fix kerneldoc typos Use the correct argument names in the kerneldoc. Fixes: cad3193fe9d1 ("mtd: spi-nor: implement OTP support for Winbond and similar flashes") Reported-by: Pratyush Yadav Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Reviewed-by: Pratyush Yadav --- drivers/mtd/spi-nor/otp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index fcf38d260345..61036c716abb 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -17,7 +17,7 @@ /** * spi_nor_otp_read_secr() - read OTP data * @nor: pointer to 'struct spi_nor' - * @from: offset to read from + * @addr: offset to read from * @len: number of bytes to read * @buf: pointer to dst buffer * @@ -59,7 +59,7 @@ int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf) /** * spi_nor_otp_write_secr() - write OTP data * @nor: pointer to 'struct spi_nor' - * @to: offset to write to + * @addr: offset to write to * @len: number of bytes to write * @buf: pointer to src buffer * -- cgit v1.2.3-59-g8ed1b From 854955ae96dbd436ba4719dd1cedb7c1c40bd303 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 15 Apr 2021 13:11:38 +0300 Subject: mtd: spi-nor: intel-spi: Add support for Intel Alder Lake-M SPI serial flash Intel Alder Lake-M has the same SPI serial flash controller as Alder Lake-S. Add Alder Lake-M PCI ID to the driver list of supported devices. Signed-off-by: Mika Westerberg Signed-off-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/controllers/intel-spi-pci.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/controllers/intel-spi-pci.c b/drivers/mtd/spi-nor/controllers/intel-spi-pci.c index 825610a2e9dc..1bc53b8bb88a 100644 --- a/drivers/mtd/spi-nor/controllers/intel-spi-pci.c +++ b/drivers/mtd/spi-nor/controllers/intel-spi-pci.c @@ -74,6 +74,7 @@ static const struct pci_device_id intel_spi_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x4b24), (unsigned long)&bxt_info }, { PCI_VDEVICE(INTEL, 0x4da4), (unsigned long)&bxt_info }, { PCI_VDEVICE(INTEL, 0x51a4), (unsigned long)&cnl_info }, + { PCI_VDEVICE(INTEL, 0x54a4), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7aa4), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0xa0a4), (unsigned long)&bxt_info }, { PCI_VDEVICE(INTEL, 0xa1a4), (unsigned long)&bxt_info }, -- cgit v1.2.3-59-g8ed1b From d406f49b05e547d1e1ff1e9e0e0133fa2538b2fc Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 2 Apr 2021 11:20:31 +0300 Subject: mtd: spi-nor: macronix: Fix name for mx66l51235f According to macronix website, there is no mx66l51235l part number. The chip detected as such is actually mx66l51235f. Rename the flash. Do not update the mx66l51235l name from the spi_nor_dev_ids[], since there are dt that are using this compatible. Signed-off-by: Tudor Ambarus Signed-off-by: Vignesh Raghavendra Reviewed-by: Vignesh Raghavendra --- drivers/mtd/spi-nor/macronix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 42c2cf31702e..c8167de55e55 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -72,7 +72,7 @@ static const struct flash_info macronix_parts[] = { SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) }, - { "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, + { "mx66l51235f", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, { "mx66u51235f", INFO(0xc2253a, 0, 64 * 1024, 1024, -- cgit v1.2.3-59-g8ed1b From 7ea40b54e83baed17d85567cfae56175def39a55 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Mon, 10 May 2021 15:33:40 +0200 Subject: mtd: spi-nor: enable locking support for MX25L12805D Macronix MX25L12805D supports locking with 4 block protection bits in its status register. Add the corresponding flag in order to clear these bits when unloking the flash. Otherwise, the flash might not be writable depending on the state left by the bootloader. Tested-on: Ubiquiti UniFi AC Lite (ath79) Signed-off-by: David Bauer Signed-off-by: Vignesh Raghavendra Reviewed-by: Michael Walle --- drivers/mtd/spi-nor/macronix.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index c8167de55e55..27498ed0cc0d 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -49,7 +49,8 @@ static const struct flash_info macronix_parts[] = { { "mx25u4035", INFO(0xc22533, 0, 64 * 1024, 8, SECT_4K) }, { "mx25u8035", INFO(0xc22534, 0, 64 * 1024, 16, SECT_4K) }, { "mx25u6435f", INFO(0xc22537, 0, 64 * 1024, 128, SECT_4K) }, - { "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, SECT_4K) }, + { "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, SECT_4K | + SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP) }, { "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) }, { "mx25r1635f", INFO(0xc22815, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | -- cgit v1.2.3-59-g8ed1b From b97b1a769849beb6b40b740817b06f1a50e1c589 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 7 Jun 2021 13:27:41 +0200 Subject: mtd: spi-nor: otp: fix access to security registers in 4 byte mode The security registers either take a 3 byte or a 4 byte address offset, depending on the address mode of the flash. Thus just leave the nor->addr_width as is. Fixes: cad3193fe9d1 ("mtd: spi-nor: implement OTP support for Winbond and similar flashes") Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Reviewed-by: Tudor Ambarus Acked-by: Pratyush Yadav --- drivers/mtd/spi-nor/otp.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 61036c716abb..91a4c510ed51 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -40,7 +40,6 @@ int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf) rdesc = nor->dirmap.rdesc; nor->read_opcode = SPINOR_OP_RSECR; - nor->addr_width = 3; nor->read_dummy = 8; nor->read_proto = SNOR_PROTO_1_1_1; nor->dirmap.rdesc = NULL; @@ -84,7 +83,6 @@ int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, wdesc = nor->dirmap.wdesc; nor->program_opcode = SPINOR_OP_PSECR; - nor->addr_width = 3; nor->write_proto = SNOR_PROTO_1_1_1; nor->dirmap.wdesc = NULL; -- cgit v1.2.3-59-g8ed1b From d5b813e484721dfcb84410ec6883c7b05156d9d3 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 7 Jun 2021 13:27:42 +0200 Subject: mtd: spi-nor: otp: use more consistent wording Use the wording as used in the datasheet to describe the access methods of the security registers (aka OTP storage). This will also match the function names. Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Reviewed-by: Pratyush Yadav Reviewed-by: Tudor Ambarus --- drivers/mtd/spi-nor/otp.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 91a4c510ed51..3898ed67ba1c 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -15,14 +15,21 @@ #define spi_nor_otp_n_regions(nor) ((nor)->params->otp.org->n_regions) /** - * spi_nor_otp_read_secr() - read OTP data + * spi_nor_otp_read_secr() - read security register * @nor: pointer to 'struct spi_nor' * @addr: offset to read from * @len: number of bytes to read * @buf: pointer to dst buffer * - * Read OTP data from one region by using the SPINOR_OP_RSECR commands. This - * method is used on GigaDevice and Winbond flashes. + * Read a security register by using the SPINOR_OP_RSECR commands. + * + * In Winbond/GigaDevice datasheets the term "security register" stands for + * an one-time-programmable memory area, consisting of multiple bytes (usually + * 256). Thus one "security register" maps to one OTP region. + * + * This method is used on GigaDevice and Winbond flashes. + * + * Please note, the read must not span multiple registers. * * Return: number of bytes read successfully, -errno otherwise */ @@ -56,16 +63,20 @@ int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf) } /** - * spi_nor_otp_write_secr() - write OTP data + * spi_nor_otp_write_secr() - write security register * @nor: pointer to 'struct spi_nor' * @addr: offset to write to * @len: number of bytes to write * @buf: pointer to src buffer * - * Write OTP data to one region by using the SPINOR_OP_PSECR commands. This - * method is used on GigaDevice and Winbond flashes. + * Write a security register by using the SPINOR_OP_PSECR commands. + * + * For more information on the term "security register", see the documentation + * of spi_nor_otp_read_secr(). + * + * This method is used on GigaDevice and Winbond flashes. * - * Please note, the write must not span multiple OTP regions. + * Please note, the write must not span multiple registers. * * Return: number of bytes written successfully, -errno otherwise */ @@ -88,7 +99,7 @@ int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, /* * We only support a write to one single page. For now all winbond - * flashes only have one page per OTP region. + * flashes only have one page per security register. */ ret = spi_nor_write_enable(nor); if (ret) -- cgit v1.2.3-59-g8ed1b From 388161ca45c911f566b71716bce5ff0119fb5522 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 7 Jun 2021 13:27:43 +0200 Subject: mtd: spi-nor: otp: return -EROFS if region is read-only SPI NOR flashes will just ignore program commands if the OTP region is locked. Thus, a user might not notice that the intended write didn't end up in the flash. Return -EROFS to the user in this case. From what I can tell, chips/cfi_cmdset_0001.c also return this error code. One could optimize spi_nor_mtd_otp_range_is_locked() to read the status register only once and not for every OTP region, but for that we would need some more invasive changes. Given that this is one-time-programmable memory and the normal access mode is reading, we just live with the small overhead. By moving the code around a bit, we can just check the length before calling spi_nor_mtd_otp_range_is_locked() and avoid an underflow there if a len is 0. This way we don't need to take the lock either. We also skip the "*retlen = 0" assignment, mtdcore already takes care of that for us. Fixes: 069089acf88b ("mtd: spi-nor: add OTP support") Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Reviewed-by: Pratyush Yadav Reviewed-by: Tudor Ambarus --- drivers/mtd/spi-nor/otp.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 3898ed67ba1c..89fe52e3851a 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -249,6 +249,29 @@ out: return ret; } +static int spi_nor_mtd_otp_range_is_locked(struct spi_nor *nor, loff_t ofs, + size_t len) +{ + const struct spi_nor_otp_ops *ops = nor->params->otp.ops; + unsigned int region; + int locked; + + /* + * If any of the affected OTP regions are locked the entire range is + * considered locked. + */ + for (region = spi_nor_otp_offset_to_region(nor, ofs); + region <= spi_nor_otp_offset_to_region(nor, ofs + len - 1); + region++) { + locked = ops->is_locked(nor, region); + /* take the branch it is locked or in case of an error */ + if (locked) + return locked; + } + + return 0; +} + static int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs, size_t total_len, size_t *retlen, const u8 *buf, bool is_write) @@ -264,14 +287,26 @@ static int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs, if (ofs < 0 || ofs >= spi_nor_otp_size(nor)) return 0; + /* don't access beyond the end */ + total_len = min_t(size_t, total_len, spi_nor_otp_size(nor) - ofs); + + if (!total_len) + return 0; + ret = spi_nor_lock_and_prep(nor); if (ret) return ret; - /* don't access beyond the end */ - total_len = min_t(size_t, total_len, spi_nor_otp_size(nor) - ofs); + if (is_write) { + ret = spi_nor_mtd_otp_range_is_locked(nor, ofs, total_len); + if (ret < 0) { + goto out; + } else if (ret) { + ret = -EROFS; + goto out; + } + } - *retlen = 0; while (total_len) { /* * The OTP regions are mapped into a contiguous area starting -- cgit v1.2.3-59-g8ed1b From c6ec3e1e3a853f9469c7d07b0fde0be4da8c3ba1 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 7 Jun 2021 13:27:44 +0200 Subject: mtd: spi-nor: otp: implement erase for Winbond and similar flashes Winbond flashes with OTP support provide a command to erase the OTP data. This might come in handy during development. This was tested with a Winbond W25Q32JW on a LS1028A SoC with the NXP FSPI controller. Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Reviewed-by: Pratyush Yadav Reviewed-by: Tudor Ambarus --- drivers/mtd/spi-nor/core.c | 2 +- drivers/mtd/spi-nor/core.h | 4 ++ drivers/mtd/spi-nor/otp.c | 86 +++++++++++++++++++++++++++++++++++++++++++ drivers/mtd/spi-nor/winbond.c | 1 + 4 files changed, 92 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index f6a6ef2d8bd8..a21b0085de05 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1318,7 +1318,7 @@ static u32 spi_nor_convert_addr(struct spi_nor *nor, loff_t addr) /* * Initiate the erasure of a single sector */ -static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) +int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) { int i; diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 28a2e0be97a3..9398a8738857 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -207,6 +207,7 @@ struct spi_nor_otp_organization { * @read: read from the SPI NOR OTP area. * @write: write to the SPI NOR OTP area. * @lock: lock an OTP region. + * @erase: erase an OTP region. * @is_locked: check if an OTP region of the SPI NOR is locked. */ struct spi_nor_otp_ops { @@ -214,6 +215,7 @@ struct spi_nor_otp_ops { int (*write)(struct spi_nor *nor, loff_t addr, size_t len, const u8 *buf); int (*lock)(struct spi_nor *nor, unsigned int region); + int (*erase)(struct spi_nor *nor, loff_t addr); int (*is_locked)(struct spi_nor *nor, unsigned int region); }; @@ -503,10 +505,12 @@ ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, const u8 *buf); +int spi_nor_erase_sector(struct spi_nor *nor, u32 addr); int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf); int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, const u8 *buf); +int spi_nor_otp_erase_secr(struct spi_nor *nor, loff_t addr); int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region); int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region); diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 89fe52e3851a..983e40b19134 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -120,6 +120,38 @@ out: return ret ?: written; } +/** + * spi_nor_otp_erase_secr() - erase a security register + * @nor: pointer to 'struct spi_nor' + * @addr: offset of the security register to be erased + * + * Erase a security register by using the SPINOR_OP_ESECR command. + * + * For more information on the term "security register", see the documentation + * of spi_nor_otp_read_secr(). + * + * This method is used on GigaDevice and Winbond flashes. + * + * Return: 0 on success, -errno otherwise + */ +int spi_nor_otp_erase_secr(struct spi_nor *nor, loff_t addr) +{ + u8 erase_opcode = nor->erase_opcode; + int ret; + + ret = spi_nor_write_enable(nor); + if (ret) + return ret; + + nor->erase_opcode = SPINOR_OP_ESECR; + ret = spi_nor_erase_sector(nor, addr); + nor->erase_opcode = erase_opcode; + if (ret) + return ret; + + return spi_nor_wait_till_ready(nor); +} + static int spi_nor_otp_lock_bit_cr(unsigned int region) { static const int lock_bits[] = { SR2_LB1, SR2_LB2, SR2_LB3 }; @@ -360,6 +392,59 @@ static int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len, return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true); } +static int spi_nor_mtd_otp_erase(struct mtd_info *mtd, loff_t from, size_t len) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + const struct spi_nor_otp_ops *ops = nor->params->otp.ops; + const size_t rlen = spi_nor_otp_region_len(nor); + unsigned int region; + loff_t rstart; + int ret; + + /* OTP erase is optional */ + if (!ops->erase) + return -EOPNOTSUPP; + + if (!len) + return 0; + + if (from < 0 || (from + len) > spi_nor_otp_size(nor)) + return -EINVAL; + + /* the user has to explicitly ask for whole regions */ + if (!IS_ALIGNED(len, rlen) || !IS_ALIGNED(from, rlen)) + return -EINVAL; + + ret = spi_nor_lock_and_prep(nor); + if (ret) + return ret; + + ret = spi_nor_mtd_otp_range_is_locked(nor, from, len); + if (ret < 0) { + goto out; + } else if (ret) { + ret = -EROFS; + goto out; + } + + while (len) { + region = spi_nor_otp_offset_to_region(nor, from); + rstart = spi_nor_otp_region_start(nor, region); + + ret = ops->erase(nor, rstart); + if (ret) + goto out; + + len -= rlen; + from += rlen; + } + +out: + spi_nor_unlock_and_unprep(nor); + + return ret; +} + static int spi_nor_mtd_otp_lock(struct mtd_info *mtd, loff_t from, size_t len) { struct spi_nor *nor = mtd_to_spi_nor(mtd); @@ -418,4 +503,5 @@ void spi_nor_otp_init(struct spi_nor *nor) mtd->_read_user_prot_reg = spi_nor_mtd_otp_read; mtd->_write_user_prot_reg = spi_nor_mtd_otp_write; mtd->_lock_user_prot_reg = spi_nor_mtd_otp_lock; + mtd->_erase_user_prot_reg = spi_nor_mtd_otp_erase; } diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 9a81c67a60c6..96573f61caf5 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -139,6 +139,7 @@ static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable) static const struct spi_nor_otp_ops winbond_otp_ops = { .read = spi_nor_otp_read_secr, .write = spi_nor_otp_write_secr, + .erase = spi_nor_otp_erase_secr, .lock = spi_nor_otp_lock_sr2, .is_locked = spi_nor_otp_is_locked_sr2, }; -- cgit v1.2.3-59-g8ed1b From c93081b265735db2417f0964718516044d06b1a2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 27 May 2021 10:43:45 +0200 Subject: mtd: spinand: Fix double counting of ECC stats In the raw NAND world, ECC engines increment ecc_stats and the final caller is responsible for returning -EBADMSG if the verification failed. In the SPI-NAND world it was a bit different until now because there was only one possible ECC engine: the on-die one. Indeed, the spinand_mtd_read() call was incrementing the ecc_stats counters depending on the outcome of spinand_check_ecc_status() directly. So now let's split the logic like this: - spinand_check_ecc_status() is specific to the SPI-NAND on-die engine and is kept very simple: it just returns the ECC status (bonus point: the content of this helper can be overloaded). - spinand_ondie_ecc_finish_io_req() is the caller of spinand_check_ecc_status() and will increment the counters and eventually return -EBADMSG. - spinand_mtd_read() is not tied to the on-die ECC implementation and should be able to handle results coming from other ECC engines: it has the responsibility of returning the maximum number of bitflips which happened during the entire operation as this is the only helper that is aware that several pages may be read in a row. Fixes: 945845b54c9c ("mtd: spinand: Instantiate a SPI-NAND on-die ECC engine") Reported-by: YouChing Lin Signed-off-by: Miquel Raynal Tested-by: YouChing Lin Link: https://lore.kernel.org/linux-mtd/20210527084345.208215-1-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/core.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 17f63f95f4a2..54ae540bc66b 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -290,6 +290,8 @@ static int spinand_ondie_ecc_finish_io_req(struct nand_device *nand, { struct spinand_ondie_ecc_conf *engine_conf = nand->ecc.ctx.priv; struct spinand_device *spinand = nand_to_spinand(nand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + int ret; if (req->mode == MTD_OPS_RAW) return 0; @@ -299,7 +301,13 @@ static int spinand_ondie_ecc_finish_io_req(struct nand_device *nand, return 0; /* Finish a page write: check the status, report errors/bitflips */ - return spinand_check_ecc_status(spinand, engine_conf->status); + ret = spinand_check_ecc_status(spinand, engine_conf->status); + if (ret == -EBADMSG) + mtd->ecc_stats.failed++; + else if (ret > 0) + mtd->ecc_stats.corrected += ret; + + return ret; } static struct nand_ecc_engine_ops spinand_ondie_ecc_engine_ops = { @@ -620,13 +628,10 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, if (ret < 0 && ret != -EBADMSG) break; - if (ret == -EBADMSG) { + if (ret == -EBADMSG) ecc_failed = true; - mtd->ecc_stats.failed++; - } else { - mtd->ecc_stats.corrected += ret; + else max_bitflips = max_t(unsigned int, max_bitflips, ret); - } ret = 0; ops->retlen += iter.req.datalen; -- cgit v1.2.3-59-g8ed1b From 97f41002945fd9c55e8b80c654ea34fd434250e7 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 3 Jun 2021 20:30:41 +0800 Subject: mtd: mtdpart: use DEVICE_ATTR_RO() helper macro Use DEVICE_ATTR_RO() helper macro instead of plain DEVICE_ATTR(), which makes the code a bit shorter and easier to read. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210603123041.12036-1-thunder.leizhen@huawei.com --- drivers/mtd/mtdpart.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 66a67c6e3cbc..04af12b66110 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -212,15 +212,14 @@ out_register: return child; } -static ssize_t mtd_partition_offset_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t offset_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct mtd_info *mtd = dev_get_drvdata(dev); return sysfs_emit(buf, "%lld\n", mtd->part.offset); } - -static DEVICE_ATTR(offset, S_IRUGO, mtd_partition_offset_show, NULL); +static DEVICE_ATTR_RO(offset); /* mtd partition offset */ static const struct attribute *mtd_partition_attrs[] = { &dev_attr_offset.attr, -- cgit v1.2.3-59-g8ed1b From b4e248632c968d985f0ecfd7924423fbefc39d1c Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 3 Jun 2021 20:53:22 +0800 Subject: mtd: core: add MTD_DEVICE_ATTR_RO/RW() helper macros Compared with the definition of DEVICE_ATTR_RO/RW(), the read and write function names of the sysfs attribute have an additional "mtd_" prefix. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210603125323.12142-2-thunder.leizhen@huawei.com --- drivers/mtd/mtdcore.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index ce514305f8f7..770f64d67015 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -96,6 +96,12 @@ static void mtd_release(struct device *dev) device_destroy(&mtd_class, index + 1); } +#define MTD_DEVICE_ATTR_RO(name) \ +static DEVICE_ATTR(name, 0444, mtd_##name##_show, NULL) + +#define MTD_DEVICE_ATTR_RW(name) \ +static DEVICE_ATTR(name, 0644, mtd_##name##_show, mtd_##name##_store) + static ssize_t mtd_type_show(struct device *dev, struct device_attribute *attr, char *buf) { -- cgit v1.2.3-59-g8ed1b From a17da115ac042fd560cba2f8e4057722cf0c42cd Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 3 Jun 2021 20:53:23 +0800 Subject: mtd: core: use MTD_DEVICE_ATTR_RO/RW() helper macros Use MTD_DEVICE_ATTR_RO/RW() helper macros instead of plain DEVICE_ATTR(), which makes the code a bit shorter and easier to read. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210603125323.12142-3-thunder.leizhen@huawei.com --- drivers/mtd/mtdcore.c | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 770f64d67015..b5ccd3037788 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -139,7 +139,7 @@ static ssize_t mtd_type_show(struct device *dev, return sysfs_emit(buf, "%s\n", type); } -static DEVICE_ATTR(type, S_IRUGO, mtd_type_show, NULL); +MTD_DEVICE_ATTR_RO(type); static ssize_t mtd_flags_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -148,7 +148,7 @@ static ssize_t mtd_flags_show(struct device *dev, return sysfs_emit(buf, "0x%lx\n", (unsigned long)mtd->flags); } -static DEVICE_ATTR(flags, S_IRUGO, mtd_flags_show, NULL); +MTD_DEVICE_ATTR_RO(flags); static ssize_t mtd_size_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -157,7 +157,7 @@ static ssize_t mtd_size_show(struct device *dev, return sysfs_emit(buf, "%llu\n", (unsigned long long)mtd->size); } -static DEVICE_ATTR(size, S_IRUGO, mtd_size_show, NULL); +MTD_DEVICE_ATTR_RO(size); static ssize_t mtd_erasesize_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -166,7 +166,7 @@ static ssize_t mtd_erasesize_show(struct device *dev, return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->erasesize); } -static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL); +MTD_DEVICE_ATTR_RO(erasesize); static ssize_t mtd_writesize_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -175,7 +175,7 @@ static ssize_t mtd_writesize_show(struct device *dev, return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->writesize); } -static DEVICE_ATTR(writesize, S_IRUGO, mtd_writesize_show, NULL); +MTD_DEVICE_ATTR_RO(writesize); static ssize_t mtd_subpagesize_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -185,7 +185,7 @@ static ssize_t mtd_subpagesize_show(struct device *dev, return sysfs_emit(buf, "%u\n", subpagesize); } -static DEVICE_ATTR(subpagesize, S_IRUGO, mtd_subpagesize_show, NULL); +MTD_DEVICE_ATTR_RO(subpagesize); static ssize_t mtd_oobsize_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -194,7 +194,7 @@ static ssize_t mtd_oobsize_show(struct device *dev, return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->oobsize); } -static DEVICE_ATTR(oobsize, S_IRUGO, mtd_oobsize_show, NULL); +MTD_DEVICE_ATTR_RO(oobsize); static ssize_t mtd_oobavail_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -203,7 +203,7 @@ static ssize_t mtd_oobavail_show(struct device *dev, return sysfs_emit(buf, "%u\n", mtd->oobavail); } -static DEVICE_ATTR(oobavail, S_IRUGO, mtd_oobavail_show, NULL); +MTD_DEVICE_ATTR_RO(oobavail); static ssize_t mtd_numeraseregions_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -212,8 +212,7 @@ static ssize_t mtd_numeraseregions_show(struct device *dev, return sysfs_emit(buf, "%u\n", mtd->numeraseregions); } -static DEVICE_ATTR(numeraseregions, S_IRUGO, mtd_numeraseregions_show, - NULL); +MTD_DEVICE_ATTR_RO(numeraseregions); static ssize_t mtd_name_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -222,7 +221,7 @@ static ssize_t mtd_name_show(struct device *dev, return sysfs_emit(buf, "%s\n", mtd->name); } -static DEVICE_ATTR(name, S_IRUGO, mtd_name_show, NULL); +MTD_DEVICE_ATTR_RO(name); static ssize_t mtd_ecc_strength_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -231,7 +230,7 @@ static ssize_t mtd_ecc_strength_show(struct device *dev, return sysfs_emit(buf, "%u\n", mtd->ecc_strength); } -static DEVICE_ATTR(ecc_strength, S_IRUGO, mtd_ecc_strength_show, NULL); +MTD_DEVICE_ATTR_RO(ecc_strength); static ssize_t mtd_bitflip_threshold_show(struct device *dev, struct device_attribute *attr, @@ -257,9 +256,7 @@ static ssize_t mtd_bitflip_threshold_store(struct device *dev, mtd->bitflip_threshold = bitflip_threshold; return count; } -static DEVICE_ATTR(bitflip_threshold, S_IRUGO | S_IWUSR, - mtd_bitflip_threshold_show, - mtd_bitflip_threshold_store); +MTD_DEVICE_ATTR_RW(bitflip_threshold); static ssize_t mtd_ecc_step_size_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -269,9 +266,9 @@ static ssize_t mtd_ecc_step_size_show(struct device *dev, return sysfs_emit(buf, "%u\n", mtd->ecc_step_size); } -static DEVICE_ATTR(ecc_step_size, S_IRUGO, mtd_ecc_step_size_show, NULL); +MTD_DEVICE_ATTR_RO(ecc_step_size); -static ssize_t mtd_ecc_stats_corrected_show(struct device *dev, +static ssize_t mtd_corrected_bits_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mtd_info *mtd = dev_get_drvdata(dev); @@ -279,10 +276,9 @@ static ssize_t mtd_ecc_stats_corrected_show(struct device *dev, return sysfs_emit(buf, "%u\n", ecc_stats->corrected); } -static DEVICE_ATTR(corrected_bits, S_IRUGO, - mtd_ecc_stats_corrected_show, NULL); +MTD_DEVICE_ATTR_RO(corrected_bits); /* ecc stats corrected */ -static ssize_t mtd_ecc_stats_errors_show(struct device *dev, +static ssize_t mtd_ecc_failures_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mtd_info *mtd = dev_get_drvdata(dev); @@ -290,9 +286,9 @@ static ssize_t mtd_ecc_stats_errors_show(struct device *dev, return sysfs_emit(buf, "%u\n", ecc_stats->failed); } -static DEVICE_ATTR(ecc_failures, S_IRUGO, mtd_ecc_stats_errors_show, NULL); +MTD_DEVICE_ATTR_RO(ecc_failures); /* ecc stats errors */ -static ssize_t mtd_badblocks_show(struct device *dev, +static ssize_t mtd_bad_blocks_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mtd_info *mtd = dev_get_drvdata(dev); @@ -300,9 +296,9 @@ static ssize_t mtd_badblocks_show(struct device *dev, return sysfs_emit(buf, "%u\n", ecc_stats->badblocks); } -static DEVICE_ATTR(bad_blocks, S_IRUGO, mtd_badblocks_show, NULL); +MTD_DEVICE_ATTR_RO(bad_blocks); -static ssize_t mtd_bbtblocks_show(struct device *dev, +static ssize_t mtd_bbt_blocks_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mtd_info *mtd = dev_get_drvdata(dev); @@ -310,7 +306,7 @@ static ssize_t mtd_bbtblocks_show(struct device *dev, return sysfs_emit(buf, "%u\n", ecc_stats->bbtblocks); } -static DEVICE_ATTR(bbt_blocks, S_IRUGO, mtd_bbtblocks_show, NULL); +MTD_DEVICE_ATTR_RO(bbt_blocks); static struct attribute *mtd_attrs[] = { &dev_attr_type.attr, -- cgit v1.2.3-59-g8ed1b From 88d1250267535b26106ca9582701bbad940cec01 Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Mon, 7 Jun 2021 05:39:09 +0200 Subject: mtd: devices: add support for microchip 48l640 EERAM The Microchip 48l640 is a 8KByte EERAM connected via SPI. Signed-off-by: Heiko Schocher Tested-by: Fabio Estevam Reported-by: kernel test robot Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210607033909.1424605-3-hs@denx.de --- drivers/mtd/devices/Kconfig | 6 + drivers/mtd/devices/Makefile | 1 + drivers/mtd/devices/mchp48l640.c | 373 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 drivers/mtd/devices/mchp48l640.c (limited to 'drivers/mtd') diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 0f4c2d823de8..79cb981ececc 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -89,6 +89,12 @@ config MTD_MCHP23K256 platform data, or a device tree description if you want to specify device partitioning +config MTD_MCHP48L640 + tristate "Microchip 48L640 EERAM" + depends on SPI_MASTER + help + This enables access to Microchip 48L640 EERAM chips, using SPI. + config MTD_SPEAR_SMI tristate "SPEAR MTD NOR Support through SMI controller" depends on PLAT_SPEAR || COMPILE_TEST diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 991c8d12c016..0362cf6bdc67 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_MTD_LART) += lart.o obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o obj-$(CONFIG_MTD_MCHP23K256) += mchp23k256.o +obj-$(CONFIG_MTD_MCHP48L640) += mchp48l640.o obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o obj-$(CONFIG_MTD_SST25L) += sst25l.o obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o diff --git a/drivers/mtd/devices/mchp48l640.c b/drivers/mtd/devices/mchp48l640.c new file mode 100644 index 000000000000..efc2003bd13a --- /dev/null +++ b/drivers/mtd/devices/mchp48l640.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Microchip 48L640 64 Kb SPI Serial EERAM + * + * Copyright Heiko Schocher + * + * datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20006055B.pdf + * + * we set continuous mode but reading/writing more bytes than + * pagesize seems to bring chip into state where readden values + * are wrong ... no idea why. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mchp48_caps { + unsigned int size; + unsigned int page_size; +}; + +struct mchp48l640_flash { + struct spi_device *spi; + struct mutex lock; + struct mtd_info mtd; + const struct mchp48_caps *caps; +}; + +#define MCHP48L640_CMD_WREN 0x06 +#define MCHP48L640_CMD_WRDI 0x04 +#define MCHP48L640_CMD_WRITE 0x02 +#define MCHP48L640_CMD_READ 0x03 +#define MCHP48L640_CMD_WRSR 0x01 +#define MCHP48L640_CMD_RDSR 0x05 + +#define MCHP48L640_STATUS_RDY 0x01 +#define MCHP48L640_STATUS_WEL 0x02 +#define MCHP48L640_STATUS_BP0 0x04 +#define MCHP48L640_STATUS_BP1 0x08 +#define MCHP48L640_STATUS_SWM 0x10 +#define MCHP48L640_STATUS_PRO 0x20 +#define MCHP48L640_STATUS_ASE 0x40 + +#define MCHP48L640_TIMEOUT 100 + +#define MAX_CMD_SIZE 0x10 + +#define to_mchp48l640_flash(x) container_of(x, struct mchp48l640_flash, mtd) + +static int mchp48l640_mkcmd(struct mchp48l640_flash *flash, u8 cmd, loff_t addr, char *buf) +{ + buf[0] = cmd; + buf[1] = addr >> 8; + buf[2] = addr; + + return 3; +} + +static int mchp48l640_read_status(struct mchp48l640_flash *flash, int *status) +{ + unsigned char cmd[2]; + int ret; + + cmd[0] = MCHP48L640_CMD_RDSR; + cmd[1] = 0x00; + mutex_lock(&flash->lock); + ret = spi_write_then_read(flash->spi, &cmd[0], 1, &cmd[1], 1); + mutex_unlock(&flash->lock); + if (!ret) + *status = cmd[1]; + dev_dbg(&flash->spi->dev, "read status ret: %d status: %x", ret, *status); + + return ret; +} + +static int mchp48l640_waitforbit(struct mchp48l640_flash *flash, int bit, bool set) +{ + int ret, status; + unsigned long deadline; + + deadline = jiffies + msecs_to_jiffies(MCHP48L640_TIMEOUT); + do { + ret = mchp48l640_read_status(flash, &status); + dev_dbg(&flash->spi->dev, "read status ret: %d bit: %x %sset status: %x", + ret, bit, (set ? "" : "not"), status); + if (ret) + return ret; + + if (set) { + if ((status & bit) == bit) + return 0; + } else { + if ((status & bit) == 0) + return 0; + } + + usleep_range(1000, 2000); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(&flash->spi->dev, "Timeout waiting for bit %x %s set in status register.", + bit, (set ? "" : "not")); + return -ETIMEDOUT; +} + +static int mchp48l640_write_prepare(struct mchp48l640_flash *flash, bool enable) +{ + unsigned char cmd[2]; + int ret; + + if (enable) + cmd[0] = MCHP48L640_CMD_WREN; + else + cmd[0] = MCHP48L640_CMD_WRDI; + + mutex_lock(&flash->lock); + ret = spi_write(flash->spi, cmd, 1); + mutex_unlock(&flash->lock); + + if (ret) + dev_err(&flash->spi->dev, "write %sable failed ret: %d", + (enable ? "en" : "dis"), ret); + + dev_dbg(&flash->spi->dev, "write %sable success ret: %d", + (enable ? "en" : "dis"), ret); + if (enable) + return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_WEL, true); + + return ret; +} + +static int mchp48l640_set_mode(struct mchp48l640_flash *flash) +{ + unsigned char cmd[2]; + int ret; + + ret = mchp48l640_write_prepare(flash, true); + if (ret) + return ret; + + cmd[0] = MCHP48L640_CMD_WRSR; + cmd[1] = MCHP48L640_STATUS_PRO; + + mutex_lock(&flash->lock); + ret = spi_write(flash->spi, cmd, 2); + mutex_unlock(&flash->lock); + if (ret) + dev_err(&flash->spi->dev, "Could not set continuous mode ret: %d", ret); + + return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_PRO, true); +} + +static int mchp48l640_wait_rdy(struct mchp48l640_flash *flash) +{ + return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_RDY, false); +}; + +static int mchp48l640_write_page(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + unsigned char *cmd; + int ret; + int cmdlen; + + cmd = kmalloc((3 + len), GFP_KERNEL | GFP_DMA); + if (!cmd) + return -ENOMEM; + + ret = mchp48l640_wait_rdy(flash); + if (ret) + goto fail; + + ret = mchp48l640_write_prepare(flash, true); + if (ret) + goto fail; + + mutex_lock(&flash->lock); + cmdlen = mchp48l640_mkcmd(flash, MCHP48L640_CMD_WRITE, to, cmd); + memcpy(&cmd[cmdlen], buf, len); + ret = spi_write(flash->spi, cmd, cmdlen + len); + mutex_unlock(&flash->lock); + if (!ret) + *retlen += len; + else + goto fail; + + ret = mchp48l640_waitforbit(flash, MCHP48L640_STATUS_WEL, false); + if (ret) + goto fail; + + kfree(cmd); + return 0; +fail: + kfree(cmd); + dev_err(&flash->spi->dev, "write fail with: %d", ret); + return ret; +}; + +static int mchp48l640_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + int ret; + size_t wlen = 0; + loff_t woff = to; + size_t ws; + size_t page_sz = flash->caps->page_size; + + /* + * we set PRO bit (page rollover), but writing length > page size + * does result in total chaos, so write in 32 byte chunks. + */ + while (wlen < len) { + ws = min((len - wlen), page_sz); + ret = mchp48l640_write_page(mtd, woff, ws, retlen, &buf[wlen]); + if (ret) + return ret; + wlen += ws; + woff += ws; + } + + return ret; +} + +static int mchp48l640_read_page(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + unsigned char *cmd; + int ret; + int cmdlen; + + cmd = kmalloc((3 + len), GFP_KERNEL | GFP_DMA); + if (!cmd) + return -ENOMEM; + + ret = mchp48l640_wait_rdy(flash); + if (ret) + goto fail; + + mutex_lock(&flash->lock); + cmdlen = mchp48l640_mkcmd(flash, MCHP48L640_CMD_READ, from, cmd); + ret = spi_write_then_read(flash->spi, cmd, cmdlen, buf, len); + mutex_unlock(&flash->lock); + if (!ret) + *retlen += len; + + return ret; + +fail: + kfree(cmd); + dev_err(&flash->spi->dev, "read fail with: %d", ret); + return ret; +} + +static int mchp48l640_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + int ret; + size_t wlen = 0; + loff_t woff = from; + size_t ws; + size_t page_sz = flash->caps->page_size; + + /* + * we set PRO bit (page rollover), but if read length > page size + * does result in total chaos in result ... + */ + while (wlen < len) { + ws = min((len - wlen), page_sz); + ret = mchp48l640_read_page(mtd, woff, ws, retlen, &buf[wlen]); + if (ret) + return ret; + wlen += ws; + woff += ws; + } + + return ret; +}; + +static const struct mchp48_caps mchp48l640_caps = { + .size = SZ_8K, + .page_size = 32, +}; + +static int mchp48l640_probe(struct spi_device *spi) +{ + struct mchp48l640_flash *flash; + struct flash_platform_data *data; + int err; + int status; + + flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL); + if (!flash) + return -ENOMEM; + + flash->spi = spi; + mutex_init(&flash->lock); + spi_set_drvdata(spi, flash); + + err = mchp48l640_read_status(flash, &status); + if (err) + return err; + + err = mchp48l640_set_mode(flash); + if (err) + return err; + + data = dev_get_platdata(&spi->dev); + + flash->caps = of_device_get_match_data(&spi->dev); + if (!flash->caps) + flash->caps = &mchp48l640_caps; + + mtd_set_of_node(&flash->mtd, spi->dev.of_node); + flash->mtd.dev.parent = &spi->dev; + flash->mtd.type = MTD_RAM; + flash->mtd.flags = MTD_CAP_RAM; + flash->mtd.writesize = flash->caps->page_size; + flash->mtd.size = flash->caps->size; + flash->mtd._read = mchp48l640_read; + flash->mtd._write = mchp48l640_write; + + err = mtd_device_register(&flash->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); + if (err) + return err; + + return 0; +} + +static int mchp48l640_remove(struct spi_device *spi) +{ + struct mchp48l640_flash *flash = spi_get_drvdata(spi); + + return mtd_device_unregister(&flash->mtd); +} + +static const struct of_device_id mchp48l640_of_table[] = { + { + .compatible = "microchip,48l640", + .data = &mchp48l640_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(of, mchp48l640_of_table); + +static struct spi_driver mchp48l640_driver = { + .driver = { + .name = "mchp48l640", + .of_match_table = of_match_ptr(mchp48l640_of_table), + }, + .probe = mchp48l640_probe, + .remove = mchp48l640_remove, +}; + +module_spi_driver(mchp48l640_driver); + +MODULE_DESCRIPTION("MTD SPI driver for Microchip 48l640 EERAM chips"); +MODULE_AUTHOR("Heiko Schocher "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:mchp48l640"); -- cgit v1.2.3-59-g8ed1b From cba8b3bc4ac210b46cfc13afbbcaabdf17e51de2 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 09:50:52 +0800 Subject: mtd: rfd_ftl: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610015052.14864-1-thunder.leizhen@huawei.com --- drivers/mtd/rfd_ftl.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c index cce3bf6f99b4..6e0d5ce9b010 100644 --- a/drivers/mtd/rfd_ftl.c +++ b/drivers/mtd/rfd_ftl.c @@ -192,11 +192,8 @@ static int scan_header(struct partition *part) part->sector_map = vmalloc(array_size(sizeof(u_long), part->sector_count)); - if (!part->sector_map) { - printk(KERN_ERR PREFIX "'%s': unable to allocate memory for " - "sector map", part->mbd.mtd->name); + if (!part->sector_map) goto err; - } for (i=0; isector_count; i++) part->sector_map[i] = -1; -- cgit v1.2.3-59-g8ed1b From 8ef029135c7b4e1ed4c424efe5cbd8d0088fe8ba Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:01:30 +0800 Subject: mtd: nftl: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610020130.14917-1-thunder.leizhen@huawei.com --- drivers/mtd/nftlmount.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nftlmount.c b/drivers/mtd/nftlmount.c index fd331bc15871..75e86ed3e678 100644 --- a/drivers/mtd/nftlmount.c +++ b/drivers/mtd/nftlmount.c @@ -188,17 +188,14 @@ device is already correct. /* memory alloc */ nftl->EUNtable = kmalloc_array(nftl->nb_blocks, sizeof(u16), GFP_KERNEL); - if (!nftl->EUNtable) { - printk(KERN_NOTICE "NFTL: allocation of EUNtable failed\n"); + if (!nftl->EUNtable) return -ENOMEM; - } nftl->ReplUnitTable = kmalloc_array(nftl->nb_blocks, sizeof(u16), GFP_KERNEL); if (!nftl->ReplUnitTable) { kfree(nftl->EUNtable); - printk(KERN_NOTICE "NFTL: allocation of ReplUnitTable failed\n"); return -ENOMEM; } -- cgit v1.2.3-59-g8ed1b From b0821cc5dea98b260ae0f356210d6afdf1eeb8ad Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:06:20 +0800 Subject: mtd: rawnand: sunxi: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610020620.14970-1-thunder.leizhen@huawei.com --- drivers/mtd/nand/raw/sunxi_nand.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 923a9e236fcf..ea953e31933e 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -1972,10 +1972,8 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, sunxi_nand = devm_kzalloc(dev, struct_size(sunxi_nand, sels, nsels), GFP_KERNEL); - if (!sunxi_nand) { - dev_err(dev, "could not allocate chip\n"); + if (!sunxi_nand) return -ENOMEM; - } sunxi_nand->nsels = nsels; -- cgit v1.2.3-59-g8ed1b From 185675232072b38bafc2f6cc7e2904aa3d477620 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:09:58 +0800 Subject: mtd: rawnand: atmel: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610020958.15023-1-thunder.leizhen@huawei.com --- drivers/mtd/nand/raw/atmel/nand-controller.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index 8aab1017b460..01b757ebde86 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -1629,10 +1629,8 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc, } nand = devm_kzalloc(nc->dev, struct_size(nand, cs, numcs), GFP_KERNEL); - if (!nand) { - dev_err(nc->dev, "Failed to allocate NAND object\n"); + if (!nand) return ERR_PTR(-ENOMEM); - } nand->numcs = numcs; -- cgit v1.2.3-59-g8ed1b From 313ea21aee18f5bb49cbfd767547c935f6d5a018 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:12:01 +0800 Subject: mtd: mtdoops: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610021201.15076-1-thunder.leizhen@huawei.com --- drivers/mtd/mtdoops.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index 862c4a889234..227df24387df 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c @@ -401,10 +401,8 @@ static int __init mtdoops_init(void) cxt->mtd_index = mtd_index; cxt->oops_buf = vmalloc(record_size); - if (!cxt->oops_buf) { - printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n"); + if (!cxt->oops_buf) return -ENOMEM; - } memset(cxt->oops_buf, 0xff, record_size); cxt->oops_buf_busy = 0; -- cgit v1.2.3-59-g8ed1b From 4a7bd5e96627e019891170c725b061d8afe0ae87 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:14:00 +0800 Subject: mtd: sun_uflash: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610021400.15132-1-thunder.leizhen@huawei.com --- drivers/mtd/maps/sun_uflash.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/maps/sun_uflash.c b/drivers/mtd/maps/sun_uflash.c index f9cfb084c029..6c0c91bfec05 100644 --- a/drivers/mtd/maps/sun_uflash.c +++ b/drivers/mtd/maps/sun_uflash.c @@ -62,10 +62,8 @@ int uflash_devinit(struct platform_device *op, struct device_node *dp) } up = kzalloc(sizeof(struct uflash_dev), GFP_KERNEL); - if (!up) { - printk(KERN_ERR PFX "Cannot allocate struct uflash_dev\n"); + if (!up) return -ENOMEM; - } /* copy defaults and tweak parameters */ memcpy(&up->map, &uflash_map_templ, sizeof(uflash_map_templ)); -- cgit v1.2.3-59-g8ed1b From 4883307c6d8e59018a661ad1fa1e9c62328d6150 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:21:00 +0800 Subject: mtd: ichxrom: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610022100.15185-1-thunder.leizhen@huawei.com --- drivers/mtd/maps/ichxrom.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/maps/ichxrom.c b/drivers/mtd/maps/ichxrom.c index fda72c5fd8f9..c8b2793691db 100644 --- a/drivers/mtd/maps/ichxrom.c +++ b/drivers/mtd/maps/ichxrom.c @@ -213,10 +213,8 @@ static int __init ichxrom_init_one(struct pci_dev *pdev, if (!map) { map = kmalloc(sizeof(*map), GFP_KERNEL); - } - if (!map) { - printk(KERN_ERR MOD_NAME ": kmalloc failed"); - goto out; + if (!map) + goto out; } memset(map, 0, sizeof(*map)); INIT_LIST_HEAD(&map->list); -- cgit v1.2.3-59-g8ed1b From bb89d137b2ed2a94df83cd468b0b1d473811c6bb Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:22:39 +0800 Subject: mtd: esb2rom: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610022239.15238-1-thunder.leizhen@huawei.com --- drivers/mtd/maps/esb2rom.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/maps/esb2rom.c b/drivers/mtd/maps/esb2rom.c index 85e14150a073..15d5b76ff504 100644 --- a/drivers/mtd/maps/esb2rom.c +++ b/drivers/mtd/maps/esb2rom.c @@ -277,11 +277,10 @@ static int __init esb2rom_init_one(struct pci_dev *pdev, unsigned long offset; int i; - if (!map) - map = kmalloc(sizeof(*map), GFP_KERNEL); if (!map) { - printk(KERN_ERR MOD_NAME ": kmalloc failed"); - goto out; + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) + goto out; } memset(map, 0, sizeof(*map)); INIT_LIST_HEAD(&map->list); -- cgit v1.2.3-59-g8ed1b From 042bf272d5fcb2edef48b5cc44882adef2519d72 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:24:16 +0800 Subject: mtd: ck804xrom: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610022416.15291-1-thunder.leizhen@huawei.com --- drivers/mtd/maps/ck804xrom.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/maps/ck804xrom.c b/drivers/mtd/maps/ck804xrom.c index 460494212f6a..c0216bc740cc 100644 --- a/drivers/mtd/maps/ck804xrom.c +++ b/drivers/mtd/maps/ck804xrom.c @@ -217,12 +217,10 @@ static int __init ck804xrom_init_one(struct pci_dev *pdev, unsigned long offset; int i; - if (!map) - map = kmalloc(sizeof(*map), GFP_KERNEL); - if (!map) { - printk(KERN_ERR MOD_NAME ": kmalloc failed"); - goto out; + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) + goto out; } memset(map, 0, sizeof(*map)); INIT_LIST_HEAD(&map->list); -- cgit v1.2.3-59-g8ed1b From 3d2fac0eeec2a3699a7747a9322723b911276ec0 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:26:31 +0800 Subject: mtd: amd76xrom: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610022631.15344-1-thunder.leizhen@huawei.com --- drivers/mtd/maps/amd76xrom.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/maps/amd76xrom.c b/drivers/mtd/maps/amd76xrom.c index 42a95ba40f2c..281fcbaa74e7 100644 --- a/drivers/mtd/maps/amd76xrom.c +++ b/drivers/mtd/maps/amd76xrom.c @@ -189,10 +189,8 @@ static int amd76xrom_init_one(struct pci_dev *pdev, if (!map) { map = kmalloc(sizeof(*map), GFP_KERNEL); - } - if (!map) { - printk(KERN_ERR MOD_NAME ": kmalloc failed"); - goto out; + if (!map) + goto out; } memset(map, 0, sizeof(*map)); INIT_LIST_HEAD(&map->list); -- cgit v1.2.3-59-g8ed1b From 828ed786554f3e8d772f859c9c0ea6472558aba1 Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 10 Jun 2021 10:28:50 +0800 Subject: mtd: inftl: remove unnecessary oom message Fixes scripts/checkpatch.pl warning: WARNING: Possible unnecessary 'out of memory' message Remove it can help us save a bit of memory. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610022850.15397-1-thunder.leizhen@huawei.com --- drivers/mtd/inftlmount.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c index 39d024118610..6276daa296da 100644 --- a/drivers/mtd/inftlmount.c +++ b/drivers/mtd/inftlmount.c @@ -259,20 +259,13 @@ static int find_boot_record(struct INFTLrecord *inftl) /* Memory alloc */ inftl->PUtable = kmalloc_array(inftl->nb_blocks, sizeof(u16), GFP_KERNEL); - if (!inftl->PUtable) { - printk(KERN_WARNING "INFTL: allocation of PUtable " - "failed (%zd bytes)\n", - inftl->nb_blocks * sizeof(u16)); + if (!inftl->PUtable) return -ENOMEM; - } inftl->VUtable = kmalloc_array(inftl->nb_blocks, sizeof(u16), GFP_KERNEL); if (!inftl->VUtable) { kfree(inftl->PUtable); - printk(KERN_WARNING "INFTL: allocation of VUtable " - "failed (%zd bytes)\n", - inftl->nb_blocks * sizeof(u16)); return -ENOMEM; } @@ -558,12 +551,8 @@ int INFTL_mount(struct INFTLrecord *s) /* Temporary buffer to store ANAC numbers. */ ANACtable = kcalloc(s->nb_blocks, sizeof(u8), GFP_KERNEL); - if (!ANACtable) { - printk(KERN_WARNING "INFTL: allocation of ANACtable " - "failed (%zd bytes)\n", - s->nb_blocks * sizeof(u8)); + if (!ANACtable) return -ENOMEM; - } /* * First pass is to explore each physical unit, and construct the -- cgit v1.2.3-59-g8ed1b From 6aa12138cd9aeb01308a3da8b23451dcf7f00d52 Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Tue, 8 Jun 2021 20:34:59 +0800 Subject: mtd: Convert list_for_each to entry variant convert list_for_each() to list_for_each_entry() where applicable. Reported-by: Hulk Robot Signed-off-by: Zou Wei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1623155699-61935-1-git-send-email-zou_wei@huawei.com --- drivers/mtd/chips/chipreg.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/chips/chipreg.c b/drivers/mtd/chips/chipreg.c index ff86373d7d24..a05e103682a4 100644 --- a/drivers/mtd/chips/chipreg.c +++ b/drivers/mtd/chips/chipreg.c @@ -31,14 +31,11 @@ void unregister_mtd_chip_driver(struct mtd_chip_driver *drv) static struct mtd_chip_driver *get_mtd_chip_driver (const char *name) { - struct list_head *pos; struct mtd_chip_driver *ret = NULL, *this; spin_lock(&chip_drvs_lock); - list_for_each(pos, &chip_drvs_list) { - this = list_entry(pos, typeof(*this), list); - + list_for_each_entry(this, &chip_drvs_list, list) { if (!strcmp(this->name, name)) { ret = this; break; -- cgit v1.2.3-59-g8ed1b From 65b6d89d45a77256b743f421d109d469baefa688 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 3 May 2021 17:56:50 +0200 Subject: mtd: spi-nor: sfdp: save a copy of the SFDP data Due to possible mode switching to 8D-8D-8D, it might not be possible to read the SFDP after the initial probe. To be able to dump the SFDP via sysfs afterwards, make a complete copy of it. Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Tested-by: Heiko Thiery Reviewed-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.h | 10 ++++++++ drivers/mtd/spi-nor/sfdp.c | 58 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 2 ++ 3 files changed, 70 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 9398a8738857..f6dc28091c6d 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -461,6 +461,16 @@ struct spi_nor_manufacturer { const struct spi_nor_fixups *fixups; }; +/** + * struct sfdp - SFDP data + * @num_dwords: number of entries in the dwords array + * @dwords: array of double words of the SFDP data + */ +struct sfdp { + size_t num_dwords; + u32 *dwords; +}; + /* Manufacturer drivers. */ extern const struct spi_nor_manufacturer spi_nor_atmel; extern const struct spi_nor_manufacturer spi_nor_catalyst; diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c index 23c28e91f698..c500c2118a5d 100644 --- a/drivers/mtd/spi-nor/sfdp.c +++ b/drivers/mtd/spi-nor/sfdp.c @@ -16,6 +16,7 @@ (((p)->parameter_table_pointer[2] << 16) | \ ((p)->parameter_table_pointer[1] << 8) | \ ((p)->parameter_table_pointer[0] << 0)) +#define SFDP_PARAM_HEADER_PARAM_LEN(p) ((p)->length * 4) #define SFDP_BFPT_ID 0xff00 /* Basic Flash Parameter Table */ #define SFDP_SECTOR_MAP_ID 0xff81 /* Sector Map Table */ @@ -1245,6 +1246,8 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) struct sfdp_parameter_header *param_headers = NULL; struct sfdp_header header; struct device *dev = nor->dev; + struct sfdp *sfdp; + size_t sfdp_size; size_t psize; int i, err; @@ -1267,6 +1270,9 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) bfpt_header->major != SFDP_JESD216_MAJOR) return -EINVAL; + sfdp_size = SFDP_PARAM_HEADER_PTP(bfpt_header) + + SFDP_PARAM_HEADER_PARAM_LEN(bfpt_header); + /* * Allocate memory then read all parameter headers with a single * Read SFDP command. These parameter headers will actually be parsed @@ -1293,6 +1299,58 @@ int spi_nor_parse_sfdp(struct spi_nor *nor) } } + /* + * Cache the complete SFDP data. It is not (easily) possible to fetch + * SFDP after probe time and we need it for the sysfs access. + */ + for (i = 0; i < header.nph; i++) { + param_header = ¶m_headers[i]; + sfdp_size = max_t(size_t, sfdp_size, + SFDP_PARAM_HEADER_PTP(param_header) + + SFDP_PARAM_HEADER_PARAM_LEN(param_header)); + } + + /* + * Limit the total size to a reasonable value to avoid allocating too + * much memory just of because the flash returned some insane values. + */ + if (sfdp_size > PAGE_SIZE) { + dev_dbg(dev, "SFDP data (%zu) too big, truncating\n", + sfdp_size); + sfdp_size = PAGE_SIZE; + } + + sfdp = devm_kzalloc(dev, sizeof(*sfdp), GFP_KERNEL); + if (!sfdp) { + err = -ENOMEM; + goto exit; + } + + /* + * The SFDP is organized in chunks of DWORDs. Thus, in theory, the + * sfdp_size should be a multiple of DWORDs. But in case a flash + * is not spec compliant, make sure that we have enough space to store + * the complete SFDP data. + */ + sfdp->num_dwords = DIV_ROUND_UP(sfdp_size, sizeof(*sfdp->dwords)); + sfdp->dwords = devm_kcalloc(dev, sfdp->num_dwords, + sizeof(*sfdp->dwords), GFP_KERNEL); + if (!sfdp->dwords) { + err = -ENOMEM; + devm_kfree(dev, sfdp); + goto exit; + } + + err = spi_nor_read_sfdp(nor, 0, sfdp_size, sfdp->dwords); + if (err < 0) { + dev_dbg(dev, "failed to read SFDP data\n"); + devm_kfree(dev, sfdp->dwords); + devm_kfree(dev, sfdp); + goto exit; + } + + nor->sfdp = sfdp; + /* * Check other parameter headers to get the latest revision of * the basic flash parameter table. diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 98ed91b529ea..f67457748ed8 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -383,6 +383,7 @@ struct spi_nor_flash_parameter; * @read_proto: the SPI protocol for read operations * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations + * @sfdp: the SFDP data of the flash * @controller_ops: SPI NOR controller driver specific operations. * @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings. * The structure includes legacy flash parameters and @@ -412,6 +413,7 @@ struct spi_nor { bool sst_write_second; u32 flags; enum spi_nor_cmd_ext cmd_ext_type; + struct sfdp *sfdp; const struct spi_nor_controller_ops *controller_ops; -- cgit v1.2.3-59-g8ed1b From 36ac0228626585ba718186b9db2e5986a198152c Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 3 May 2021 17:56:51 +0200 Subject: mtd: spi-nor: add initial sysfs support Add support to show the manufacturer, the partname and JEDEC identifier as well as to dump the SFDP table. Not all flashes list their SFDP table contents in their datasheet. So having that is useful. It might also be helpful in bug reports from users. Signed-off-by: Michael Walle Signed-off-by: Vignesh Raghavendra Acked-by: Pratyush Yadav --- .../ABI/testing/sysfs-bus-spi-devices-spi-nor | 31 ++++++++ drivers/mtd/spi-nor/Makefile | 2 +- drivers/mtd/spi-nor/core.c | 1 + drivers/mtd/spi-nor/core.h | 2 + drivers/mtd/spi-nor/sysfs.c | 93 ++++++++++++++++++++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-spi-devices-spi-nor create mode 100644 drivers/mtd/spi-nor/sysfs.c (limited to 'drivers/mtd') diff --git a/Documentation/ABI/testing/sysfs-bus-spi-devices-spi-nor b/Documentation/ABI/testing/sysfs-bus-spi-devices-spi-nor new file mode 100644 index 000000000000..d76cd3946434 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-spi-devices-spi-nor @@ -0,0 +1,31 @@ +What: /sys/bus/spi/devices/.../spi-nor/jedec_id +Date: April 2021 +KernelVersion: 5.14 +Contact: linux-mtd@lists.infradead.org +Description: (RO) The JEDEC ID of the SPI NOR flash as reported by the + flash device. + + +What: /sys/bus/spi/devices/.../spi-nor/manufacturer +Date: April 2021 +KernelVersion: 5.14 +Contact: linux-mtd@lists.infradead.org +Description: (RO) Manufacturer of the SPI NOR flash. + + +What: /sys/bus/spi/devices/.../spi-nor/partname +Date: April 2021 +KernelVersion: 5.14 +Contact: linux-mtd@lists.infradead.org +Description: (RO) Part name of the SPI NOR flash. + + +What: /sys/bus/spi/devices/.../spi-nor/sfdp +Date: April 2021 +KernelVersion: 5.14 +Contact: linux-mtd@lists.infradead.org +Description: (RO) This attribute is only present if the SPI NOR flash + device supports the "Read SFDP" command (5Ah). + + If present, it contains the complete SFDP (serial flash + discoverable parameters) binary data of the flash. diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 136f245c91dc..6b904e439372 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -spi-nor-objs := core.o sfdp.o swp.o otp.o +spi-nor-objs := core.o sfdp.o swp.o otp.o sysfs.o spi-nor-objs += atmel.o spi-nor-objs += catalyst.o spi-nor-objs += eon.o diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index a21b0085de05..970ed6e3f3ba 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3459,6 +3459,7 @@ static struct spi_mem_driver spi_nor_driver = { .driver = { .name = "spi-nor", .of_match_table = spi_nor_of_table, + .dev_groups = spi_nor_sysfs_groups, }, .id_table = spi_nor_dev_ids, }, diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index f6dc28091c6d..3348e1dd1445 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -490,6 +490,8 @@ extern const struct spi_nor_manufacturer spi_nor_winbond; extern const struct spi_nor_manufacturer spi_nor_xilinx; extern const struct spi_nor_manufacturer spi_nor_xmc; +extern const struct attribute_group *spi_nor_sysfs_groups[]; + void spi_nor_spimem_setup_op(const struct spi_nor *nor, struct spi_mem_op *op, const enum spi_nor_protocol proto); diff --git a/drivers/mtd/spi-nor/sysfs.c b/drivers/mtd/spi-nor/sysfs.c new file mode 100644 index 000000000000..9aec9d8a98ad --- /dev/null +++ b/drivers/mtd/spi-nor/sysfs.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include "core.h" + +static ssize_t manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_mem *spimem = spi_get_drvdata(spi); + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + return sysfs_emit(buf, "%s\n", nor->manufacturer->name); +} +static DEVICE_ATTR_RO(manufacturer); + +static ssize_t partname_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_mem *spimem = spi_get_drvdata(spi); + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + return sysfs_emit(buf, "%s\n", nor->info->name); +} +static DEVICE_ATTR_RO(partname); + +static ssize_t jedec_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_mem *spimem = spi_get_drvdata(spi); + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + return sysfs_emit(buf, "%*phN\n", nor->info->id_len, nor->info->id); +} +static DEVICE_ATTR_RO(jedec_id); + +static struct attribute *spi_nor_sysfs_entries[] = { + &dev_attr_manufacturer.attr, + &dev_attr_partname.attr, + &dev_attr_jedec_id.attr, + NULL +}; + +static ssize_t sfdp_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct spi_device *spi = to_spi_device(kobj_to_dev(kobj)); + struct spi_mem *spimem = spi_get_drvdata(spi); + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + struct sfdp *sfdp = nor->sfdp; + size_t sfdp_size = sfdp->num_dwords * sizeof(*sfdp->dwords); + + return memory_read_from_buffer(buf, count, &off, nor->sfdp->dwords, + sfdp_size); +} +static BIN_ATTR_RO(sfdp, 0); + +static struct bin_attribute *spi_nor_sysfs_bin_entries[] = { + &bin_attr_sfdp, + NULL +}; + +static umode_t spi_nor_sysfs_is_bin_visible(struct kobject *kobj, + struct bin_attribute *attr, int n) +{ + struct spi_device *spi = to_spi_device(kobj_to_dev(kobj)); + struct spi_mem *spimem = spi_get_drvdata(spi); + struct spi_nor *nor = spi_mem_get_drvdata(spimem); + + if (attr == &bin_attr_sfdp && nor->sfdp) + return 0444; + + return 0; +} + +static const struct attribute_group spi_nor_sysfs_group = { + .name = "spi-nor", + .is_bin_visible = spi_nor_sysfs_is_bin_visible, + .attrs = spi_nor_sysfs_entries, + .bin_attrs = spi_nor_sysfs_bin_entries, +}; + +const struct attribute_group *spi_nor_sysfs_groups[] = { + &spi_nor_sysfs_group, + NULL +}; -- cgit v1.2.3-59-g8ed1b From cf67edce22c5d7edc6cad64dbeb1d5d7d0099837 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 27 May 2021 10:45:48 +0200 Subject: mtd: rawnand: arasan: Use the right DMA mask Xilinx ZynqMP SoC and the Arasan controller support 64-bit DMA addressing. Define the right mask otherwise the default is 32 and some accesses may overflow the default mask. Reported-by: Jorge Courett Signed-off-by: Miquel Raynal Tested-by: Jorge Courett Link: https://lore.kernel.org/linux-mtd/20210527084548.208429-1-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 97e5a336a760..f9b5b140720b 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -1398,6 +1398,10 @@ static int anfc_probe(struct platform_device *pdev) if (ret) goto disable_controller_clk; + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + goto disable_bus_clk; + ret = anfc_parse_cs(nfc); if (ret) goto disable_bus_clk; -- cgit v1.2.3-59-g8ed1b From 55e06ae25ea23516026fd0b2b70d5692037b86a8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 27 May 2021 10:49:13 +0200 Subject: mtd: rawnand: onfi: Fix endianness when reading NV-DDR values Without the use of le16_to_cpu(), these accesses would have been wrong on a big-endian machine. Reported-by: kernel test robot Fixes: 45606518f961 ("mtd: rawnand: Add onfi_fill_nvddr_interface_config() helper") Fixes: 9310668fb60a ("mtd: rawnand: Retrieve NV-DDR timing modes from the ONFI parameter page") Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210527084913.208635-1-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/nand_onfi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c index 8e4677f2ba76..7586befce7f9 100644 --- a/drivers/mtd/nand/raw/nand_onfi.c +++ b/drivers/mtd/nand/raw/nand_onfi.c @@ -315,10 +315,10 @@ int nand_onfi_detect(struct nand_chip *chip) onfi->tBERS = le16_to_cpu(p->t_bers); onfi->tR = le16_to_cpu(p->t_r); onfi->tCCS = le16_to_cpu(p->t_ccs); - onfi->fast_tCAD = p->nvddr_nvddr2_features & BIT(0); + onfi->fast_tCAD = le16_to_cpu(p->nvddr_nvddr2_features) & BIT(0); onfi->sdr_timing_modes = le16_to_cpu(p->sdr_timing_modes); - if (p->features & ONFI_FEATURE_NV_DDR) - onfi->nvddr_timing_modes = p->nvddr_timing_modes; + if (le16_to_cpu(p->features) & ONFI_FEATURE_NV_DDR) + onfi->nvddr_timing_modes = le16_to_cpu(p->nvddr_timing_modes); onfi->vendor_revision = le16_to_cpu(p->vendor_revision); memcpy(onfi->vendor, p->vendor, sizeof(p->vendor)); chip->parameters.onfi = onfi; -- cgit v1.2.3-59-g8ed1b From 23739c34f56c7eaa62d00b70dc8bf31b8244ef83 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 27 May 2021 10:49:58 +0200 Subject: mtd: rawnand: arasan: Rename the data interface register There are 2 timing registers: - "data interface" - "timings" So far, the "data interface" register was named "timings" which begins misleading when bringing support for the "timings" register. Rename it to "data_iface". Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210527084959.208804-1-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index f9b5b140720b..5bcc680984f0 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -144,7 +144,7 @@ struct anfc_op { * @rb: Ready-busy line * @page_sz: Register value of the page_sz field to use * @clk: Expected clock frequency to use - * @timings: Data interface timing mode to use + * @data_iface: Data interface timing mode to use * @ecc_conf: Hardware ECC configuration value * @strength: Register value of the ECC strength * @raddr_cycles: Row address cycle information @@ -164,7 +164,7 @@ struct anand { unsigned int rb; unsigned int page_sz; unsigned long clk; - u32 timings; + u32 data_iface; u32 ecc_conf; u32 strength; u16 raddr_cycles; @@ -331,7 +331,7 @@ static int anfc_select_target(struct nand_chip *chip, int target) anfc_assert_cs(nfc, nfc_cs_idx); /* Update the controller timings and the potential ECC configuration */ - writel_relaxed(anand->timings, nfc->base + DATA_INTERFACE_REG); + writel_relaxed(anand->data_iface, nfc->base + DATA_INTERFACE_REG); /* Update clock frequency */ if (nfc->cur_clk != anand->clk) { @@ -970,11 +970,11 @@ static int anfc_setup_interface(struct nand_chip *chip, int target, return 0; if (nand_interface_is_sdr(conf)) - anand->timings = DIFACE_SDR | - DIFACE_SDR_MODE(conf->timings.mode); + anand->data_iface = DIFACE_SDR | + DIFACE_SDR_MODE(conf->timings.mode); else - anand->timings = DIFACE_NVDDR | - DIFACE_DDR_MODE(conf->timings.mode); + anand->data_iface = DIFACE_NVDDR | + DIFACE_DDR_MODE(conf->timings.mode); anand->clk = ANFC_XLNX_SDR_DFLT_CORE_CLK; -- cgit v1.2.3-59-g8ed1b From 76e12c104f627a24487fe1bfa0ed8563e36a5ce2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 27 May 2021 10:49:59 +0200 Subject: mtd: rawnand: arasan: Finer grain NV-DDR configuration Add support for the timings register which may improve a bit the overall throughput. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210527084959.208804-2-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 56 ++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 5bcc680984f0..9cbcc698c64d 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -72,6 +72,15 @@ #define FLASH_STS_REG 0x28 +#define TIMING_REG 0x2C +#define TCCS_TIME_500NS 0 +#define TCCS_TIME_300NS 3 +#define TCCS_TIME_200NS 2 +#define TCCS_TIME_100NS 1 +#define FAST_TCAD BIT(2) +#define DQS_BUFF_SEL_IN(x) FIELD_PREP(GENMASK(6, 3), (x)) +#define DQS_BUFF_SEL_OUT(x) FIELD_PREP(GENMASK(18, 15), (x)) + #define DATA_PORT_REG 0x30 #define ECC_CONF_REG 0x34 @@ -145,6 +154,7 @@ struct anfc_op { * @page_sz: Register value of the page_sz field to use * @clk: Expected clock frequency to use * @data_iface: Data interface timing mode to use + * @timings: NV-DDR specific timings to use * @ecc_conf: Hardware ECC configuration value * @strength: Register value of the ECC strength * @raddr_cycles: Row address cycle information @@ -165,6 +175,7 @@ struct anand { unsigned int page_sz; unsigned long clk; u32 data_iface; + u32 timings; u32 ecc_conf; u32 strength; u16 raddr_cycles; @@ -332,6 +343,7 @@ static int anfc_select_target(struct nand_chip *chip, int target) /* Update the controller timings and the potential ECC configuration */ writel_relaxed(anand->data_iface, nfc->base + DATA_INTERFACE_REG); + writel_relaxed(anand->timings, nfc->base + TIMING_REG); /* Update clock frequency */ if (nfc->cur_clk != anand->clk) { @@ -955,6 +967,7 @@ static int anfc_setup_interface(struct nand_chip *chip, int target, struct device_node *np = nfc->dev->of_node; const struct nand_sdr_timings *sdr; const struct nand_nvddr_timings *nvddr; + unsigned int tccs_min, dqs_mode, fast_tcad; if (nand_interface_is_nvddr(conf)) { nvddr = nand_get_nvddr_timings(conf); @@ -969,13 +982,52 @@ static int anfc_setup_interface(struct nand_chip *chip, int target, if (target < 0) return 0; - if (nand_interface_is_sdr(conf)) + if (nand_interface_is_sdr(conf)) { anand->data_iface = DIFACE_SDR | DIFACE_SDR_MODE(conf->timings.mode); - else + anand->timings = 0; + } else { anand->data_iface = DIFACE_NVDDR | DIFACE_DDR_MODE(conf->timings.mode); + if (conf->timings.nvddr.tCCS_min <= 100000) + tccs_min = TCCS_TIME_100NS; + else if (conf->timings.nvddr.tCCS_min <= 200000) + tccs_min = TCCS_TIME_200NS; + else if (conf->timings.nvddr.tCCS_min <= 300000) + tccs_min = TCCS_TIME_300NS; + else + tccs_min = TCCS_TIME_500NS; + + fast_tcad = 0; + if (conf->timings.nvddr.tCAD_min < 45000) + fast_tcad = FAST_TCAD; + + switch (conf->timings.mode) { + case 5: + case 4: + dqs_mode = 2; + break; + case 3: + dqs_mode = 3; + break; + case 2: + dqs_mode = 4; + break; + case 1: + dqs_mode = 5; + break; + case 0: + default: + dqs_mode = 6; + break; + } + + anand->timings = tccs_min | fast_tcad | + DQS_BUFF_SEL_IN(dqs_mode) | + DQS_BUFF_SEL_OUT(dqs_mode); + } + anand->clk = ANFC_XLNX_SDR_DFLT_CORE_CLK; /* -- cgit v1.2.3-59-g8ed1b From ae94c49527aa9bd3b563349adc4b5617747ca6bd Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Tue, 1 Jun 2021 20:58:14 +0800 Subject: mtd: rawnand: marvell: add missing clk_disable_unprepare() on error in marvell_nfc_resume() Add clk_disable_unprepare() on error path in marvell_nfc_resume(). Fixes: bd9c3f9b3c00 ("mtd: rawnand: marvell: add suspend and resume hooks") Reported-by: Hulk Robot Signed-off-by: Yang Yingliang Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210601125814.3260364-1-yangyingliang@huawei.com --- drivers/mtd/nand/raw/marvell_nand.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c index 79da6b02e209..f83525a1ab0e 100644 --- a/drivers/mtd/nand/raw/marvell_nand.c +++ b/drivers/mtd/nand/raw/marvell_nand.c @@ -3030,8 +3030,10 @@ static int __maybe_unused marvell_nfc_resume(struct device *dev) return ret; ret = clk_prepare_enable(nfc->reg_clk); - if (ret < 0) + if (ret < 0) { + clk_disable_unprepare(nfc->core_clk); return ret; + } /* * Reset nfc->selected_chip so the next command will cause the timing -- cgit v1.2.3-59-g8ed1b From ff0cd841d2a60ea6b711e35dce767dcf0aa294e5 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Wed, 2 Jun 2021 11:49:11 +0200 Subject: mtd: spinand: add spinand_read_cfg() helper Put REG_CFG reading code in spinand_read_cfg(). This function will be needed by the future SPI-NAND resume ops. Signed-off-by: Patrice Chotard Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210602094913.26472-2-patrice.chotard@foss.st.com --- drivers/mtd/nand/spi/core.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 54ae540bc66b..a559f6107f78 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -138,20 +138,12 @@ int spinand_select_target(struct spinand_device *spinand, unsigned int target) return 0; } -static int spinand_init_cfg_cache(struct spinand_device *spinand) +static int spinand_read_cfg(struct spinand_device *spinand) { struct nand_device *nand = spinand_to_nand(spinand); - struct device *dev = &spinand->spimem->spi->dev; unsigned int target; int ret; - spinand->cfg_cache = devm_kcalloc(dev, - nand->memorg.ntargets, - sizeof(*spinand->cfg_cache), - GFP_KERNEL); - if (!spinand->cfg_cache) - return -ENOMEM; - for (target = 0; target < nand->memorg.ntargets; target++) { ret = spinand_select_target(spinand, target); if (ret) @@ -170,6 +162,21 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand) return 0; } +static int spinand_init_cfg_cache(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct device *dev = &spinand->spimem->spi->dev; + + spinand->cfg_cache = devm_kcalloc(dev, + nand->memorg.ntargets, + sizeof(*spinand->cfg_cache), + GFP_KERNEL); + if (!spinand->cfg_cache) + return -ENOMEM; + + return 0; +} + static int spinand_init_quad_enable(struct spinand_device *spinand) { bool enable = false; @@ -1117,6 +1124,10 @@ static int spinand_init(struct spinand_device *spinand) if (ret) goto err_free_bufs; + ret = spinand_read_cfg(spinand); + if (ret) + goto err_free_bufs; + ret = spinand_init_quad_enable(spinand); if (ret) goto err_free_bufs; -- cgit v1.2.3-59-g8ed1b From 41e005c23ee7689ae36b49bde4fec08e89ed121d Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Wed, 2 Jun 2021 11:49:12 +0200 Subject: mtd: spinand: Add spinand_init_flash() helper Add spinand_init_flash() helper which implement all needed init for future SPI-NAND resume ops. Signed-off-by: Patrice Chotard Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210602094913.26472-3-patrice.chotard@foss.st.com --- drivers/mtd/nand/spi/core.c | 74 +++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 29 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index a559f6107f78..82a8f0f2c157 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1086,12 +1086,55 @@ static int spinand_detect(struct spinand_device *spinand) return 0; } +static int spinand_init_flash(struct spinand_device *spinand) +{ + struct device *dev = &spinand->spimem->spi->dev; + struct nand_device *nand = spinand_to_nand(spinand); + int ret, i; + + ret = spinand_read_cfg(spinand); + if (ret) + return ret; + + ret = spinand_init_quad_enable(spinand); + if (ret) + return ret; + + ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0); + if (ret) + return ret; + + ret = spinand_manufacturer_init(spinand); + if (ret) { + dev_err(dev, + "Failed to initialize the SPI NAND chip (err = %d)\n", + ret); + return ret; + } + + /* After power up, all blocks are locked, so unlock them here. */ + for (i = 0; i < nand->memorg.ntargets; i++) { + ret = spinand_select_target(spinand, i); + if (ret) + break; + + ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED); + if (ret) + break; + } + + if (ret) + spinand_manufacturer_cleanup(spinand); + + return ret; +} + static int spinand_init(struct spinand_device *spinand) { struct device *dev = &spinand->spimem->spi->dev; struct mtd_info *mtd = spinand_to_mtd(spinand); struct nand_device *nand = mtd_to_nanddev(mtd); - int ret, i; + int ret; /* * We need a scratch buffer because the spi_mem interface requires that @@ -1124,26 +1167,10 @@ static int spinand_init(struct spinand_device *spinand) if (ret) goto err_free_bufs; - ret = spinand_read_cfg(spinand); - if (ret) - goto err_free_bufs; - - ret = spinand_init_quad_enable(spinand); - if (ret) - goto err_free_bufs; - - ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0); + ret = spinand_init_flash(spinand); if (ret) goto err_free_bufs; - ret = spinand_manufacturer_init(spinand); - if (ret) { - dev_err(dev, - "Failed to initialize the SPI NAND chip (err = %d)\n", - ret); - goto err_free_bufs; - } - ret = spinand_create_dirmaps(spinand); if (ret) { dev_err(dev, @@ -1152,17 +1179,6 @@ static int spinand_init(struct spinand_device *spinand) goto err_manuf_cleanup; } - /* After power up, all blocks are locked, so unlock them here. */ - for (i = 0; i < nand->memorg.ntargets; i++) { - ret = spinand_select_target(spinand, i); - if (ret) - goto err_manuf_cleanup; - - ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED); - if (ret) - goto err_manuf_cleanup; - } - ret = nanddev_init(nand, &spinand_ops, THIS_MODULE); if (ret) goto err_manuf_cleanup; -- cgit v1.2.3-59-g8ed1b From f145b9dcf998427f1e3f96f61b6ca9afd629dfda Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Wed, 2 Jun 2021 11:49:13 +0200 Subject: mtd: spinand: add SPI-NAND MTD resume handler After power up, all SPI NAND's blocks are locked. Only read operations are allowed, write and erase operations are forbidden. The SPI NAND framework unlocks all the blocks during its initialization. During a standby low power, the memory is powered down, losing its configuration. During the resume, the QSPI driver state is restored but the SPI NAND framework does not reconfigured the memory. This patch adds SPI-NAND MTD PM handlers for resume ops. SPI NAND resume op re-initializes SPI NAND flash to its probed state. Signed-off-by: Christophe Kerello Signed-off-by: Patrice Chotard Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210602094913.26472-4-patrice.chotard@foss.st.com --- drivers/mtd/nand/spi/core.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 82a8f0f2c157..a49cd81ae416 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1129,6 +1129,22 @@ static int spinand_init_flash(struct spinand_device *spinand) return ret; } +static void spinand_mtd_resume(struct mtd_info *mtd) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + ret = spinand_reset_op(spinand); + if (ret) + return; + + ret = spinand_init_flash(spinand); + if (ret) + return; + + spinand_ecc_enable(spinand, false); +} + static int spinand_init(struct spinand_device *spinand) { struct device *dev = &spinand->spimem->spi->dev; @@ -1199,6 +1215,7 @@ static int spinand_init(struct spinand_device *spinand) mtd->_block_isreserved = spinand_mtd_block_isreserved; mtd->_erase = spinand_mtd_erase; mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks; + mtd->_resume = spinand_mtd_resume; if (nand->ecc.engine) { ret = mtd_ooblayout_count_freebytes(mtd); -- cgit v1.2.3-59-g8ed1b From 21db4f475d56cbfa187ccc24a22e27ba024ec62c Mon Sep 17 00:00:00 2001 From: Zhen Lei Date: Thu, 3 Jun 2021 20:33:39 +0800 Subject: mtd: rawnand: r852: use DEVICE_ATTR_RO() helper macro Use DEVICE_ATTR_RO() helper macro instead of plain DEVICE_ATTR(), which makes the code a bit shorter and easier to read. Signed-off-by: Zhen Lei Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210603123339.12089-1-thunder.leizhen@huawei.com --- drivers/mtd/nand/raw/r852.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c index ebe859ca49cb..ed0cf732d20e 100644 --- a/drivers/mtd/nand/raw/r852.c +++ b/drivers/mtd/nand/raw/r852.c @@ -583,8 +583,8 @@ static void r852_update_card_detect(struct r852_device *dev) r852_write_reg(dev, R852_CARD_IRQ_ENABLE, card_detect_reg); } -static ssize_t r852_media_type_show(struct device *sys_dev, - struct device_attribute *attr, char *buf) +static ssize_t media_type_show(struct device *sys_dev, + struct device_attribute *attr, char *buf) { struct mtd_info *mtd = container_of(sys_dev, struct mtd_info, dev); struct r852_device *dev = r852_get_dev(mtd); @@ -593,8 +593,7 @@ static ssize_t r852_media_type_show(struct device *sys_dev, strcpy(buf, data); return strlen(data); } - -static DEVICE_ATTR(media_type, S_IRUGO, r852_media_type_show, NULL); +static DEVICE_ATTR_RO(media_type); /* Detect properties of card in slot */ -- cgit v1.2.3-59-g8ed1b From f856c4e9cf22471b956d2b026a71fa2bf7f4d05a Mon Sep 17 00:00:00 2001 From: Souptick Joarder Date: Tue, 8 Jun 2021 01:07:36 +0530 Subject: mtd: rawnand: marvell: Minor documentation correction Kernel test robot throws below warning -> drivers/mtd/nand/raw/marvell_nand.c:454: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst Minor documentation correction. Reported-by: kernel test robot Signed-off-by: Souptick Joarder Cc: Randy Dunlap Acked-by: Randy Dunlap Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210607193736.4654-1-jrdr.linux@gmail.com --- drivers/mtd/nand/raw/marvell_nand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c index f83525a1ab0e..2455a581fd70 100644 --- a/drivers/mtd/nand/raw/marvell_nand.c +++ b/drivers/mtd/nand/raw/marvell_nand.c @@ -451,7 +451,7 @@ struct marvell_nfc_timings { }; /** - * Derives a duration in numbers of clock cycles. + * TO_CYCLES() - Derives a duration in numbers of clock cycles. * * @ps: Duration in pico-seconds * @period_ns: Clock period in nano-seconds -- cgit v1.2.3-59-g8ed1b From bfb34eced5595ee137e46d83d12b50d4ef6fc0c9 Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Tue, 8 Jun 2021 12:18:36 +0530 Subject: mtd: rawnand: qcom: avoid writing to obsolete register QPIC_EBI2_ECC_BUF_CFG register got obsolete from QPIC V2.0 onwards. Avoid writing this register if QPIC version is V2.0 or newer. Signed-off-by: Md Sadre Alam Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/1623134916-562-1-git-send-email-mdalam@codeaurora.org --- drivers/mtd/nand/raw/qcom_nandc.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 45de67aa86a4..ef0badea4f41 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -734,6 +734,7 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i { struct nand_chip *chip = &host->chip; u32 cmd, cfg0, cfg1, ecc_bch_cfg; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); if (read) { if (host->use_ecc) @@ -762,7 +763,8 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i nandc_set_reg(chip, NAND_DEV0_CFG0, cfg0); nandc_set_reg(chip, NAND_DEV0_CFG1, cfg1); nandc_set_reg(chip, NAND_DEV0_ECC_CFG, ecc_bch_cfg); - nandc_set_reg(chip, NAND_EBI2_ECC_BUF_CFG, host->ecc_buf_cfg); + if (!nandc->props->qpic_v2) + nandc_set_reg(chip, NAND_EBI2_ECC_BUF_CFG, host->ecc_buf_cfg); nandc_set_reg(chip, NAND_FLASH_STATUS, host->clrflashstatus); nandc_set_reg(chip, NAND_READ_STATUS, host->clrreadstatus); nandc_set_reg(chip, NAND_EXEC_CMD, 1); @@ -1133,7 +1135,8 @@ static void config_nand_page_read(struct nand_chip *chip) write_reg_dma(nandc, NAND_ADDR0, 2, 0); write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0); - write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, 0); + if (!nandc->props->qpic_v2) + write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, 0); write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1, 0); write_reg_dma(nandc, NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); @@ -1191,8 +1194,9 @@ static void config_nand_page_write(struct nand_chip *chip) write_reg_dma(nandc, NAND_ADDR0, 2, 0); write_reg_dma(nandc, NAND_DEV0_CFG0, 3, 0); - write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, - NAND_BAM_NEXT_SGL); + if (!nandc->props->qpic_v2) + write_reg_dma(nandc, NAND_EBI2_ECC_BUF_CFG, 1, + NAND_BAM_NEXT_SGL); } /* @@ -1248,7 +1252,8 @@ static int nandc_param(struct qcom_nand_host *host) | 2 << WR_RD_BSY_GAP | 0 << WIDE_FLASH | 1 << DEV0_CFG1_ECC_DISABLE); - nandc_set_reg(chip, NAND_EBI2_ECC_BUF_CFG, 1 << ECC_CFG_ECC_DISABLE); + if (!nandc->props->qpic_v2) + nandc_set_reg(chip, NAND_EBI2_ECC_BUF_CFG, 1 << ECC_CFG_ECC_DISABLE); /* configure CMD1 and VLD for ONFI param probing in QPIC v1 */ if (!nandc->props->qpic_v2) { @@ -2688,7 +2693,8 @@ static int qcom_nand_attach_chip(struct nand_chip *chip) | ecc_mode << ECC_MODE | host->ecc_bytes_hw << ECC_PARITY_SIZE_BYTES_BCH; - host->ecc_buf_cfg = 0x203 << NUM_STEPS; + if (!nandc->props->qpic_v2) + host->ecc_buf_cfg = 0x203 << NUM_STEPS; host->clrflashstatus = FS_READY_BSY_N; host->clrreadstatus = 0xc0; -- cgit v1.2.3-59-g8ed1b From 08d8c62164a322eb923034acacf25246b775593a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 10 Jun 2021 10:20:40 +0200 Subject: mtd: rawnand: pl353: Add support for the ARM PL353 SMC NAND controller This hardware controller is embedded in XilinX Zynq-7000 SoCs and has partial support for Hamming ECC correction. This work is inspired from the original contributions of Punnaiah Choudary Kalluri and Naga Sureshkumar Relli. Signed-off-by: Miquel Raynal Tested-by: Michael Walle [on zynq-7000] Link: https://lore.kernel.org/linux-mtd/20210610082040.2075611-19-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/Kconfig | 8 + drivers/mtd/nand/raw/Makefile | 1 + drivers/mtd/nand/raw/pl35x-nand-controller.c | 1194 ++++++++++++++++++++++++++ 3 files changed, 1203 insertions(+) create mode 100644 drivers/mtd/nand/raw/pl35x-nand-controller.c (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 30f061939560..630728de4b7c 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -453,6 +453,14 @@ config MTD_NAND_ROCKCHIP NFC v800: RK3308, RV1108 NFC v900: PX30, RK3326 +config MTD_NAND_PL35X + tristate "ARM PL35X NAND controller" + depends on OF || COMPILE_TEST + depends on PL353_SMC + help + Enables support for PrimeCell SMC PL351 and PL353 NAND + controller found on Zynq7000. + comment "Misc" config MTD_SM_COMMON diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index d011c6c53f8f..2f97958c3a33 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_MTD_NAND_CADENCE) += cadence-nand-controller.o obj-$(CONFIG_MTD_NAND_ARASAN) += arasan-nand-controller.o obj-$(CONFIG_MTD_NAND_INTEL_LGM) += intel-nand-controller.o obj-$(CONFIG_MTD_NAND_ROCKCHIP) += rockchip-nand-controller.o +obj-$(CONFIG_MTD_NAND_PL35X) += pl35x-nand-controller.o nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o nand-objs += nand_onfi.o diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c new file mode 100644 index 000000000000..8a91e069ee2e --- /dev/null +++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c @@ -0,0 +1,1194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM PL35X NAND flash controller driver + * + * Copyright (C) 2017 Xilinx, Inc + * Author: + * Miquel Raynal + * Original work (rewritten): + * Punnaiah Choudary Kalluri + * Naga Sureshkumar Relli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PL35X_NANDC_DRIVER_NAME "pl35x-nand-controller" + +/* SMC controller status register (RO) */ +#define PL35X_SMC_MEMC_STATUS 0x0 +#define PL35X_SMC_MEMC_STATUS_RAW_INT_STATUS1 BIT(6) +/* SMC clear config register (WO) */ +#define PL35X_SMC_MEMC_CFG_CLR 0xC +#define PL35X_SMC_MEMC_CFG_CLR_INT_DIS_1 BIT(1) +#define PL35X_SMC_MEMC_CFG_CLR_INT_CLR_1 BIT(4) +#define PL35X_SMC_MEMC_CFG_CLR_ECC_INT_DIS_1 BIT(6) +/* SMC direct command register (WO) */ +#define PL35X_SMC_DIRECT_CMD 0x10 +#define PL35X_SMC_DIRECT_CMD_NAND_CS (0x4 << 23) +#define PL35X_SMC_DIRECT_CMD_UPD_REGS (0x2 << 21) +/* SMC set cycles register (WO) */ +#define PL35X_SMC_CYCLES 0x14 +#define PL35X_SMC_NAND_TRC_CYCLES(x) ((x) << 0) +#define PL35X_SMC_NAND_TWC_CYCLES(x) ((x) << 4) +#define PL35X_SMC_NAND_TREA_CYCLES(x) ((x) << 8) +#define PL35X_SMC_NAND_TWP_CYCLES(x) ((x) << 11) +#define PL35X_SMC_NAND_TCLR_CYCLES(x) ((x) << 14) +#define PL35X_SMC_NAND_TAR_CYCLES(x) ((x) << 17) +#define PL35X_SMC_NAND_TRR_CYCLES(x) ((x) << 20) +/* SMC set opmode register (WO) */ +#define PL35X_SMC_OPMODE 0x18 +#define PL35X_SMC_OPMODE_BW_8 0 +#define PL35X_SMC_OPMODE_BW_16 1 +/* SMC ECC status register (RO) */ +#define PL35X_SMC_ECC_STATUS 0x400 +#define PL35X_SMC_ECC_STATUS_ECC_BUSY BIT(6) +/* SMC ECC configuration register */ +#define PL35X_SMC_ECC_CFG 0x404 +#define PL35X_SMC_ECC_CFG_MODE_MASK 0xC +#define PL35X_SMC_ECC_CFG_MODE_BYPASS 0 +#define PL35X_SMC_ECC_CFG_MODE_APB BIT(2) +#define PL35X_SMC_ECC_CFG_MODE_MEM BIT(3) +#define PL35X_SMC_ECC_CFG_PGSIZE_MASK 0x3 +/* SMC ECC command 1 register */ +#define PL35X_SMC_ECC_CMD1 0x408 +#define PL35X_SMC_ECC_CMD1_WRITE(x) ((x) << 0) +#define PL35X_SMC_ECC_CMD1_READ(x) ((x) << 8) +#define PL35X_SMC_ECC_CMD1_READ_END(x) ((x) << 16) +#define PL35X_SMC_ECC_CMD1_READ_END_VALID(x) ((x) << 24) +/* SMC ECC command 2 register */ +#define PL35X_SMC_ECC_CMD2 0x40C +#define PL35X_SMC_ECC_CMD2_WRITE_COL_CHG(x) ((x) << 0) +#define PL35X_SMC_ECC_CMD2_READ_COL_CHG(x) ((x) << 8) +#define PL35X_SMC_ECC_CMD2_READ_COL_CHG_END(x) ((x) << 16) +#define PL35X_SMC_ECC_CMD2_READ_COL_CHG_END_VALID(x) ((x) << 24) +/* SMC ECC value registers (RO) */ +#define PL35X_SMC_ECC_VALUE(x) (0x418 + (4 * (x))) +#define PL35X_SMC_ECC_VALUE_IS_CORRECTABLE(x) ((x) & BIT(27)) +#define PL35X_SMC_ECC_VALUE_HAS_FAILED(x) ((x) & BIT(28)) +#define PL35X_SMC_ECC_VALUE_IS_VALID(x) ((x) & BIT(30)) + +/* NAND AXI interface */ +#define PL35X_SMC_CMD_PHASE 0 +#define PL35X_SMC_CMD_PHASE_CMD0(x) ((x) << 3) +#define PL35X_SMC_CMD_PHASE_CMD1(x) ((x) << 11) +#define PL35X_SMC_CMD_PHASE_CMD1_VALID BIT(20) +#define PL35X_SMC_CMD_PHASE_ADDR(pos, x) ((x) << (8 * (pos))) +#define PL35X_SMC_CMD_PHASE_NADDRS(x) ((x) << 21) +#define PL35X_SMC_DATA_PHASE BIT(19) +#define PL35X_SMC_DATA_PHASE_ECC_LAST BIT(10) +#define PL35X_SMC_DATA_PHASE_CLEAR_CS BIT(21) + +#define PL35X_NAND_MAX_CS 1 +#define PL35X_NAND_LAST_XFER_SZ 4 +#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP((ps) / 1000, period_ns)) + +#define PL35X_NAND_ECC_BITS_MASK 0xFFF +#define PL35X_NAND_ECC_BYTE_OFF_MASK 0x1FF +#define PL35X_NAND_ECC_BIT_OFF_MASK 0x7 + +struct pl35x_nand_timings { + unsigned int t_rc:4; + unsigned int t_wc:4; + unsigned int t_rea:3; + unsigned int t_wp:3; + unsigned int t_clr:3; + unsigned int t_ar:3; + unsigned int t_rr:4; + unsigned int rsvd:8; +}; + +struct pl35x_nand { + struct list_head node; + struct nand_chip chip; + unsigned int cs; + unsigned int addr_cycles; + u32 ecc_cfg; + u32 timings; +}; + +/** + * struct pl35x_nandc - NAND flash controller driver structure + * @dev: Kernel device + * @conf_regs: SMC configuration registers for command phase + * @io_regs: NAND data registers for data phase + * @controller: Core NAND controller structure + * @chip: NAND chip information structure + * @selected_chip: NAND chip currently selected by the controller + * @assigned_cs: List of assigned CS + * @ecc_buf: Temporary buffer to extract ECC bytes + */ +struct pl35x_nandc { + struct device *dev; + void __iomem *conf_regs; + void __iomem *io_regs; + struct nand_controller controller; + struct list_head chips; + struct nand_chip *selected_chip; + unsigned long assigned_cs; + u8 *ecc_buf; +}; + +static inline struct pl35x_nandc *to_pl35x_nandc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct pl35x_nandc, controller); +} + +static inline struct pl35x_nand *to_pl35x_nand(struct nand_chip *chip) +{ + return container_of(chip, struct pl35x_nand, chip); +} + +static int pl35x_ecc_ooblayout16_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * chip->ecc.bytes); + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int pl35x_ecc_ooblayout16_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * chip->ecc.bytes) + 8; + oobregion->length = 8; + + return 0; +} + +static const struct mtd_ooblayout_ops pl35x_ecc_ooblayout16_ops = { + .ecc = pl35x_ecc_ooblayout16_ecc, + .free = pl35x_ecc_ooblayout16_free, +}; + +/* Generic flash bbt decriptors */ +static u8 bbt_pattern[] = { 'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 4, + .len = 4, + .veroffs = 20, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 4, + .len = 4, + .veroffs = 20, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +static void pl35x_smc_update_regs(struct pl35x_nandc *nfc) +{ + writel(PL35X_SMC_DIRECT_CMD_NAND_CS | + PL35X_SMC_DIRECT_CMD_UPD_REGS, + nfc->conf_regs + PL35X_SMC_DIRECT_CMD); +} + +static int pl35x_smc_set_buswidth(struct pl35x_nandc *nfc, unsigned int bw) +{ + if (bw != PL35X_SMC_OPMODE_BW_8 && bw != PL35X_SMC_OPMODE_BW_16) + return -EINVAL; + + writel(bw, nfc->conf_regs + PL35X_SMC_OPMODE); + pl35x_smc_update_regs(nfc); + + return 0; +} + +static void pl35x_smc_clear_irq(struct pl35x_nandc *nfc) +{ + writel(PL35X_SMC_MEMC_CFG_CLR_INT_CLR_1, + nfc->conf_regs + PL35X_SMC_MEMC_CFG_CLR); +} + +static int pl35x_smc_wait_for_irq(struct pl35x_nandc *nfc) +{ + u32 reg; + int ret; + + ret = readl_poll_timeout(nfc->conf_regs + PL35X_SMC_MEMC_STATUS, reg, + reg & PL35X_SMC_MEMC_STATUS_RAW_INT_STATUS1, + 10, 1000000); + if (ret) + dev_err(nfc->dev, + "Timeout polling on NAND controller interrupt (0x%x)\n", + reg); + + pl35x_smc_clear_irq(nfc); + + return ret; +} + +static int pl35x_smc_wait_for_ecc_done(struct pl35x_nandc *nfc) +{ + u32 reg; + int ret; + + ret = readl_poll_timeout(nfc->conf_regs + PL35X_SMC_ECC_STATUS, reg, + !(reg & PL35X_SMC_ECC_STATUS_ECC_BUSY), + 10, 1000000); + if (ret) + dev_err(nfc->dev, + "Timeout polling on ECC controller interrupt\n"); + + return ret; +} + +static int pl35x_smc_set_ecc_mode(struct pl35x_nandc *nfc, + struct nand_chip *chip, + unsigned int mode) +{ + struct pl35x_nand *plnand; + u32 ecc_cfg; + + ecc_cfg = readl(nfc->conf_regs + PL35X_SMC_ECC_CFG); + ecc_cfg &= ~PL35X_SMC_ECC_CFG_MODE_MASK; + ecc_cfg |= mode; + writel(ecc_cfg, nfc->conf_regs + PL35X_SMC_ECC_CFG); + + if (chip) { + plnand = to_pl35x_nand(chip); + plnand->ecc_cfg = ecc_cfg; + } + + if (mode != PL35X_SMC_ECC_CFG_MODE_BYPASS) + return pl35x_smc_wait_for_ecc_done(nfc); + + return 0; +} + +static void pl35x_smc_force_byte_access(struct nand_chip *chip, + bool force_8bit) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + int ret; + + if (!(chip->options & NAND_BUSWIDTH_16)) + return; + + if (force_8bit) + ret = pl35x_smc_set_buswidth(nfc, PL35X_SMC_OPMODE_BW_8); + else + ret = pl35x_smc_set_buswidth(nfc, PL35X_SMC_OPMODE_BW_16); + + if (ret) + dev_err(nfc->dev, "Error in Buswidth\n"); +} + +static void pl35x_nand_select_target(struct nand_chip *chip, + unsigned int die_nr) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + + if (chip == nfc->selected_chip) + return; + + /* Setup the timings */ + writel(plnand->timings, nfc->conf_regs + PL35X_SMC_CYCLES); + pl35x_smc_update_regs(nfc); + + /* Configure the ECC engine */ + writel(plnand->ecc_cfg, nfc->conf_regs + PL35X_SMC_ECC_CFG); + + nfc->selected_chip = chip; +} + +static void pl35x_nand_read_data_op(struct nand_chip *chip, u8 *in, + unsigned int len, bool force_8bit, + unsigned int flags, unsigned int last_flags) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + unsigned int buf_end = len / 4; + unsigned int in_start = round_down(len, 4); + unsigned int data_phase_addr; + u32 *buf32 = (u32 *)in; + u8 *buf8 = (u8 *)in; + int i; + + if (force_8bit) + pl35x_smc_force_byte_access(chip, true); + + for (i = 0; i < buf_end; i++) { + data_phase_addr = PL35X_SMC_DATA_PHASE + flags; + if (i + 1 == buf_end) + data_phase_addr = PL35X_SMC_DATA_PHASE + last_flags; + + buf32[i] = readl(nfc->io_regs + data_phase_addr); + } + + /* No working extra flags on unaligned data accesses */ + for (i = in_start; i < len; i++) + buf8[i] = readb(nfc->io_regs + PL35X_SMC_DATA_PHASE); + + if (force_8bit) + pl35x_smc_force_byte_access(chip, false); +} + +static void pl35x_nand_write_data_op(struct nand_chip *chip, const u8 *out, + int len, bool force_8bit, + unsigned int flags, + unsigned int last_flags) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + unsigned int buf_end = len / 4; + unsigned int in_start = round_down(len, 4); + const u32 *buf32 = (const u32 *)out; + const u8 *buf8 = (const u8 *)out; + unsigned int data_phase_addr; + int i; + + if (force_8bit) + pl35x_smc_force_byte_access(chip, true); + + for (i = 0; i < buf_end; i++) { + data_phase_addr = PL35X_SMC_DATA_PHASE + flags; + if (i + 1 == buf_end) + data_phase_addr = PL35X_SMC_DATA_PHASE + last_flags; + + writel(buf32[i], nfc->io_regs + data_phase_addr); + } + + /* No working extra flags on unaligned data accesses */ + for (i = in_start; i < len; i++) + writeb(buf8[i], nfc->io_regs + PL35X_SMC_DATA_PHASE); + + if (force_8bit) + pl35x_smc_force_byte_access(chip, false); +} + +static int pl35x_nand_correct_data(struct pl35x_nandc *nfc, unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + unsigned short ecc_odd, ecc_even, read_ecc_lower, read_ecc_upper; + unsigned short calc_ecc_lower, calc_ecc_upper; + unsigned short byte_addr, bit_addr; + + read_ecc_lower = (read_ecc[0] | (read_ecc[1] << 8)) & + PL35X_NAND_ECC_BITS_MASK; + read_ecc_upper = ((read_ecc[1] >> 4) | (read_ecc[2] << 4)) & + PL35X_NAND_ECC_BITS_MASK; + + calc_ecc_lower = (calc_ecc[0] | (calc_ecc[1] << 8)) & + PL35X_NAND_ECC_BITS_MASK; + calc_ecc_upper = ((calc_ecc[1] >> 4) | (calc_ecc[2] << 4)) & + PL35X_NAND_ECC_BITS_MASK; + + ecc_odd = read_ecc_lower ^ calc_ecc_lower; + ecc_even = read_ecc_upper ^ calc_ecc_upper; + + /* No error */ + if (likely(!ecc_odd && !ecc_even)) + return 0; + + /* One error in the main data; to be corrected */ + if (ecc_odd == (~ecc_even & PL35X_NAND_ECC_BITS_MASK)) { + /* Bits [11:3] of error code give the byte offset */ + byte_addr = (ecc_odd >> 3) & PL35X_NAND_ECC_BYTE_OFF_MASK; + /* Bits [2:0] of error code give the bit offset */ + bit_addr = ecc_odd & PL35X_NAND_ECC_BIT_OFF_MASK; + /* Toggle the faulty bit */ + buf[byte_addr] ^= (BIT(bit_addr)); + + return 1; + } + + /* One error in the ECC data; no action needed */ + if (hweight32(ecc_odd | ecc_even) == 1) + return 1; + + return -EBADMSG; +} + +static void pl35x_nand_ecc_reg_to_array(struct nand_chip *chip, u32 ecc_reg, + u8 *ecc_array) +{ + u32 ecc_value = ~ecc_reg; + unsigned int ecc_byte; + + for (ecc_byte = 0; ecc_byte < chip->ecc.bytes; ecc_byte++) + ecc_array[ecc_byte] = ecc_value >> (8 * ecc_byte); +} + +static int pl35x_nand_read_eccbytes(struct pl35x_nandc *nfc, + struct nand_chip *chip, u8 *read_ecc) +{ + u32 ecc_value; + int chunk; + + for (chunk = 0; chunk < chip->ecc.steps; + chunk++, read_ecc += chip->ecc.bytes) { + ecc_value = readl(nfc->conf_regs + PL35X_SMC_ECC_VALUE(chunk)); + if (!PL35X_SMC_ECC_VALUE_IS_VALID(ecc_value)) + return -EINVAL; + + pl35x_nand_ecc_reg_to_array(chip, ecc_value, read_ecc); + } + + return 0; +} + +static int pl35x_nand_recover_data_hwecc(struct pl35x_nandc *nfc, + struct nand_chip *chip, u8 *data, + u8 *read_ecc) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int max_bitflips = 0, chunk; + u8 calc_ecc[3]; + u32 ecc_value; + int stats; + + for (chunk = 0; chunk < chip->ecc.steps; + chunk++, data += chip->ecc.size, read_ecc += chip->ecc.bytes) { + /* Read ECC value for each chunk */ + ecc_value = readl(nfc->conf_regs + PL35X_SMC_ECC_VALUE(chunk)); + + if (!PL35X_SMC_ECC_VALUE_IS_VALID(ecc_value)) + return -EINVAL; + + if (PL35X_SMC_ECC_VALUE_HAS_FAILED(ecc_value)) { + mtd->ecc_stats.failed++; + continue; + } + + pl35x_nand_ecc_reg_to_array(chip, ecc_value, calc_ecc); + stats = pl35x_nand_correct_data(nfc, data, read_ecc, calc_ecc); + if (stats < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stats; + max_bitflips = max_t(unsigned int, max_bitflips, stats); + } + } + + return max_bitflips; +} + +static int pl35x_nand_write_page_hwecc(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int first_row = (mtd->writesize <= 512) ? 1 : 2; + unsigned int nrows = plnand->addr_cycles; + u32 addr1 = 0, addr2 = 0, row; + u32 cmd_addr; + int i, ret; + + ret = pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_APB); + if (ret) + return ret; + + cmd_addr = PL35X_SMC_CMD_PHASE | + PL35X_SMC_CMD_PHASE_NADDRS(plnand->addr_cycles) | + PL35X_SMC_CMD_PHASE_CMD0(NAND_CMD_SEQIN); + + for (i = 0, row = first_row; row < nrows; i++, row++) { + u8 addr = page >> ((i * 8) & 0xFF); + + if (row < 4) + addr1 |= PL35X_SMC_CMD_PHASE_ADDR(row, addr); + else + addr2 |= PL35X_SMC_CMD_PHASE_ADDR(row - 4, addr); + } + + /* Send the command and address cycles */ + writel(addr1, nfc->io_regs + cmd_addr); + if (plnand->addr_cycles > 4) + writel(addr2, nfc->io_regs + cmd_addr); + + /* Write the data with the engine enabled */ + pl35x_nand_write_data_op(chip, buf, mtd->writesize, false, + 0, PL35X_SMC_DATA_PHASE_ECC_LAST); + ret = pl35x_smc_wait_for_ecc_done(nfc); + if (ret) + goto disable_ecc_engine; + + /* Copy the HW calculated ECC bytes in the OOB buffer */ + ret = pl35x_nand_read_eccbytes(nfc, chip, nfc->ecc_buf); + if (ret) + goto disable_ecc_engine; + + if (!oob_required) + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + ret = mtd_ooblayout_set_eccbytes(mtd, nfc->ecc_buf, chip->oob_poi, + 0, chip->ecc.total); + if (ret) + goto disable_ecc_engine; + + /* Write the spare area with ECC bytes */ + pl35x_nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false, 0, + PL35X_SMC_CMD_PHASE_CMD1(NAND_CMD_PAGEPROG) | + PL35X_SMC_CMD_PHASE_CMD1_VALID | + PL35X_SMC_DATA_PHASE_CLEAR_CS); + ret = pl35x_smc_wait_for_irq(nfc); + if (ret) + goto disable_ecc_engine; + +disable_ecc_engine: + pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_BYPASS); + + return ret; +} + +/* + * This functions reads data and checks the data integrity by comparing hardware + * generated ECC values and read ECC values from spare area. + * + * There is a limitation with SMC controller: ECC_LAST must be set on the + * last data access to tell the ECC engine not to expect any further data. + * In practice, this implies to shrink the last data transfert by eg. 4 bytes, + * and doing a last 4-byte transfer with the additional bit set. The last block + * should be aligned with the end of an ECC block. Because of this limitation, + * it is not possible to use the core routines. + */ +static int pl35x_nand_read_page_hwecc(struct nand_chip *chip, + u8 *buf, int oob_required, int page) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(chip)); + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int first_row = (mtd->writesize <= 512) ? 1 : 2; + unsigned int nrows = plnand->addr_cycles; + unsigned int addr1 = 0, addr2 = 0, row; + u32 cmd_addr; + int i, ret; + + ret = pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_APB); + if (ret) + return ret; + + cmd_addr = PL35X_SMC_CMD_PHASE | + PL35X_SMC_CMD_PHASE_NADDRS(plnand->addr_cycles) | + PL35X_SMC_CMD_PHASE_CMD0(NAND_CMD_READ0) | + PL35X_SMC_CMD_PHASE_CMD1(NAND_CMD_READSTART) | + PL35X_SMC_CMD_PHASE_CMD1_VALID; + + for (i = 0, row = first_row; row < nrows; i++, row++) { + u8 addr = page >> ((i * 8) & 0xFF); + + if (row < 4) + addr1 |= PL35X_SMC_CMD_PHASE_ADDR(row, addr); + else + addr2 |= PL35X_SMC_CMD_PHASE_ADDR(row - 4, addr); + } + + /* Send the command and address cycles */ + writel(addr1, nfc->io_regs + cmd_addr); + if (plnand->addr_cycles > 4) + writel(addr2, nfc->io_regs + cmd_addr); + + /* Wait the data to be available in the NAND cache */ + ndelay(PSEC_TO_NSEC(sdr->tRR_min)); + ret = pl35x_smc_wait_for_irq(nfc); + if (ret) + goto disable_ecc_engine; + + /* Retrieve the raw data with the engine enabled */ + pl35x_nand_read_data_op(chip, buf, mtd->writesize, false, + 0, PL35X_SMC_DATA_PHASE_ECC_LAST); + ret = pl35x_smc_wait_for_ecc_done(nfc); + if (ret) + goto disable_ecc_engine; + + /* Retrieve the stored ECC bytes */ + pl35x_nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, + 0, PL35X_SMC_DATA_PHASE_CLEAR_CS); + ret = mtd_ooblayout_get_eccbytes(mtd, nfc->ecc_buf, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + goto disable_ecc_engine; + + pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_BYPASS); + + /* Correct the data and report failures */ + return pl35x_nand_recover_data_hwecc(nfc, chip, buf, nfc->ecc_buf); + +disable_ecc_engine: + pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_BYPASS); + + return ret; +} + +static int pl35x_nand_exec_op(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + const struct nand_op_instr *instr, *data_instr = NULL; + unsigned int rdy_tim_ms = 0, naddrs = 0, cmds = 0, last_flags = 0; + u32 addr1 = 0, addr2 = 0, cmd0 = 0, cmd1 = 0, cmd_addr = 0; + unsigned int op_id, len, offset, rdy_del_ns; + int last_instr_type = -1; + bool cmd1_valid = false; + const u8 *addrs; + int i, ret; + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (!cmds) { + cmd0 = PL35X_SMC_CMD_PHASE_CMD0(instr->ctx.cmd.opcode); + } else { + cmd1 = PL35X_SMC_CMD_PHASE_CMD1(instr->ctx.cmd.opcode); + if (last_instr_type != NAND_OP_DATA_OUT_INSTR) + cmd1_valid = true; + } + cmds++; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + cmd_addr |= PL35X_SMC_CMD_PHASE_NADDRS(naddrs); + + for (i = offset; i < naddrs; i++) { + if (i < 4) + addr1 |= PL35X_SMC_CMD_PHASE_ADDR(i, addrs[i]); + else + addr2 |= PL35X_SMC_CMD_PHASE_ADDR(i - 4, addrs[i]); + } + break; + + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + data_instr = instr; + len = nand_subop_get_data_len(subop, op_id); + break; + + case NAND_OP_WAITRDY_INSTR: + rdy_tim_ms = instr->ctx.waitrdy.timeout_ms; + rdy_del_ns = instr->delay_ns; + break; + } + + last_instr_type = instr->type; + } + + /* Command phase */ + cmd_addr |= PL35X_SMC_CMD_PHASE | cmd0 | cmd1 | + (cmd1_valid ? PL35X_SMC_CMD_PHASE_CMD1_VALID : 0); + writel(addr1, nfc->io_regs + cmd_addr); + if (naddrs > 4) + writel(addr2, nfc->io_regs + cmd_addr); + + /* Data phase */ + if (data_instr && data_instr->type == NAND_OP_DATA_OUT_INSTR) { + last_flags = PL35X_SMC_DATA_PHASE_CLEAR_CS; + if (cmds == 2) + last_flags |= cmd1 | PL35X_SMC_CMD_PHASE_CMD1_VALID; + + pl35x_nand_write_data_op(chip, data_instr->ctx.data.buf.out, + len, data_instr->ctx.data.force_8bit, + 0, last_flags); + } + + if (rdy_tim_ms) { + ndelay(rdy_del_ns); + ret = pl35x_smc_wait_for_irq(nfc); + if (ret) + return ret; + } + + if (data_instr && data_instr->type == NAND_OP_DATA_IN_INSTR) + pl35x_nand_read_data_op(chip, data_instr->ctx.data.buf.in, + len, data_instr->ctx.data.force_8bit, + 0, PL35X_SMC_DATA_PHASE_CLEAR_CS); + + return 0; +} + +static const struct nand_op_parser pl35x_nandc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(pl35x_nand_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 7), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 2112)), + NAND_OP_PARSER_PATTERN(pl35x_nand_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, 7), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 2112), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + NAND_OP_PARSER_PATTERN(pl35x_nand_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, 7), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 2112), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + ); + +static int pl35x_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + if (!check_only) + pl35x_nand_select_target(chip, op->cs); + + return nand_op_parser_exec_op(chip, &pl35x_nandc_op_parser, + op, check_only); +} + +static int pl35x_nfc_setup_interface(struct nand_chip *chip, int cs, + const struct nand_interface_config *conf) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct pl35x_nand_timings tmgs = {}; + const struct nand_sdr_timings *sdr; + unsigned int period_ns, val; + struct clk *mclk; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + mclk = of_clk_get_by_name(nfc->dev->parent->of_node, "memclk"); + if (IS_ERR(mclk)) { + dev_err(nfc->dev, "Failed to retrieve SMC memclk\n"); + return PTR_ERR(mclk); + } + + /* + * SDR timings are given in pico-seconds while NFC timings must be + * expressed in NAND controller clock cycles. We use the TO_CYCLE() + * macro to convert from one to the other. + */ + period_ns = NSEC_PER_SEC / clk_get_rate(mclk); + + /* + * PL35X SMC needs one extra read cycle in SDR Mode 5. This is not + * written anywhere in the datasheet but is an empirical observation. + */ + val = TO_CYCLES(sdr->tRC_min, period_ns); + if (sdr->tRC_min <= 20000) + val++; + + tmgs.t_rc = val; + if (tmgs.t_rc != val || tmgs.t_rc < 2) + return -EINVAL; + + val = TO_CYCLES(sdr->tWC_min, period_ns); + tmgs.t_wc = val; + if (tmgs.t_wc != val || tmgs.t_wc < 2) + return -EINVAL; + + /* + * For all SDR modes, PL35X SMC needs tREA_max being 1, + * this is also an empirical result. + */ + tmgs.t_rea = 1; + + val = TO_CYCLES(sdr->tWP_min, period_ns); + tmgs.t_wp = val; + if (tmgs.t_wp != val || tmgs.t_wp < 1) + return -EINVAL; + + val = TO_CYCLES(sdr->tCLR_min, period_ns); + tmgs.t_clr = val; + if (tmgs.t_clr != val) + return -EINVAL; + + val = TO_CYCLES(sdr->tAR_min, period_ns); + tmgs.t_ar = val; + if (tmgs.t_ar != val) + return -EINVAL; + + val = TO_CYCLES(sdr->tRR_min, period_ns); + tmgs.t_rr = val; + if (tmgs.t_rr != val) + return -EINVAL; + + if (cs == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + plnand->timings = PL35X_SMC_NAND_TRC_CYCLES(tmgs.t_rc) | + PL35X_SMC_NAND_TWC_CYCLES(tmgs.t_wc) | + PL35X_SMC_NAND_TREA_CYCLES(tmgs.t_rea) | + PL35X_SMC_NAND_TWP_CYCLES(tmgs.t_wp) | + PL35X_SMC_NAND_TCLR_CYCLES(tmgs.t_clr) | + PL35X_SMC_NAND_TAR_CYCLES(tmgs.t_ar) | + PL35X_SMC_NAND_TRR_CYCLES(tmgs.t_rr); + + return 0; +} + +static void pl35x_smc_set_ecc_pg_size(struct pl35x_nandc *nfc, + struct nand_chip *chip, + unsigned int pg_sz) +{ + struct pl35x_nand *plnand = to_pl35x_nand(chip); + u32 sz; + + switch (pg_sz) { + case SZ_512: + sz = 1; + break; + case SZ_1K: + sz = 2; + break; + case SZ_2K: + sz = 3; + break; + default: + sz = 0; + break; + } + + plnand->ecc_cfg = readl(nfc->conf_regs + PL35X_SMC_ECC_CFG); + plnand->ecc_cfg &= ~PL35X_SMC_ECC_CFG_PGSIZE_MASK; + plnand->ecc_cfg |= sz; + writel(plnand->ecc_cfg, nfc->conf_regs + PL35X_SMC_ECC_CFG); +} + +static int pl35x_nand_init_hw_ecc_controller(struct pl35x_nandc *nfc, + struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret = 0; + + if (mtd->writesize < SZ_512 || mtd->writesize > SZ_2K) { + dev_err(nfc->dev, + "The hardware ECC engine is limited to pages up to 2kiB\n"); + return -EOPNOTSUPP; + } + + chip->ecc.strength = 1; + chip->ecc.bytes = 3; + chip->ecc.size = SZ_512; + chip->ecc.steps = mtd->writesize / chip->ecc.size; + chip->ecc.read_page = pl35x_nand_read_page_hwecc; + chip->ecc.write_page = pl35x_nand_write_page_hwecc; + chip->ecc.write_page_raw = nand_monolithic_write_page_raw; + pl35x_smc_set_ecc_pg_size(nfc, chip, mtd->writesize); + + nfc->ecc_buf = devm_kmalloc(nfc->dev, chip->ecc.bytes * chip->ecc.steps, + GFP_KERNEL); + if (!nfc->ecc_buf) + return -ENOMEM; + + switch (mtd->oobsize) { + case 16: + /* Legacy Xilinx layout */ + mtd_set_ooblayout(mtd, &pl35x_ecc_ooblayout16_ops); + chip->bbt_options |= NAND_BBT_NO_OOB_BBM; + break; + case 64: + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + break; + default: + dev_err(nfc->dev, "Unsupported OOB size\n"); + return -EOPNOTSUPP; + } + + return ret; +} + +static int pl35x_nand_attach_chip(struct nand_chip *chip) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_NONE && + (!chip->ecc.size || !chip->ecc.strength)) { + if (requirements->step_size && requirements->strength) { + chip->ecc.size = requirements->step_size; + chip->ecc.strength = requirements->strength; + } else { + dev_info(nfc->dev, + "No minimum ECC strength, using 1b/512B\n"); + chip->ecc.size = 512; + chip->ecc.strength = 1; + } + } + + if (mtd->writesize <= SZ_512) + plnand->addr_cycles = 1; + else + plnand->addr_cycles = 2; + + if (chip->options & NAND_ROW_ADDR_3) + plnand->addr_cycles += 3; + else + plnand->addr_cycles += 2; + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_DIE: + /* Keep these legacy BBT descriptors for ON_DIE situations */ + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + fallthrough; + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + break; + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = pl35x_nand_init_hw_ecc_controller(nfc, chip); + if (ret) + return ret; + break; + default: + dev_err(nfc->dev, "Unsupported ECC mode: %d\n", + chip->ecc.engine_type); + return -EINVAL; + } + + return 0; +} + +static const struct nand_controller_ops pl35x_nandc_ops = { + .attach_chip = pl35x_nand_attach_chip, + .exec_op = pl35x_nfc_exec_op, + .setup_interface = pl35x_nfc_setup_interface, +}; + +static int pl35x_nand_reset_state(struct pl35x_nandc *nfc) +{ + int ret; + + /* Disable interrupts and clear their status */ + writel(PL35X_SMC_MEMC_CFG_CLR_INT_CLR_1 | + PL35X_SMC_MEMC_CFG_CLR_ECC_INT_DIS_1 | + PL35X_SMC_MEMC_CFG_CLR_INT_DIS_1, + nfc->conf_regs + PL35X_SMC_MEMC_CFG_CLR); + + /* Set default bus width to 8-bit */ + ret = pl35x_smc_set_buswidth(nfc, PL35X_SMC_OPMODE_BW_8); + if (ret) + return ret; + + /* Ensure the ECC controller is bypassed by default */ + ret = pl35x_smc_set_ecc_mode(nfc, NULL, PL35X_SMC_ECC_CFG_MODE_BYPASS); + if (ret) + return ret; + + /* + * Configure the commands that the ECC block uses to detect the + * operations it should start/end. + */ + writel(PL35X_SMC_ECC_CMD1_WRITE(NAND_CMD_SEQIN) | + PL35X_SMC_ECC_CMD1_READ(NAND_CMD_READ0) | + PL35X_SMC_ECC_CMD1_READ_END(NAND_CMD_READSTART) | + PL35X_SMC_ECC_CMD1_READ_END_VALID(NAND_CMD_READ1), + nfc->conf_regs + PL35X_SMC_ECC_CMD1); + writel(PL35X_SMC_ECC_CMD2_WRITE_COL_CHG(NAND_CMD_RNDIN) | + PL35X_SMC_ECC_CMD2_READ_COL_CHG(NAND_CMD_RNDOUT) | + PL35X_SMC_ECC_CMD2_READ_COL_CHG_END(NAND_CMD_RNDOUTSTART) | + PL35X_SMC_ECC_CMD2_READ_COL_CHG_END_VALID(NAND_CMD_READ1), + nfc->conf_regs + PL35X_SMC_ECC_CMD2); + + return 0; +} + +static int pl35x_nand_chip_init(struct pl35x_nandc *nfc, + struct device_node *np) +{ + struct pl35x_nand *plnand; + struct nand_chip *chip; + struct mtd_info *mtd; + int cs, ret; + + plnand = devm_kzalloc(nfc->dev, sizeof(*plnand), GFP_KERNEL); + if (!plnand) + return -ENOMEM; + + ret = of_property_read_u32(np, "reg", &cs); + if (ret) + return ret; + + if (cs >= PL35X_NAND_MAX_CS) { + dev_err(nfc->dev, "Wrong CS %d\n", cs); + return -EINVAL; + } + + if (test_and_set_bit(cs, &nfc->assigned_cs)) { + dev_err(nfc->dev, "Already assigned CS %d\n", cs); + return -EINVAL; + } + + plnand->cs = cs; + + chip = &plnand->chip; + chip->options = NAND_BUSWIDTH_AUTO | NAND_USES_DMA | NAND_NO_SUBPAGE_WRITE; + chip->bbt_options = NAND_BBT_USE_FLASH; + chip->controller = &nfc->controller; + mtd = nand_to_mtd(chip); + mtd->dev.parent = nfc->dev; + nand_set_flash_node(chip, nfc->dev->of_node); + if (!mtd->name) { + mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL, + "%s", PL35X_NANDC_DRIVER_NAME); + if (!mtd->name) { + dev_err(nfc->dev, "Failed to allocate mtd->name\n"); + return -ENOMEM; + } + } + + ret = nand_scan(chip, 1); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + nand_cleanup(chip); + return ret; + } + + list_add_tail(&plnand->node, &nfc->chips); + + return ret; +} + +static void pl35x_nand_chips_cleanup(struct pl35x_nandc *nfc) +{ + struct pl35x_nand *plnand, *tmp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(plnand, tmp, &nfc->chips, node) { + chip = &plnand->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&plnand->node); + } +} + +static int pl35x_nand_chips_init(struct pl35x_nandc *nfc) +{ + struct device_node *np = nfc->dev->of_node, *nand_np; + int nchips = of_get_child_count(np); + int ret; + + if (!nchips || nchips > PL35X_NAND_MAX_CS) { + dev_err(nfc->dev, "Incorrect number of NAND chips (%d)\n", + nchips); + return -EINVAL; + } + + for_each_child_of_node(np, nand_np) { + ret = pl35x_nand_chip_init(nfc, nand_np); + if (ret) { + of_node_put(nand_np); + pl35x_nand_chips_cleanup(nfc); + break; + } + } + + return ret; +} + +static int pl35x_nand_probe(struct platform_device *pdev) +{ + struct device *smc_dev = pdev->dev.parent; + struct amba_device *smc_amba = to_amba_device(smc_dev); + struct pl35x_nandc *nfc; + u32 ret; + + nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = &pdev->dev; + nand_controller_init(&nfc->controller); + nfc->controller.ops = &pl35x_nandc_ops; + INIT_LIST_HEAD(&nfc->chips); + + nfc->conf_regs = devm_ioremap_resource(&smc_amba->dev, &smc_amba->res); + if (IS_ERR(nfc->conf_regs)) + return PTR_ERR(nfc->conf_regs); + + nfc->io_regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->io_regs)) + return PTR_ERR(nfc->io_regs); + + ret = pl35x_nand_reset_state(nfc); + if (ret) + return ret; + + ret = pl35x_nand_chips_init(nfc); + if (ret) + return ret; + + platform_set_drvdata(pdev, nfc); + + return 0; +} + +static int pl35x_nand_remove(struct platform_device *pdev) +{ + struct pl35x_nandc *nfc = platform_get_drvdata(pdev); + + pl35x_nand_chips_cleanup(nfc); + + return 0; +} + +static const struct of_device_id pl35x_nand_of_match[] = { + { .compatible = "arm,pl353-nand-r2p1" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pl35x_nand_of_match); + +static struct platform_driver pl35x_nandc_driver = { + .probe = pl35x_nand_probe, + .remove = pl35x_nand_remove, + .driver = { + .name = PL35X_NANDC_DRIVER_NAME, + .of_match_table = pl35x_nand_of_match, + }, +}; +module_platform_driver(pl35x_nandc_driver); + +MODULE_AUTHOR("Xilinx, Inc."); +MODULE_ALIAS("platform:" PL35X_NANDC_DRIVER_NAME); +MODULE_DESCRIPTION("ARM PL35X NAND controller driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 47b4c8bd5db1c986c8b4b7189791701fe5c1897e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 10 Jun 2021 15:49:02 +0200 Subject: mtd: rawnand: omap: Aggregate the HW configuration of the ELM Instead of calling elm_config() for each possible BCH configuration, just save the BCH configuration that must be applied and use it in a single call at the bottom. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610134906.3503303-2-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/omap2.c | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index c75e7a0b101f..a36c4e7e5bc1 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -1921,6 +1921,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) struct omap_nand_info *info = mtd_to_omap(mtd); struct device *dev = &info->pdev->dev; int min_oobbytes = BADBLOCK_MARKER_LENGTH; + int elm_bch_strength = -1; int oobbytes_per_step; dma_cap_mask_t mask; int err; @@ -2074,12 +2075,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) chip->ecc.write_subpage = omap_write_subpage_bch; mtd_set_ooblayout(mtd, &omap_ooblayout_ops); oobbytes_per_step = chip->ecc.bytes; - - err = elm_config(info->elm_dev, BCH4_ECC, - mtd->writesize / chip->ecc.size, - chip->ecc.size, chip->ecc.bytes); - if (err < 0) - return err; + elm_bch_strength = BCH4_ECC; break; case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: @@ -2116,13 +2112,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) chip->ecc.write_subpage = omap_write_subpage_bch; mtd_set_ooblayout(mtd, &omap_ooblayout_ops); oobbytes_per_step = chip->ecc.bytes; - - err = elm_config(info->elm_dev, BCH8_ECC, - mtd->writesize / chip->ecc.size, - chip->ecc.size, chip->ecc.bytes); - if (err < 0) - return err; - + elm_bch_strength = BCH8_ECC; break; case OMAP_ECC_BCH16_CODE_HW: @@ -2138,17 +2128,19 @@ static int omap_nand_attach_chip(struct nand_chip *chip) chip->ecc.write_subpage = omap_write_subpage_bch; mtd_set_ooblayout(mtd, &omap_ooblayout_ops); oobbytes_per_step = chip->ecc.bytes; + elm_bch_strength = BCH16_ECC; + break; + default: + dev_err(dev, "Invalid or unsupported ECC scheme\n"); + return -EINVAL; + } - err = elm_config(info->elm_dev, BCH16_ECC, + if (elm_bch_strength >= 0) { + err = elm_config(info->elm_dev, elm_bch_strength, mtd->writesize / chip->ecc.size, chip->ecc.size, chip->ecc.bytes); if (err < 0) return err; - - break; - default: - dev_err(dev, "Invalid or unsupported ECC scheme\n"); - return -EINVAL; } /* Check if NAND device's OOB is enough to store ECC signatures */ -- cgit v1.2.3-59-g8ed1b From e29973843d9293a57c5c8f14094d0fa74b770ed7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 10 Jun 2021 15:49:03 +0200 Subject: mtd: rawnand: omap: Rename a macro The macro BADBLOCK_MARKER_LENGTH is pretty long and could be reduced to BBM_LEN which is more handy to use in the code. This is a purely cosmetic change and is only done to avoid further change to contain 100+ char lines just because of this definition. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610134906.3503303-3-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/omap2.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index a36c4e7e5bc1..659ca8ab490e 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -131,7 +131,7 @@ #define BCH_ECC_SIZE0 0x0 /* ecc_size0 = 0, no oob protection */ #define BCH_ECC_SIZE1 0x20 /* ecc_size1 = 32 */ -#define BADBLOCK_MARKER_LENGTH 2 +#define BBM_LEN 2 static u_char bch16_vector[] = {0xf5, 0x24, 0x1c, 0xd0, 0x61, 0xb3, 0xf1, 0x55, 0x2e, 0x2c, 0x86, 0xa3, 0xed, 0x36, 0x1b, 0x78, @@ -1649,8 +1649,8 @@ static int omap_read_page_bch(struct nand_chip *chip, uint8_t *buf, /* Read oob bytes */ nand_change_read_column_op(chip, - mtd->writesize + BADBLOCK_MARKER_LENGTH, - chip->oob_poi + BADBLOCK_MARKER_LENGTH, + mtd->writesize + BBM_LEN, + chip->oob_poi + BBM_LEN, chip->ecc.total, false); /* Calculate ecc bytes */ @@ -1820,7 +1820,7 @@ static int omap_ooblayout_ecc(struct mtd_info *mtd, int section, { struct omap_nand_info *info = mtd_to_omap(mtd); struct nand_chip *chip = &info->nand; - int off = BADBLOCK_MARKER_LENGTH; + int off = BBM_LEN; if (info->ecc_opt == OMAP_ECC_HAM1_CODE_HW && !(chip->options & NAND_BUSWIDTH_16)) @@ -1840,7 +1840,7 @@ static int omap_ooblayout_free(struct mtd_info *mtd, int section, { struct omap_nand_info *info = mtd_to_omap(mtd); struct nand_chip *chip = &info->nand; - int off = BADBLOCK_MARKER_LENGTH; + int off = BBM_LEN; if (info->ecc_opt == OMAP_ECC_HAM1_CODE_HW && !(chip->options & NAND_BUSWIDTH_16)) @@ -1870,7 +1870,7 @@ static int omap_sw_ooblayout_ecc(struct mtd_info *mtd, int section, struct nand_device *nand = mtd_to_nanddev(mtd); unsigned int nsteps = nanddev_get_ecc_nsteps(nand); unsigned int ecc_bytes = nanddev_get_ecc_bytes_per_step(nand); - int off = BADBLOCK_MARKER_LENGTH; + int off = BBM_LEN; if (section >= nsteps) return -ERANGE; @@ -1891,7 +1891,7 @@ static int omap_sw_ooblayout_free(struct mtd_info *mtd, int section, struct nand_device *nand = mtd_to_nanddev(mtd); unsigned int nsteps = nanddev_get_ecc_nsteps(nand); unsigned int ecc_bytes = nanddev_get_ecc_bytes_per_step(nand); - int off = BADBLOCK_MARKER_LENGTH; + int off = BBM_LEN; if (section) return -ERANGE; @@ -1920,7 +1920,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) struct mtd_info *mtd = nand_to_mtd(chip); struct omap_nand_info *info = mtd_to_omap(mtd); struct device *dev = &info->pdev->dev; - int min_oobbytes = BADBLOCK_MARKER_LENGTH; + int min_oobbytes = BBM_LEN; int elm_bch_strength = -1; int oobbytes_per_step; dma_cap_mask_t mask; -- cgit v1.2.3-59-g8ed1b From 11a017782852ad6a59d7a449b69478ddc1d30cf0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 10 Jun 2021 15:49:04 +0200 Subject: mtd: rawnand: omap: Check return values Check the return value of many helpers which might return error codes. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610134906.3503303-4-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/omap2.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index 659ca8ab490e..bb28f2b0ecef 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -1528,7 +1528,9 @@ static int omap_write_page_bch(struct nand_chip *chip, const uint8_t *buf, int ret; uint8_t *ecc_calc = chip->ecc.calc_buf; - nand_prog_page_begin_op(chip, page, 0, NULL, 0); + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; /* Enable GPMC ecc engine */ chip->ecc.hwctl(chip, NAND_ECC_WRITE); @@ -1537,7 +1539,9 @@ static int omap_write_page_bch(struct nand_chip *chip, const uint8_t *buf, chip->legacy.write_buf(chip, buf, mtd->writesize); /* Update ecc vector from GPMC result registers */ - omap_calculate_ecc_bch_multi(mtd, buf, &ecc_calc[0]); + ret = omap_calculate_ecc_bch_multi(mtd, buf, &ecc_calc[0]); + if (ret) + return ret; ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, chip->ecc.total); @@ -1580,7 +1584,9 @@ static int omap_write_subpage_bch(struct nand_chip *chip, u32 offset, * ECC is calculated for all subpages but we choose * only what we want. */ - nand_prog_page_begin_op(chip, page, 0, NULL, 0); + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; /* Enable GPMC ECC engine */ chip->ecc.hwctl(chip, NAND_ECC_WRITE); @@ -1639,7 +1645,9 @@ static int omap_read_page_bch(struct nand_chip *chip, uint8_t *buf, int stat, ret; unsigned int max_bitflips = 0; - nand_read_page_op(chip, page, 0, NULL, 0); + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; /* Enable GPMC ecc engine */ chip->ecc.hwctl(chip, NAND_ECC_READ); @@ -1648,13 +1656,17 @@ static int omap_read_page_bch(struct nand_chip *chip, uint8_t *buf, chip->legacy.read_buf(chip, buf, mtd->writesize); /* Read oob bytes */ - nand_change_read_column_op(chip, - mtd->writesize + BBM_LEN, - chip->oob_poi + BBM_LEN, - chip->ecc.total, false); + ret = nand_change_read_column_op(chip, + mtd->writesize + BBM_LEN, + chip->oob_poi + BBM_LEN, + chip->ecc.total, false); + if (ret) + return ret; /* Calculate ecc bytes */ - omap_calculate_ecc_bch_multi(mtd, buf, ecc_calc); + ret = omap_calculate_ecc_bch_multi(mtd, buf, ecc_calc); + if (ret) + return ret; ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0, chip->ecc.total); -- cgit v1.2.3-59-g8ed1b From 496030b1b71e2129ce2cf1411f18ee55ec305ab6 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 10 Jun 2021 15:49:05 +0200 Subject: mtd: rawnand: omap: Various style fixes Fix the comments style, declare the variables in a reverse Christmas tree order, add an upper case character at the beginning of a sentence. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20210610134906.3503303-5-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/omap2.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index bb28f2b0ecef..3d5a421a6b5c 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -1525,8 +1525,8 @@ static int omap_write_page_bch(struct nand_chip *chip, const uint8_t *buf, int oob_required, int page) { struct mtd_info *mtd = nand_to_mtd(chip); - int ret; uint8_t *ecc_calc = chip->ecc.calc_buf; + int ret; ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); if (ret) @@ -1595,7 +1595,7 @@ static int omap_write_subpage_bch(struct nand_chip *chip, u32 offset, chip->legacy.write_buf(chip, buf, mtd->writesize); for (step = 0; step < ecc_steps; step++) { - /* mask ECC of un-touched subpages by padding 0xFF */ + /* Mask ECC of un-touched subpages with 0xFFs */ if (step < start_step || step > end_step) memset(ecc_calc, 0xff, ecc_bytes); else @@ -1608,8 +1608,10 @@ static int omap_write_subpage_bch(struct nand_chip *chip, u32 offset, ecc_calc += ecc_bytes; } - /* copy calculated ECC for whole page to chip->buffer->oob */ - /* this include masked-value(0xFF) for unwritten subpages */ + /* + * Copy the calculated ECC for the whole page including the + * masked values (0xFF) corresponding to unwritten subpages. + */ ecc_calc = chip->ecc.calc_buf; ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, chip->ecc.total); @@ -1642,8 +1644,8 @@ static int omap_read_page_bch(struct nand_chip *chip, uint8_t *buf, struct mtd_info *mtd = nand_to_mtd(chip); uint8_t *ecc_calc = chip->ecc.calc_buf; uint8_t *ecc_code = chip->ecc.code_buf; - int stat, ret; unsigned int max_bitflips = 0; + int stat, ret; ret = nand_read_page_op(chip, page, 0, NULL, 0); if (ret) -- cgit v1.2.3-59-g8ed1b From c06dd49fd59a0abd6fa3d9fc5f6eb1776af4e5e4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 10 Jun 2021 15:49:06 +0200 Subject: mtd: rawnand: omap: Add larger page NAND chips support There is no reason to be limited to 4kiB page NAND chips just because this is the maximum length the ELM is able to handle in one go. Just call the ELM several times and it will process as many data as needed. Here we introduce the concept of ECC page (which is at most 4kiB). The ELM will be sought as many times as there are ECC pages. Signed-off-by: Miquel Raynal Tested-by: Ryan Barnett Link: https://lore.kernel.org/linux-mtd/20210610134906.3503303-6-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/omap2.c | 183 +++++++++++++++++++++++++--------------- drivers/mtd/nand/raw/omap_elm.c | 2 +- 2 files changed, 116 insertions(+), 69 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index 3d5a421a6b5c..b1839eef5b65 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -171,6 +171,10 @@ struct omap_nand_info { struct device *elm_dev; /* NAND ready gpio */ struct gpio_desc *ready_gpiod; + unsigned int neccpg; + unsigned int nsteps_per_eccpg; + unsigned int eccpg_size; + unsigned int eccpg_bytes; }; static inline struct omap_nand_info *mtd_to_omap(struct mtd_info *mtd) @@ -1355,7 +1359,7 @@ static int omap_elm_correct_data(struct nand_chip *chip, u_char *data, { struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); struct nand_ecc_ctrl *ecc = &info->nand.ecc; - int eccsteps = info->nand.ecc.steps; + int eccsteps = info->nsteps_per_eccpg; int i , j, stat = 0; int eccflag, actual_eccbytes; struct elm_errorvec err_vec[ERROR_VECTOR_MAX]; @@ -1525,28 +1529,37 @@ static int omap_write_page_bch(struct nand_chip *chip, const uint8_t *buf, int oob_required, int page) { struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); uint8_t *ecc_calc = chip->ecc.calc_buf; + unsigned int eccpg; int ret; ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); if (ret) return ret; - /* Enable GPMC ecc engine */ - chip->ecc.hwctl(chip, NAND_ECC_WRITE); + for (eccpg = 0; eccpg < info->neccpg; eccpg++) { + /* Enable GPMC ecc engine */ + chip->ecc.hwctl(chip, NAND_ECC_WRITE); - /* Write data */ - chip->legacy.write_buf(chip, buf, mtd->writesize); + /* Write data */ + chip->legacy.write_buf(chip, buf + (eccpg * info->eccpg_size), + info->eccpg_size); - /* Update ecc vector from GPMC result registers */ - ret = omap_calculate_ecc_bch_multi(mtd, buf, &ecc_calc[0]); - if (ret) - return ret; + /* Update ecc vector from GPMC result registers */ + ret = omap_calculate_ecc_bch_multi(mtd, + buf + (eccpg * info->eccpg_size), + ecc_calc); + if (ret) + return ret; - ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, - chip->ecc.total); - if (ret) - return ret; + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, + chip->oob_poi, + eccpg * info->eccpg_bytes, + info->eccpg_bytes); + if (ret) + return ret; + } /* Write ecc vector to OOB area */ chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); @@ -1570,12 +1583,13 @@ static int omap_write_subpage_bch(struct nand_chip *chip, u32 offset, int oob_required, int page) { struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); u8 *ecc_calc = chip->ecc.calc_buf; int ecc_size = chip->ecc.size; int ecc_bytes = chip->ecc.bytes; - int ecc_steps = chip->ecc.steps; u32 start_step = offset / ecc_size; u32 end_step = (offset + data_len - 1) / ecc_size; + unsigned int eccpg; int step, ret = 0; /* @@ -1588,36 +1602,44 @@ static int omap_write_subpage_bch(struct nand_chip *chip, u32 offset, if (ret) return ret; - /* Enable GPMC ECC engine */ - chip->ecc.hwctl(chip, NAND_ECC_WRITE); - - /* Write data */ - chip->legacy.write_buf(chip, buf, mtd->writesize); - - for (step = 0; step < ecc_steps; step++) { - /* Mask ECC of un-touched subpages with 0xFFs */ - if (step < start_step || step > end_step) - memset(ecc_calc, 0xff, ecc_bytes); - else - ret = _omap_calculate_ecc_bch(mtd, buf, ecc_calc, step); + for (eccpg = 0; eccpg < info->neccpg; eccpg++) { + /* Enable GPMC ECC engine */ + chip->ecc.hwctl(chip, NAND_ECC_WRITE); + + /* Write data */ + chip->legacy.write_buf(chip, buf + (eccpg * info->eccpg_size), + info->eccpg_size); + + for (step = 0; step < info->nsteps_per_eccpg; step++) { + unsigned int base_step = eccpg * info->nsteps_per_eccpg; + const u8 *bufoffs = buf + (eccpg * info->eccpg_size); + + /* Mask ECC of un-touched subpages with 0xFFs */ + if ((step + base_step) < start_step || + (step + base_step) > end_step) + memset(ecc_calc + (step * ecc_bytes), 0xff, + ecc_bytes); + else + ret = _omap_calculate_ecc_bch(mtd, + bufoffs + (step * ecc_size), + ecc_calc + (step * ecc_bytes), + step); + + if (ret) + return ret; + } + /* + * Copy the calculated ECC for the whole page including the + * masked values (0xFF) corresponding to unwritten subpages. + */ + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, + eccpg * info->eccpg_bytes, + info->eccpg_bytes); if (ret) return ret; - - buf += ecc_size; - ecc_calc += ecc_bytes; } - /* - * Copy the calculated ECC for the whole page including the - * masked values (0xFF) corresponding to unwritten subpages. - */ - ecc_calc = chip->ecc.calc_buf; - ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, - chip->ecc.total); - if (ret) - return ret; - /* write OOB buffer to NAND device */ chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); @@ -1642,46 +1664,60 @@ static int omap_read_page_bch(struct nand_chip *chip, uint8_t *buf, int oob_required, int page) { struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); uint8_t *ecc_calc = chip->ecc.calc_buf; uint8_t *ecc_code = chip->ecc.code_buf; - unsigned int max_bitflips = 0; + unsigned int max_bitflips = 0, eccpg; int stat, ret; ret = nand_read_page_op(chip, page, 0, NULL, 0); if (ret) return ret; - /* Enable GPMC ecc engine */ - chip->ecc.hwctl(chip, NAND_ECC_READ); - - /* Read data */ - chip->legacy.read_buf(chip, buf, mtd->writesize); + for (eccpg = 0; eccpg < info->neccpg; eccpg++) { + /* Enable GPMC ecc engine */ + chip->ecc.hwctl(chip, NAND_ECC_READ); - /* Read oob bytes */ - ret = nand_change_read_column_op(chip, - mtd->writesize + BBM_LEN, - chip->oob_poi + BBM_LEN, - chip->ecc.total, false); - if (ret) - return ret; + /* Read data */ + ret = nand_change_read_column_op(chip, eccpg * info->eccpg_size, + buf + (eccpg * info->eccpg_size), + info->eccpg_size, false); + if (ret) + return ret; - /* Calculate ecc bytes */ - ret = omap_calculate_ecc_bch_multi(mtd, buf, ecc_calc); - if (ret) - return ret; + /* Read oob bytes */ + ret = nand_change_read_column_op(chip, + mtd->writesize + BBM_LEN + + (eccpg * info->eccpg_bytes), + chip->oob_poi + BBM_LEN + + (eccpg * info->eccpg_bytes), + info->eccpg_bytes, false); + if (ret) + return ret; - ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0, - chip->ecc.total); - if (ret) - return ret; + /* Calculate ecc bytes */ + ret = omap_calculate_ecc_bch_multi(mtd, + buf + (eccpg * info->eccpg_size), + ecc_calc); + if (ret) + return ret; - stat = chip->ecc.correct(chip, buf, ecc_code, ecc_calc); + ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, + chip->oob_poi, + eccpg * info->eccpg_bytes, + info->eccpg_bytes); + if (ret) + return ret; - if (stat < 0) { - mtd->ecc_stats.failed++; - } else { - mtd->ecc_stats.corrected += stat; - max_bitflips = max_t(unsigned int, max_bitflips, stat); + stat = chip->ecc.correct(chip, + buf + (eccpg * info->eccpg_size), + ecc_code, ecc_calc); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } } return max_bitflips; @@ -2150,9 +2186,20 @@ static int omap_nand_attach_chip(struct nand_chip *chip) } if (elm_bch_strength >= 0) { + chip->ecc.steps = mtd->writesize / chip->ecc.size; + info->neccpg = chip->ecc.steps / ERROR_VECTOR_MAX; + if (info->neccpg) { + info->nsteps_per_eccpg = ERROR_VECTOR_MAX; + } else { + info->neccpg = 1; + info->nsteps_per_eccpg = chip->ecc.steps; + } + info->eccpg_size = info->nsteps_per_eccpg * chip->ecc.size; + info->eccpg_bytes = info->nsteps_per_eccpg * chip->ecc.bytes; + err = elm_config(info->elm_dev, elm_bch_strength, - mtd->writesize / chip->ecc.size, - chip->ecc.size, chip->ecc.bytes); + info->nsteps_per_eccpg, chip->ecc.size, + chip->ecc.bytes); if (err < 0) return err; } diff --git a/drivers/mtd/nand/raw/omap_elm.c b/drivers/mtd/nand/raw/omap_elm.c index 550695a4c1ab..2b21ce04b3ec 100644 --- a/drivers/mtd/nand/raw/omap_elm.c +++ b/drivers/mtd/nand/raw/omap_elm.c @@ -116,7 +116,7 @@ int elm_config(struct device *dev, enum bch_ecc bch_type, return -EINVAL; } /* ELM support 8 error syndrome process */ - if (ecc_steps > ERROR_VECTOR_MAX) { + if (ecc_steps > ERROR_VECTOR_MAX && ecc_steps % ERROR_VECTOR_MAX) { dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps); return -EINVAL; } -- cgit v1.2.3-59-g8ed1b From c17e5c85b32f8809135f3211ba2525fb98b5c09f Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 18 Jun 2021 10:33:31 +0100 Subject: mtd: spi-nor: remove redundant continue statement The continue statement at the end of a for-loop has no effect, invert the if expression and remove the continue. Addresses-Coverity: ("Continue has no effect") Signed-off-by: Colin Ian King Signed-off-by: Vignesh Raghavendra Reviewed-by: Michael Walle Reviewed-by: Pratyush Yadav --- drivers/mtd/spi-nor/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 970ed6e3f3ba..cc08bd707378 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1411,9 +1411,7 @@ spi_nor_find_best_erase_type(const struct spi_nor_erase_map *map, continue; spi_nor_div_by_erase_size(erase, addr, &rem); - if (rem) - continue; - else + if (!rem) return erase; } -- cgit v1.2.3-59-g8ed1b