diff options
Diffstat (limited to 'drivers')
61 files changed, 2454 insertions, 802 deletions
diff --git a/drivers/bcma/driver_chipcommon_nflash.c b/drivers/bcma/driver_chipcommon_nflash.c index d4f699aef8c4..a1a814750b4a 100644 --- a/drivers/bcma/driver_chipcommon_nflash.c +++ b/drivers/bcma/driver_chipcommon_nflash.c @@ -7,18 +7,28 @@ #include "bcma_private.h" +#include <linux/bitops.h> #include <linux/platform_device.h> +#include <linux/platform_data/brcmnand.h> #include <linux/bcma/bcma.h> +/* Alternate NAND controller driver name in order to allow both bcm47xxnflash + * and bcma_brcmnand to be built into the same kernel image. + */ +static const char *bcma_nflash_alt_name = "bcma_brcmnand"; + struct platform_device bcma_nflash_dev = { .name = "bcma_nflash", .num_resources = 0, }; +static const char *probes[] = { "bcm47xxpart", NULL }; + /* Initialize NAND flash access */ int bcma_nflash_init(struct bcma_drv_cc *cc) { struct bcma_bus *bus = cc->core->bus; + u32 reg; if (bus->chipinfo.id != BCMA_CHIP_ID_BCM4706 && cc->core->id.rev != 38) { @@ -33,8 +43,16 @@ int bcma_nflash_init(struct bcma_drv_cc *cc) cc->nflash.present = true; if (cc->core->id.rev == 38 && - (cc->status & BCMA_CC_CHIPST_5357_NAND_BOOT)) + (cc->status & BCMA_CC_CHIPST_5357_NAND_BOOT)) { cc->nflash.boot = true; + /* Determine the chip select that is being used */ + reg = bcma_cc_read32(cc, BCMA_CC_NAND_CS_NAND_SELECT) & 0xff; + cc->nflash.brcmnand_info.chip_select = ffs(reg) - 1; + cc->nflash.brcmnand_info.part_probe_types = probes; + cc->nflash.brcmnand_info.ecc_stepsize = 512; + cc->nflash.brcmnand_info.ecc_strength = 1; + bcma_nflash_dev.name = bcma_nflash_alt_name; + } /* Prepare platform device, but don't register it yet. It's too early, * malloc (required by device_private_init) is not available yet. */ diff --git a/drivers/mtd/devices/mchp23k256.c b/drivers/mtd/devices/mchp23k256.c index 008df9d8898d..3a6ea7a6a30c 100644 --- a/drivers/mtd/devices/mchp23k256.c +++ b/drivers/mtd/devices/mchp23k256.c @@ -229,13 +229,27 @@ static const struct of_device_id mchp23k256_of_table[] = { }; MODULE_DEVICE_TABLE(of, mchp23k256_of_table); +static const struct spi_device_id mchp23k256_spi_ids[] = { + { + .name = "mchp23k256", + .driver_data = (kernel_ulong_t)&mchp23k256_caps, + }, + { + .name = "mchp23lcv1024", + .driver_data = (kernel_ulong_t)&mchp23lcv1024_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(spi, mchp23k256_spi_ids); + static struct spi_driver mchp23k256_driver = { .driver = { .name = "mchp23k256", - .of_match_table = of_match_ptr(mchp23k256_of_table), + .of_match_table = mchp23k256_of_table, }, .probe = mchp23k256_probe, .remove = mchp23k256_remove, + .id_table = mchp23k256_spi_ids, }; module_spi_driver(mchp23k256_driver); diff --git a/drivers/mtd/devices/mchp48l640.c b/drivers/mtd/devices/mchp48l640.c index a3fd426df74b..40cd5041174c 100644 --- a/drivers/mtd/devices/mchp48l640.c +++ b/drivers/mtd/devices/mchp48l640.c @@ -357,13 +357,23 @@ static const struct of_device_id mchp48l640_of_table[] = { }; MODULE_DEVICE_TABLE(of, mchp48l640_of_table); +static const struct spi_device_id mchp48l640_spi_ids[] = { + { + .name = "48l640", + .driver_data = (kernel_ulong_t)&mchp48l640_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(spi, mchp48l640_spi_ids); + static struct spi_driver mchp48l640_driver = { .driver = { .name = "mchp48l640", - .of_match_table = of_match_ptr(mchp48l640_of_table), + .of_match_table = mchp48l640_of_table, }, .probe = mchp48l640_probe, .remove = mchp48l640_remove, + .id_table = mchp48l640_spi_ids, }; module_spi_driver(mchp48l640_driver); diff --git a/drivers/mtd/devices/spear_smi.c b/drivers/mtd/devices/spear_smi.c index 2e00862389dd..24073518587f 100644 --- a/drivers/mtd/devices/spear_smi.c +++ b/drivers/mtd/devices/spear_smi.c @@ -969,7 +969,7 @@ static int spear_smi_probe(struct platform_device *pdev) goto err; } - dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_ATOMIC); + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { ret = -ENOMEM; goto err; diff --git a/drivers/mtd/hyperbus/Kconfig b/drivers/mtd/hyperbus/Kconfig index 46c7e407e378..30ffc4c16e4d 100644 --- a/drivers/mtd/hyperbus/Kconfig +++ b/drivers/mtd/hyperbus/Kconfig @@ -15,7 +15,7 @@ if MTD_HYPERBUS config HBMC_AM654 tristate "HyperBus controller driver for AM65x SoC" - depends on ARM64 || COMPILE_TEST + depends on ARCH_K3 || COMPILE_TEST select MULTIPLEXER imply MUX_MMIO help diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index 243f28a3206b..64d2b093f114 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -158,6 +158,7 @@ static void mtd_blktrans_work(struct mtd_blktrans_dev *dev) } background_done = 0; + cond_resched(); spin_lock_irq(&dev->queue_lock); } } diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index eef87b28d6c8..c5c3e9387647 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -336,49 +336,31 @@ static const struct device_type mtd_devtype = { .release = mtd_release, }; -static int mtd_partid_debug_show(struct seq_file *s, void *p) -{ - struct mtd_info *mtd = s->private; - - seq_printf(s, "%s\n", mtd->dbg.partid); +static bool mtd_expert_analysis_mode; - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(mtd_partid_debug); - -static int mtd_partname_debug_show(struct seq_file *s, void *p) +#ifdef CONFIG_DEBUG_FS +bool mtd_check_expert_analysis_mode(void) { - struct mtd_info *mtd = s->private; - - seq_printf(s, "%s\n", mtd->dbg.partname); + const char *mtd_expert_analysis_warning = + "Bad block checks have been entirely disabled.\n" + "This is only reserved for post-mortem forensics and debug purposes.\n" + "Never enable this mode if you do not know what you are doing!\n"; - return 0; + return WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning); } - -DEFINE_SHOW_ATTRIBUTE(mtd_partname_debug); +EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode); +#endif 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; if (IS_ERR_OR_NULL(dfs_dir_mtd)) return; - root = debugfs_create_dir(dev_name(dev), dfs_dir_mtd); - mtd->dbg.dfs_dir = root; - - if (master->dbg.partid) - debugfs_create_file("partid", 0400, root, master, - &mtd_partid_debug_fops); - - if (master->dbg.partname) - debugfs_create_file("partname", 0400, root, master, - &mtd_partname_debug_fops); + mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(dev), dfs_dir_mtd); } #ifndef CONFIG_MMU @@ -2372,14 +2354,6 @@ static struct backing_dev_info * __init mtd_bdi_init(const char *name) return ret ? ERR_PTR(ret) : bdi; } -char *mtd_expert_analysis_warning = - "Bad block checks have been entirely disabled.\n" - "This is only reserved for post-mortem forensics and debug purposes.\n" - "Never enable this mode if you do not know what you are doing!\n"; -EXPORT_SYMBOL_GPL(mtd_expert_analysis_warning); -bool mtd_expert_analysis_mode; -EXPORT_SYMBOL_GPL(mtd_expert_analysis_mode); - static struct proc_dir_entry *proc_mtd; static int __init init_mtd(void) diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index b40455234cbd..9b249826ef93 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -46,6 +46,13 @@ config MTD_NAND_ECC_SW_BCH ECC codes. They are used with NAND devices requiring more than 1 bit of error correction. +config MTD_NAND_ECC_MXIC + bool "Macronix external hardware ECC engine" + depends on HAS_IOMEM + select MTD_NAND_ECC + help + This enables support for the hardware ECC engine from Macronix. + endmenu endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 1c0b46960eb1..a4e6b7ae0614 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -10,3 +10,4 @@ obj-y += spi/ nandcore-$(CONFIG_MTD_NAND_ECC) += ecc.o nandcore-$(CONFIG_MTD_NAND_ECC_SW_HAMMING) += ecc-sw-hamming.o nandcore-$(CONFIG_MTD_NAND_ECC_SW_BCH) += ecc-sw-bch.o +nandcore-$(CONFIG_MTD_NAND_ECC_MXIC) += ecc-mxic.o diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c index 416947f28b67..dbd7b06524b3 100644 --- a/drivers/mtd/nand/core.c +++ b/drivers/mtd/nand/core.c @@ -21,7 +21,7 @@ */ bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos) { - if (WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning)) + if (mtd_check_expert_analysis_mode()) return false; if (nanddev_bbt_is_initialized(nand)) { @@ -235,7 +235,9 @@ static int nanddev_get_ecc_engine(struct nand_device *nand) nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand); break; case NAND_ECC_ENGINE_TYPE_ON_HOST: - pr_err("On-host hardware ECC engines not supported yet\n"); + nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand); + if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER) + return -EPROBE_DEFER; break; default: pr_err("Missing ECC engine type\n"); @@ -255,7 +257,7 @@ static int nanddev_put_ecc_engine(struct nand_device *nand) { switch (nand->ecc.ctx.conf.engine_type) { case NAND_ECC_ENGINE_TYPE_ON_HOST: - pr_err("On-host hardware ECC engines not supported yet\n"); + nand_ecc_put_on_host_hw_engine(nand); break; case NAND_ECC_ENGINE_TYPE_NONE: case NAND_ECC_ENGINE_TYPE_SOFT: @@ -300,7 +302,9 @@ int nanddev_ecc_engine_init(struct nand_device *nand) /* Look for the ECC engine to use */ ret = nanddev_get_ecc_engine(nand); if (ret) { - pr_err("No ECC engine found\n"); + if (ret != -EPROBE_DEFER) + pr_err("No ECC engine found\n"); + return ret; } diff --git a/drivers/mtd/nand/ecc-mxic.c b/drivers/mtd/nand/ecc-mxic.c new file mode 100644 index 000000000000..8afdca731b87 --- /dev/null +++ b/drivers/mtd/nand/ecc-mxic.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for Macronix external hardware ECC engine for NAND devices, also + * called DPE for Data Processing Engine. + * + * Copyright © 2019 Macronix + * Author: Miquel Raynal <miquel.raynal@bootlin.com> + */ + +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand-ecc-mxic.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* DPE Configuration */ +#define DP_CONFIG 0x00 +#define ECC_EN BIT(0) +#define ECC_TYP(idx) (((idx) << 3) & GENMASK(6, 3)) +/* DPE Interrupt Status */ +#define INTRPT_STS 0x04 +#define TRANS_CMPLT BIT(0) +#define SDMA_MAIN BIT(1) +#define SDMA_SPARE BIT(2) +#define ECC_ERR BIT(3) +#define TO_SPARE BIT(4) +#define TO_MAIN BIT(5) +/* DPE Interrupt Status Enable */ +#define INTRPT_STS_EN 0x08 +/* DPE Interrupt Signal Enable */ +#define INTRPT_SIG_EN 0x0C +/* Host Controller Configuration */ +#define HC_CONFIG 0x10 +#define DEV2MEM 0 /* TRANS_TYP_DMA in the spec */ +#define MEM2MEM BIT(4) /* TRANS_TYP_IO in the spec */ +#define MAPPING BIT(5) /* TRANS_TYP_MAPPING in the spec */ +#define ECC_PACKED 0 /* LAYOUT_TYP_INTEGRATED in the spec */ +#define ECC_INTERLEAVED BIT(2) /* LAYOUT_TYP_DISTRIBUTED in the spec */ +#define BURST_TYP_FIXED 0 +#define BURST_TYP_INCREASING BIT(0) +/* Host Controller Slave Address */ +#define HC_SLV_ADDR 0x14 +/* ECC Chunk Size */ +#define CHUNK_SIZE 0x20 +/* Main Data Size */ +#define MAIN_SIZE 0x24 +/* Spare Data Size */ +#define SPARE_SIZE 0x28 +#define META_SZ(reg) ((reg) & GENMASK(7, 0)) +#define PARITY_SZ(reg) (((reg) & GENMASK(15, 8)) >> 8) +#define RSV_SZ(reg) (((reg) & GENMASK(23, 16)) >> 16) +#define SPARE_SZ(reg) ((reg) >> 24) +/* ECC Chunk Count */ +#define CHUNK_CNT 0x30 +/* SDMA Control */ +#define SDMA_CTRL 0x40 +#define WRITE_NAND 0 +#define READ_NAND BIT(1) +#define CONT_NAND BIT(29) +#define CONT_SYSM BIT(30) /* Continue System Memory? */ +#define SDMA_STRT BIT(31) +/* SDMA Address of Main Data */ +#define SDMA_MAIN_ADDR 0x44 +/* SDMA Address of Spare Data */ +#define SDMA_SPARE_ADDR 0x48 +/* DPE Version Number */ +#define DP_VER 0xD0 +#define DP_VER_OFFSET 16 + +/* Status bytes between each chunk of spare data */ +#define STAT_BYTES 4 +#define NO_ERR 0x00 +#define MAX_CORR_ERR 0x28 +#define UNCORR_ERR 0xFE +#define ERASED_CHUNK 0xFF + +struct mxic_ecc_engine { + struct device *dev; + void __iomem *regs; + int irq; + struct completion complete; + struct nand_ecc_engine external_engine; + struct nand_ecc_engine pipelined_engine; + struct mutex lock; +}; + +struct mxic_ecc_ctx { + /* ECC machinery */ + unsigned int data_step_sz; + unsigned int oob_step_sz; + unsigned int parity_sz; + unsigned int meta_sz; + u8 *status; + int steps; + + /* DMA boilerplate */ + struct nand_ecc_req_tweak_ctx req_ctx; + u8 *oobwithstat; + struct scatterlist sg[2]; + struct nand_page_io_req *req; + unsigned int pageoffs; +}; + +static struct mxic_ecc_engine *ext_ecc_eng_to_mxic(struct nand_ecc_engine *eng) +{ + return container_of(eng, struct mxic_ecc_engine, external_engine); +} + +static struct mxic_ecc_engine *pip_ecc_eng_to_mxic(struct nand_ecc_engine *eng) +{ + return container_of(eng, struct mxic_ecc_engine, pipelined_engine); +} + +static struct mxic_ecc_engine *nand_to_mxic(struct nand_device *nand) +{ + struct nand_ecc_engine *eng = nand->ecc.engine; + + if (eng->integration == NAND_ECC_ENGINE_INTEGRATION_EXTERNAL) + return ext_ecc_eng_to_mxic(eng); + else + return pip_ecc_eng_to_mxic(eng); +} + +static int mxic_ecc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + + if (section < 0 || section >= ctx->steps) + return -ERANGE; + + oobregion->offset = (section * ctx->oob_step_sz) + ctx->meta_sz; + oobregion->length = ctx->parity_sz; + + return 0; +} + +static int mxic_ecc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + + if (section < 0 || section >= ctx->steps) + return -ERANGE; + + if (!section) { + oobregion->offset = 2; + oobregion->length = ctx->meta_sz - 2; + } else { + oobregion->offset = section * ctx->oob_step_sz; + oobregion->length = ctx->meta_sz; + } + + return 0; +} + +static const struct mtd_ooblayout_ops mxic_ecc_ooblayout_ops = { + .ecc = mxic_ecc_ooblayout_ecc, + .free = mxic_ecc_ooblayout_free, +}; + +static void mxic_ecc_disable_engine(struct mxic_ecc_engine *mxic) +{ + u32 reg; + + reg = readl(mxic->regs + DP_CONFIG); + reg &= ~ECC_EN; + writel(reg, mxic->regs + DP_CONFIG); +} + +static void mxic_ecc_enable_engine(struct mxic_ecc_engine *mxic) +{ + u32 reg; + + reg = readl(mxic->regs + DP_CONFIG); + reg |= ECC_EN; + writel(reg, mxic->regs + DP_CONFIG); +} + +static void mxic_ecc_disable_int(struct mxic_ecc_engine *mxic) +{ + writel(0, mxic->regs + INTRPT_SIG_EN); +} + +static void mxic_ecc_enable_int(struct mxic_ecc_engine *mxic) +{ + writel(TRANS_CMPLT, mxic->regs + INTRPT_SIG_EN); +} + +static irqreturn_t mxic_ecc_isr(int irq, void *dev_id) +{ + struct mxic_ecc_engine *mxic = dev_id; + u32 sts; + + sts = readl(mxic->regs + INTRPT_STS); + if (!sts) + return IRQ_NONE; + + if (sts & TRANS_CMPLT) + complete(&mxic->complete); + + writel(sts, mxic->regs + INTRPT_STS); + + return IRQ_HANDLED; +} + +static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + struct nand_ecc_props *reqs = &nand->ecc.requirements; + struct nand_ecc_props *user = &nand->ecc.user_conf; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int step_size = 0, strength = 0, desired_correction = 0, steps, idx; + static const int possible_strength[] = {4, 8, 40, 48}; + static const int spare_size[] = {32, 32, 96, 96}; + struct mxic_ecc_ctx *ctx; + u32 spare_reg; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + nand->ecc.ctx.priv = ctx; + + /* Only large page NAND chips may use BCH */ + if (mtd->oobsize < 64) { + pr_err("BCH cannot be used with small page NAND chips\n"); + return -EINVAL; + } + + mtd_set_ooblayout(mtd, &mxic_ecc_ooblayout_ops); + + /* Enable all status bits */ + writel(TRANS_CMPLT | SDMA_MAIN | SDMA_SPARE | ECC_ERR | + TO_SPARE | TO_MAIN, mxic->regs + INTRPT_STS_EN); + + /* Configure the correction depending on the NAND device topology */ + if (user->step_size && user->strength) { + step_size = user->step_size; + strength = user->strength; + } else if (reqs->step_size && reqs->strength) { + step_size = reqs->step_size; + strength = reqs->strength; + } + + if (step_size && strength) { + steps = mtd->writesize / step_size; + desired_correction = steps * strength; + } + + /* Step size is fixed to 1kiB, strength may vary (4 possible values) */ + conf->step_size = SZ_1K; + steps = mtd->writesize / conf->step_size; + + ctx->status = devm_kzalloc(dev, steps * sizeof(u8), GFP_KERNEL); + if (!ctx->status) + return -ENOMEM; + + if (desired_correction) { + strength = desired_correction / steps; + + for (idx = 0; idx < ARRAY_SIZE(possible_strength); idx++) + if (possible_strength[idx] >= strength) + break; + + idx = min_t(unsigned int, idx, + ARRAY_SIZE(possible_strength) - 1); + } else { + /* Missing data, maximize the correction */ + idx = ARRAY_SIZE(possible_strength) - 1; + } + + /* Tune the selected strength until it fits in the OOB area */ + for (; idx >= 0; idx--) { + if (spare_size[idx] * steps <= mtd->oobsize) + break; + } + + /* This engine cannot be used with this NAND device */ + if (idx < 0) + return -EINVAL; + + /* Configure the engine for the desired strength */ + writel(ECC_TYP(idx), mxic->regs + DP_CONFIG); + conf->strength = possible_strength[idx]; + spare_reg = readl(mxic->regs + SPARE_SIZE); + + ctx->steps = steps; + ctx->data_step_sz = mtd->writesize / steps; + ctx->oob_step_sz = mtd->oobsize / steps; |
