summaryrefslogtreecommitdiff
path: root/drivers/mtd/nand
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/nand')
-rw-r--r--drivers/mtd/nand/Kconfig7
-rw-r--r--drivers/mtd/nand/Makefile1
-rw-r--r--drivers/mtd/nand/core.c12
-rw-r--r--drivers/mtd/nand/ecc-mxic.c879
-rw-r--r--drivers/mtd/nand/ecc.c119
-rw-r--r--drivers/mtd/nand/onenand/generic.c7
-rw-r--r--drivers/mtd/nand/raw/Kconfig13
-rw-r--r--drivers/mtd/nand/raw/atmel/nand-controller.c18
-rw-r--r--drivers/mtd/nand/raw/atmel/pmecc.c4
-rw-r--r--drivers/mtd/nand/raw/brcmnand/Makefile2
-rw-r--r--drivers/mtd/nand/raw/brcmnand/bcma_nand.c132
-rw-r--r--drivers/mtd/nand/raw/brcmnand/brcmnand.c162
-rw-r--r--drivers/mtd/nand/raw/brcmnand/brcmnand.h29
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c27
-rw-r--r--drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c2
-rw-r--r--drivers/mtd/nand/raw/ingenic/jz4780_bch.c2
-rw-r--r--drivers/mtd/nand/raw/mtk_ecc.c2
-rw-r--r--drivers/mtd/nand/raw/nand_base.c83
-rw-r--r--drivers/mtd/nand/raw/nand_bbt.c2
-rw-r--r--drivers/mtd/nand/raw/nandsim.c47
-rw-r--r--drivers/mtd/nand/raw/omap2.c2
-rw-r--r--drivers/mtd/nand/raw/omap_elm.c4
-rw-r--r--drivers/mtd/nand/raw/pl35x-nand-controller.c2
-rw-r--r--drivers/mtd/nand/raw/renesas-nand-controller.c2
-rw-r--r--drivers/mtd/nand/raw/rockchip-nand-controller.c1
-rw-r--r--drivers/mtd/nand/raw/sh_flctl.c2
-rw-r--r--drivers/mtd/nand/raw/stm32_fmc2_nand.c40
-rw-r--r--drivers/mtd/nand/spi/core.c51
-rw-r--r--drivers/mtd/nand/spi/macronix.c2
29 files changed, 1490 insertions, 166 deletions
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;
+ ctx->parity_sz = PARITY_SZ(spare_reg);
+ ctx->meta_sz = META_SZ(spare_reg);
+
+ /* Ensure buffers will contain enough bytes to store the STAT_BYTES */
+ ctx->req_ctx.oob_buffer_size = nanddev_per_page_oobsize(nand) +
+ (ctx->steps * STAT_BYTES);
+ ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
+ if (ret)
+ return ret;
+
+ ctx->oobwithstat = kmalloc(mtd->oobsize + (ctx->steps * STAT_BYTES),
+ GFP_KERNEL);
+ if (!ctx->oobwithstat) {
+ ret = -ENOMEM;
+ goto cleanup_req_tweak;
+ }
+
+ sg_init_table(ctx->sg, 2);
+
+ /* Configuration dump and sanity checks */
+ dev_err(dev, "DPE version number: %d\n",
+ readl(mxic->regs + DP_VER) >> DP_VER_OFFSET);
+ dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
+ dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
+ dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
+ dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
+ dev_err(dev, "Parity size: %d\n", ctx->parity_sz);
+ dev_err(dev, "Meta size: %d\n", ctx->meta_sz);
+
+ if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) !=
+ SPARE_SZ(spare_reg)) {
+ dev_err(dev, "Wrong OOB configuration: %d + %d + %ld != %d\n",
+ ctx->meta_sz, ctx->parity_sz, RSV_SZ(spare_reg),
+ SPARE_SZ(spare_reg));
+ ret = -EINVAL;
+ goto free_oobwithstat;
+ }
+
+ if (ctx->oob_step_sz != SPARE_SZ(spare_reg)) {
+ dev_err(dev, "Wrong OOB configuration: %d != %d\n",
+ ctx->oob_step_sz, SPARE_SZ(spare_reg));
+ ret = -EINVAL;
+ goto free_oobwithstat;
+ }
+
+ return 0;
+
+free_oobwithstat:
+ kfree(ctx->oobwithstat);
+cleanup_req_tweak:
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
+
+ return ret;
+}
+
+static int mxic_ecc_init_ctx_external(struct nand_device *nand)
+{
+ struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
+ struct device *dev = nand->ecc.engine->dev;
+ int ret;
+
+ dev_info(dev, "Macronix ECC engine in external mode\n");
+
+ ret = mxic_ecc_init_ctx(nand, dev);
+ if (ret)
+ return ret;
+
+ /* Trigger each step manually */
+ writel(1, mxic->regs + CHUNK_CNT);
+ writel(BURST_TYP_INCREASING | ECC_PACKED | MEM2MEM,
+ mxic->regs + HC_CONFIG);
+
+ return 0;
+}
+
+static int mxic_ecc_init_ctx_pipelined(struct nand_device *nand)
+{
+ struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
+ struct mxic_ecc_ctx *ctx;
+ struct device *dev;
+ int ret;
+
+ dev = nand_ecc_get_engine_dev(nand->ecc.engine->dev);
+ if (!dev)
+ return -EINVAL;
+
+ dev_info(dev, "Macronix ECC engine in pipelined/mapping mode\n");
+
+ ret = mxic_ecc_init_ctx(nand, dev);
+ if (ret)
+ return ret;
+
+ ctx = nand_to_ecc_ctx(nand);
+
+ /* All steps should be handled in one go directly by the internal DMA */
+ writel(ctx->steps, mxic->regs + CHUNK_CNT);
+
+ /*
+ * Interleaved ECC scheme cannot be used otherwise factory bad block
+ * markers would be lost. A packed layout is mandatory.
+ */
+ writel(BURST_TYP_INCREASING | ECC_PACKED | MAPPING,
+ mxic->regs + HC_CONFIG);
+
+ return 0;
+}
+
+static void mxic_ecc_cleanup_ctx(struct nand_device *nand)
+{
+ struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
+
+ if (ctx) {
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
+ kfree(ctx->oobwithstat);
+ }
+}
+
+static int mxic_ecc_data_xfer_wait_for_completion(struct mxic_ecc_engine *mxic)
+{
+ u32 val;
+ int ret;
+
+ if (mxic->irq) {
+ reinit_completion(&mxic->complete);
+ mxic_ecc_enable_int(mxic);
+ ret = wait_for_completion_timeout(&mxic->complete,
+ msecs_to_jiffies(1000));
+ mxic_ecc_disable_int(mxic);
+ } else {
+ ret = readl_poll_timeout(mxic->regs + INTRPT_STS, val,
+ val & TRANS_CMPLT, 10, USEC_PER_SEC);
+ writel(val, mxic->regs + INTRPT_STS);
+ }
+
+ if (ret) {
+ dev_err(mxic->dev, "Timeout on data xfer completion\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int mxic_ecc_process_data(struct mxic_ecc_engine *mxic,
+ unsigned int direction)
+{
+ unsigned int dir = (direction == NAND_PAGE_READ) ?
+ READ_NAND : WRITE_NAND;
+ int ret;
+
+ mxic_ecc_enable_engine(mxic);
+
+ /* Trigger processing */
+ writel(SDMA_STRT | dir, mxic->regs + SDMA_CTRL);
+
+ /* Wait for completion */
+ ret = mxic_ecc_data_xfer_wait_for_completion(mxic);
+
+ mxic_ecc_disable_engine(mxic);
+
+ return ret;
+}
+
+int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng,
+ unsigned int direction, dma_addr_t dirmap)
+{
+ struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
+
+ if (dirmap)
+ writel(dirmap, mxic->regs + HC_SLV_ADDR);
+
+ return mxic_ecc_process_data(mxic, direction);
+}
+EXPORT_SYMBOL_GPL(mxic_ecc_process_data_pipelined);
+
+static void mxic_ecc_extract_status_bytes(struct mxic_ecc_ctx *ctx)
+{
+ u8 *buf = ctx->oobwithstat;
+ int next_stat_pos;
+ int step;
+
+ /* Extract the ECC status */
+ for (step = 0; step < ctx->steps; step++) {
+ next_stat_pos = ctx->oob_step_sz +
+ ((STAT_BYTES + ctx->oob_step_sz) * step);
+
+ ctx->status[step] = buf[next_stat_pos];
+ }
+}
+
+static void mxic_ecc_reconstruct_oobbuf(struct mxic_ecc_ctx *ctx,
+ u8 *dst, const u8 *src)
+{
+ int step;
+
+ /* Reconstruct the OOB buffer linearly (without the ECC status bytes) */
+ for (step = 0; step < ctx->steps; step++)
+ memcpy(dst + (step * ctx->oob_step_sz),
+ src + (step * (ctx->oob_step_sz + STAT_BYTES)),
+ ctx->oob_step_sz);
+}
+
+static void mxic_ecc_add_room_in_oobbuf(struct mxic_ecc_ctx *ctx,
+ u8 *dst, const u8 *src)
+{
+ int step;
+
+ /* Add some space in the OOB buffer for the status bytes */
+ for (step = 0; step < ctx->steps; step++)
+ memcpy(dst + (step * (ctx->oob_step_sz + STAT_BYTES)),
+ src + (step * ctx->oob_step_sz),
+ ctx->oob_step_sz);
+}
+
+static int mxic_ecc_count_biterrs(struct mxic_ecc_engine *mxic,
+ struct nand_device *nand)
+{
+ struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ struct device *dev = mxic->dev;
+ unsigned int max_bf = 0;
+ bool failure = false;
+ int step;
+
+ for (step = 0; step < ctx->steps; step++) {
+ u8 stat = ctx->status[step];
+
+ if (stat == NO_ERR) {
+ dev_dbg(dev, "ECC step %d: no error\n", step);
+ } else if (stat == ERASED_CHUNK) {
+ dev_dbg(dev, "ECC step %d: erased\n", step);
+ } else if (stat == UNCORR_ERR || stat > MAX_CORR_ERR) {
+ dev_dbg(dev, "ECC step %d: uncorrectable\n", step);
+ mtd->ecc_stats.failed++;
+ failure = true;
+ } else {
+ dev_dbg(dev, "ECC step %d: %d bits corrected\n",
+ step, stat);
+ max_bf = max_t(unsigned int, max_bf, stat);
+ mtd->ecc_stats.corrected += stat;
+ }
+ }
+
+ return failure ? -EBADMSG : max_bf;
+}
+
+/* External ECC engine helpers */
+static int mxic_ecc_prepare_io_req_external(struct nand_device *nand,
+ struct nand_page_io_req *req)
+{
+ struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
+ struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ int offset, nents, step, ret;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
+ ctx->req = req;
+
+ if (req->type == NAND_PAGE_READ)
+ return 0;
+
+ mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat,
+ ctx->req->oobbuf.out);
+
+ sg_set_buf(&ctx->sg[0], req->databuf.out, req->datalen);
+ sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
+ req->ooblen + (ctx->steps * STAT_BYTES));
+
+ nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
+ if (!nents)
+ return -EINVAL;
+
+ mutex_lock(&mxic->lock);
+
+ for (step = 0; step < ctx->steps; step++) {
+ writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
+ mxic->regs + SDMA_MAIN_ADDR);
+ writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
+ mxic->regs + SDMA_SPARE_ADDR);
+ ret = mxic_ecc_process_data(mxic, ctx->req->type);
+ if (ret)
+ break;
+ }
+
+ mutex_unlock(&mxic->lock);
+
+ dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
+
+ if (ret)
+ return ret;
+
+ /* Retrieve the calculated ECC bytes */
+ for (step = 0; step < ctx->steps; step++) {
+ offset = ctx->meta_sz + (step * ctx->oob_step_sz);
+ mtd_ooblayout_get_eccbytes(mtd,
+ (u8 *)ctx->req->oobbuf.out + offset,
+ ctx->oobwithstat + (step * STAT_BYTES),
+ step * ctx->parity_sz,
+ ctx->parity_sz);
+ }
+
+ return 0;
+}
+
+static int mxic_ecc_finish_io_req_external(struct nand_device *nand,
+ struct nand_page_io_req *req)
+{
+ struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
+ struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
+ int nents, step, ret;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ if (req->type == NAND_PAGE_WRITE) {
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+ return 0;
+ }
+
+ /* Copy the OOB buffer and add room for the ECC engine status bytes */
+ mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
+
+ sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
+ sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
+ req->ooblen + (ctx->steps * STAT_BYTES));
+ nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
+ if (!nents)
+ return -EINVAL;
+
+ mutex_lock(&mxic->lock);
+
+ for (step = 0; step < ctx->steps; step++) {
+ writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
+ mxic->regs + SDMA_MAIN_ADDR);
+ writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
+ mxic->regs + SDMA_SPARE_ADDR);
+ ret = mxic_ecc_process_data(mxic, ctx->req->type);
+ if (ret)
+ break;
+ }
+
+ mutex_unlock(&mxic->lock);
+
+ dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
+
+ if (ret) {
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+ return ret;
+ }
+
+ /* Extract the status bytes and reconstruct the buffer */
+ mxic_ecc_extract_status_bytes(ctx);
+ mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in, ctx->oobwithstat);
+
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+
+ return mxic_ecc_count_biterrs(mxic, nand);
+}
+
+/* Pipelined ECC engine helpers */
+static int mxic_ecc_prepare_io_req_pipelined(struct nand_device *nand,
+ struct nand_page_io_req *req)
+{
+ struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
+ struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
+ int nents;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
+ ctx->req = req;
+
+ /* Copy the OOB buffer and add room for the ECC engine status bytes */
+ mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
+
+ sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
+ sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
+ req->ooblen + (ctx->steps * STAT_BYTES));
+
+ nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
+ if (!nents)
+ return -EINVAL;
+
+ mutex_lock(&mxic->lock);
+
+ writel(sg_dma_address(&ctx->sg[0]), mxic->regs + SDMA_MAIN_ADDR);
+ writel(sg_dma_address(&ctx->sg[1]), mxic->regs + SDMA_SPARE_ADDR);
+
+ return 0;
+}
+
+static int mxic_ecc_finish_io_req_pipelined(struct nand_device *nand,
+ struct nand_page_io_req *req)
+{
+ struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
+ struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
+ int ret = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ mutex_unlock(&mxic->lock);
+
+ dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
+
+ if (req->type == NAND_PAGE_READ) {
+ mxic_ecc_extract_status_bytes(ctx);
+ mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in,
+ ctx->oobwithstat);
+ ret = mxic_ecc_count_biterrs(mxic, nand);
+ }
+
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+
+ return ret;
+}
+
+static struct nand_ecc_engine_ops mxic_ecc_engine_external_ops = {
+ .init_ctx = mxic_ecc_init_ctx_external,
+ .cleanup_ctx = mxic_ecc_cleanup_ctx,
+ .prepare_io_req = mxic_ecc_prepare_io_req_external,
+ .finish_io_req = mxic_ecc_finish_io_req_external,
+};
+
+static struct nand_ecc_engine_ops mxic_ecc_engine_pipelined_ops = {
+ .init_ctx = mxic_ecc_init_ctx_pipelined,
+ .cleanup_ctx = mxic_ecc_cleanup_ctx,
+ .prepare_io_req = mxic_ecc_prepare_io_req_pipelined,
+ .finish_io_req = mxic_ecc_finish_io_req_pipelined,
+};
+
+struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void)
+{
+ return &mxic_ecc_engine_pipelined_ops;
+}
+EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_ops);
+
+static struct platform_device *
+mxic_ecc_get_pdev(struct platform_device *spi_pdev)
+{
+ struct platform_device *eng_pdev;
+ struct device_node *np;
+
+ /* Retrieve the nand-ecc-engine phandle */
+ np = of_parse_phandle(spi_pdev->dev.of_node, "nand-ecc-engine", 0);
+ if (!np)
+ return NULL;
+
+ /* Jump to the engine's device node */
+ eng_pdev = of_find_device_by_node(np);
+ of_node_put(np);
+
+ return eng_pdev;
+}
+
+void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng)
+{
+ struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
+
+ platform_device_put(to_platform_device(mxic->dev));
+}
+EXPORT_SYMBOL_GPL(mxic_ecc_put_pipelined_engine);
+
+struct nand_ecc_engine *
+mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev)
+{
+ struct platform_device *eng_pdev;
+ struct mxic_ecc_engine *mxic;
+
+ eng_pdev = mxic_ecc_get_pdev(spi_pdev);
+ if (!eng_pdev)
+ return ERR_PTR(-ENODEV);
+
+ mxic = platform_get_drvdata(eng_pdev);
+ if (!mxic) {
+ platform_device_put(eng_pdev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return &mxic->pipelined_engine;
+}
+EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_engine);
+
+/*
+ * Only the external ECC engine is exported as the pipelined is SoC specific, so
+ * it is registered directly by the drivers that wrap it.
+ */
+static int mxic_ecc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mxic_ecc_engine *mxic;
+ int ret;
+
+ mxic = devm_kzalloc(&pdev->dev, sizeof(*mxic), GFP_KERNEL);
+ if (!mxic)
+ return -ENOMEM;
+
+ mxic->dev = &pdev->dev;
+
+ /*
+ * Both memory regions for the ECC engine itself and the AXI slave
+ * address are mandatory.
+ */
+ mxic->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mxic->regs)) {
+ dev_err(&pdev->dev, "Missing memory region\n");
+ return PTR_ERR(mxic->regs);
+ }
+
+ mxic_ecc_disable_engine(mxic);
+ mxic_ecc_disable_int(mxic);
+
+ /* IRQ is optional yet much more efficient */
+ mxic->irq = platform_get_irq_byname_optional(pdev, "ecc-engine");
+ if (mxic->irq > 0) {
+ ret = devm_request_irq(&pdev->dev, mxic->irq, mxic_ecc_isr, 0,
+ "mxic-ecc", mxic);
+ if (ret)
+ return ret;
+ } else {
+ dev_info(dev, "Invalid or missing IRQ, fallback to polling\n");
+ mxic->irq = 0;
+ }
+
+ mutex_init(&mxic->lock);
+
+ /*
+ * In external mode, the device is the ECC engine. In pipelined mode,
+ * the device is the host controller. The device is used to match the
+ * right ECC engine based on the DT properties.
+ */
+ mxic->external_engine.dev = &pdev->dev;
+ mxic->external_engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
+ mxic->external_engine.ops = &mxic_ecc_engine_external_ops;
+
+ nand_ecc_register_on_host_hw_engine(&mxic->external_engine);
+
+ platform_set_drvdata(pdev, mxic);
+
+ return 0;
+}
+
+static int mxic_ecc_remove(struct platform_device *pdev)
+{
+ struct mxic_ecc_engine *mxic = platform_get_drvdata(pdev);
+
+ nand_ecc_unregister_on_host_hw_engine(&mxic->external_engine);
+
+ return 0;
+}
+
+static const struct of_device_id mxic_ecc_of_ids[] = {
+ {
+ .compatible = "mxicy,nand-ecc-engine-rev3",
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mxic_ecc_of_ids);
+
+static struct platform_driver mxic_ecc_driver = {
+ .driver = {
+ .name = "mxic-nand-ecc-engine",
+ .of_match_table = mxic_ecc_of_ids,
+ },
+ .probe = mxic_ecc_probe,
+ .remove = mxic_ecc_remove,
+};
+module_platform_driver(mxic_ecc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
+MODULE_DESCRIPTION("Macronix NAND hardware ECC controller");
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c
index 6c43dfda01d4..5250764cedee 100644
--- a/drivers/mtd/nand/ecc.c
+++ b/drivers/mtd/nand/ecc.c
@@ -96,6 +96,12 @@
#include <linux/module.h>
#include <linux/mtd/nand.h>
#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+static LIST_HEAD(on_host_hw_engines);
+static DEFINE_MUTEX(on_host_hw_engines_mutex);
/**
* nand_ecc_init_ctx - Init the ECC engine context
@@ -611,6 +617,119 @@ struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand)
}
EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine);
+int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine)
+{
+ struct nand_ecc_engine *item;
+
+ if (!engine)
+ return -EINVAL;
+
+ /* Prevent multiple registrations of one engine */
+ list_for_each_entry(item, &on_host_hw_engines, node)
+ if (item == engine)
+ return 0;
+
+ mutex_lock(&on_host_hw_engines_mutex);
+ list_add_tail(&engine->node, &on_host_hw_engines);
+ mutex_unlock(&on_host_hw_engines_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(nand_ecc_register_on_host_hw_engine);
+
+int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine)
+{
+ if (!engine)
+ return -EINVAL;
+
+ mutex_lock(&on_host_hw_engines_mutex);
+ list_del(&engine->node);
+ mutex_unlock(&on_host_hw_engines_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(nand_ecc_unregister_on_host_hw_engine);
+
+static struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *dev)
+{
+ struct nand_ecc_engine *item;
+
+ list_for_each_entry(item, &on_host_hw_engines, node)
+ if (item->dev == dev)
+ return item;
+
+ return NULL;
+}
+
+struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand)
+{
+ struct nand_ecc_engine *engine = NULL;
+ struct device *dev = &nand->mtd.dev;
+ struct platform_device *pdev;
+ struct device_node *np;
+
+ if (list_empty(&on_host_hw_engines))
+ return NULL;
+
+ /* Check for an explicit nand-ecc-engine property */
+ np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0);
+ if (np) {
+ pdev = of_find_device_by_node(np);
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ engine = nand_ecc_match_on_host_hw_engine(&pdev->dev);
+ platform_device_put(pdev);
+ of_node_put(np);
+
+ if (!engine)
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (engine)
+ get_device(engine->dev);
+
+ return engine;
+}
+EXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine);
+
+void nand_ecc_put_on_host_hw_engine(struct nand_device *nand)
+{
+ put_device(nand->ecc.engine->dev);
+}
+EXPORT_SYMBOL(nand_ecc_put_on_host_hw_engine);
+
+/*
+ * In the case of a pipelined engine, the device registering the ECC
+ * engine is not necessarily the ECC engine itself but may be a host controller.
+ * It is then useful to provide a helper to retrieve the right device object
+ * which actually represents the ECC engine.
+ */
+struct device *nand_ecc_get_engine_dev(struct device *host)
+{
+ struct platform_device *ecc_pdev;
+ struct device_node *np;
+
+ /*
+ * If the device node contains this property, it means we need to follow
+ * it in order to get the right ECC engine device we are looking for.
+ */
+ np = of_parse_phandle(host->of_node, "nand-ecc-engine", 0);
+ if (!np)
+ return host;
+
+ ecc_pdev = of_find_device_by_node(np);
+ if (!ecc_pdev) {
+ of_node_put(np);
+ return NULL;
+ }
+
+ platform_device_put(ecc_pdev);
+ of_node_put(np);
+
+ return &ecc_pdev->dev;
+}
+
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
MODULE_DESCRIPTION("Generic ECC engine");
diff --git a/drivers/mtd/nand/onenand/generic.c b/drivers/mtd/nand/onenand/generic.c
index 8b6f4da5d720..a4b8b65fe15f 100644
--- a/drivers/mtd/nand/onenand/generic.c
+++ b/drivers/mtd/nand/onenand/generic.c
@@ -53,7 +53,12 @@ static int generic_onenand_probe(struct platform_device *pdev)
}
info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL;
- info->onenand.irq = platform_get_irq(pdev, 0);
+
+ err = platform_get_irq(pdev, 0);
+ if (err < 0)
+ goto out_iounmap;
+
+ info->onenand.irq = err;
info->mtd.dev.parent = &pdev->dev;
info->mtd.priv = &info->onenand;
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 820e5dc3bc9b..9b078e78f3fa 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -209,6 +209,19 @@ config MTD_NAND_BRCMNAND
originally designed for Set-Top Box but is used on various BCM7xxx,
BCM3xxx, BCM63xxx, iProc/Cygnus and more.
+if MTD_NAND_BRCMNAND
+
+config MTD_NAND_BRCMNAND_BCMA
+ tristate "Broadcom BCMA NAND controller"
+ depends on BCMA_NFLASH
+ depends on BCMA
+ help
+ Enables the BRCMNAND controller over BCMA on BCM47186/BCM5358 SoCs.
+ The glue driver will take care of performing the low-level I/O
+ operations to interface the BRCMNAND controller over the BCMA bus.
+
+endif # MTD_NAND_BRCMNAND
+
config MTD_NAND_BCM47XXNFLASH
tristate "BCM4706 BCMA NAND controller"
depends on BCMA_NFLASH
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index f3276ee9e4fe..6ef14442c71a 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -1938,7 +1938,7 @@ static const struct atmel_smc_nand_ebi_csa_cfg sam9x60_ebi_csa = {
.nfd0_on_d16 = AT91_SFR_CCFG_NFD0_ON_D16,
};
-static const struct of_device_id atmel_ebi_csa_regmap_of_ids[] = {
+static const struct of_device_id __maybe_unused atmel_ebi_csa_regmap_of_ids[] = {
{
.compatible = "atmel,at91sam9260-matrix",
.data = &at91sam9260_ebi_csa,
@@ -2060,13 +2060,15 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc,
nc->mck = of_clk_get(dev->parent->of_node, 0);
if (IS_ERR(nc->mck)) {
dev_err(dev, "Failed to retrieve MCK clk\n");
- return PTR_ERR(nc->mck);
+ ret = PTR_ERR(nc->mck);
+ goto out_release_dma;
}
np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0);
if (!np) {
dev_err(dev, "Missing or invalid atmel,smc property\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto out_release_dma;
}
nc->smc = syscon_node_to_regmap(np);
@@ -2074,10 +2076,16 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc,
if (IS_ERR(nc->smc)) {
ret = PTR_ERR(nc->smc);
dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret);
- return ret;
+ goto out_release_dma;
}
return 0;
+
+out_release_dma:
+ if (nc->dmac)
+ dma_release_channel(nc->dmac);
+
+ return ret;
}
static int
@@ -2648,7 +2656,7 @@ static SIMPLE_DEV_PM_OPS(atmel_nand_controller_pm_ops, NULL,
static struct platform_driver atmel_nand_controller_driver = {
.driver = {
.name = "atmel-nand-controller",
- .of_match_table = of_match_ptr(atmel_nand_controller_of_ids),
+ .of_match_table = atmel_nand_controller_of_ids,
.pm = &atmel_nand_controller_pm_ops,
},
.probe = atmel_nand_controller_probe,
diff --git a/drivers/mtd/nand/raw/atmel/pmecc.c b/drivers/mtd/nand/raw/atmel/pmecc.c
index 498e41ccabbd..4d7dc8a9c373 100644
--- a/drivers/mtd/nand/raw/atmel/pmecc.c
+++ b/drivers/mtd/nand/raw/atmel/pmecc.c
@@ -920,7 +920,7 @@ static struct atmel_pmecc_caps sama5d2_caps = {
.correct_erased_chunks = true,
};
-static const struct of_device_id atmel_pmecc_legacy_match[] = {
+static const struct of_device_id __maybe_unused atmel_pmecc_legacy_match[] = {
{ .compatible = "atmel,sama5d4-nand", &sama5d4_caps },
{ .compatible = "atmel,sama5d2-nand", &sama5d2_caps },
{ /* sentinel */ }
@@ -1003,7 +1003,7 @@ static int atmel_pmecc_probe(struct platform_device *pdev)
static struct platform_driver atmel_pmecc_driver = {
.driver = {
.name = "atmel-pmecc",
- .of_match_table = of_match_ptr(atmel_pmecc_match),
+ .of_match_table = atmel_pmecc_match,
},
.probe = atmel_pmecc_probe,
};
diff --git a/drivers/mtd/nand/raw/brcmnand/Makefile b/drivers/mtd/nand/raw/brcmnand/Makefile
index 195b845e48b8..16dc7254200e 100644
--- a/drivers/mtd/nand/raw/brcmnand/Makefile
+++ b/drivers/mtd/nand/raw/brcmnand/Makefile
@@ -6,3 +6,5 @@ obj-$(CONFIG_MTD_NAND_BRCMNAND) += bcm63138_nand.o
obj-$(CONFIG_MTD_NAND_BRCMNAND) += bcm6368_nand.o
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmstb_nand.o
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand.o
+
+obj-$(CONFIG_MTD_NAND_BRCMNAND_BCMA) += bcma_nand.o
diff --git a/drivers/mtd/nand/raw/brcmnand/bcma_nand.c b/drivers/mtd/nand/raw/brcmnand/bcma_nand.c
new file mode 100644
index 000000000000..dd27977919fb
--- /dev/null
+++ b/drivers/mtd/nand/raw/brcmnand/bcma_nand.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright © 2021 Broadcom
+ */
+#include <linux/bcma/bcma.h>
+#include <linux/bcma/bcma_driver_chipcommon.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "brcmnand.h"
+
+struct brcmnand_bcma_soc {
+ struct brcmnand_soc soc;
+ struct bcma_drv_cc *cc;
+};
+
+static inline bool brcmnand_bcma_needs_swapping(u32 offset)
+{
+ switch (offset) {
+ case BCMA_CC_NAND_SPARE_RD0:
+ case BCMA_CC_NAND_SPARE_RD4:
+ case BCMA_CC_NAND_SPARE_RD8:
+ case BCMA_CC_NAND_SPARE_RD12:
+ case BCMA_CC_NAND_SPARE_WR0:
+ case BCMA_CC_NAND_SPARE_WR4:
+ case BCMA_CC_NAND_SPARE_WR8:
+ case BCMA_CC_NAND_SPARE_WR12:
+ case BCMA_CC_NAND_DEVID:
+ case BCMA_CC_NAND_DEVID_X:
+ case BCMA_CC_NAND_SPARE_RD16:
+ case BCMA_CC_NAND_SPARE_RD20:
+ case BCMA_CC_NAND_SPARE_RD24:
+ case BCMA_CC_NAND_SPARE_RD28:
+ return true;
+ }
+
+ return false;
+}
+
+static inline struct brcmnand_bcma_soc *to_bcma_soc(struct brcmnand_soc *soc)
+{
+ return container_of(soc, struct brcmnand_bcma_soc, soc);
+}
+
+static u32 brcmnand_bcma_read_reg(struct brcmnand_soc *soc, u32 offset)
+{
+ struct brcmnand_bcma_soc *sc = to_bcma_soc(soc);
+ u32 val;
+
+ /* Offset into the NAND block and deal with the flash cache separately */
+ if (offset == BRCMNAND_NON_MMIO_FC_ADDR)
+ offset = BCMA_CC_NAND_CACHE_DATA;
+ else
+ offset += BCMA_CC_NAND_REVISION;
+
+ val = bcma_cc_read32(sc->cc, offset);
+
+ /* Swap if necessary */
+ if (brcmnand_bcma_needs_swapping(offset))
+ val = be32_to_cpu((__force __be32)val);
+ return val;
+}
+
+static void brcmnand_bcma_write_reg(struct brcmnand_soc *soc, u32 val,
+ u32 offset)
+{
+ struct brcmnand_bcma_soc *sc = to_bcma_soc(soc);
+
+ /* Offset into the NAND block */
+ if (offset == BRCMNAND_NON_MMIO_FC_ADDR)
+ offset = BCMA_CC_NAND_CACHE_DATA;
+ else
+ offset += BCMA_CC_NAND_REVISION;
+
+ /* Swap if necessary */
+ if (brcmnand_bcma_needs_swapping(offset))
+ val = (__force u32)cpu_to_be32(val);
+
+ bcma_cc_write32(sc->cc, offset, val);
+}
+
+static struct brcmnand_io_ops brcmnand_bcma_io_ops = {
+ .read_reg = brcmnand_bcma_read_reg,
+ .write_reg = brcmnand_bcma_write_reg,
+};
+
+static void brcmnand_bcma_prepare_data_bus(struct brcmnand_soc *soc, bool prepare,
+ bool is_param)
+{
+ struct brcmnand_bcma_soc *sc = to_bcma_soc(soc);
+
+ /* Reset the cache address to ensure we are already accessing the
+ * beginning of a sub-page.
+ */
+ bcma_cc_write32(sc->cc, BCMA_CC_NAND_CACHE_ADDR, 0);
+}
+
+static int brcmnand_bcma_nand_probe(struct platform_device *pdev)
+{
+ struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev);
+ struct brcmnand_bcma_soc *soc;
+
+ soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
+ if (!soc)
+ return -ENOMEM;
+
+ soc->cc = container_of(nflash, struct bcma_drv_cc, nflash);
+ soc->soc.prepare_data_bus = brcmnand_bcma_prepare_data_bus;
+ soc->soc.ops = &brcmnand_bcma_io_ops;
+
+ if (soc->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) {
+ dev_err(&pdev->dev, "Use bcm47xxnflash for 4706!\n");
+ return -ENODEV;
+ }
+
+ return brcmnand_probe(pdev, &soc->soc);
+}
+
+static struct platform_driver brcmnand_bcma_nand_driver = {
+ .probe = brcmnand_bcma_nand_probe,
+ .remove = brcmnand_remove,
+ .driver = {
+ .name = "bcma_brcmnand",
+ .pm = &brcmnand_pm_ops,
+ }
+};
+module_platform_driver(brcmnand_bcma_nand_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("NAND controller driver glue for BCMA chips");
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index aee78f5f4f15..2e9c2e2d9c9f 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -9,6 +9,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/platform_device.h>
+#include <linux/platform_data/brcmnand.h>
#include <linux/err.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
@@ -25,6 +26,7 @@
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
+#include <linux/static_key.h>
#include <linux/list.h>
#include <linux/log2.h>
@@ -207,13 +209,15 @@ enum {
struct brcmnand_host;
+static DEFINE_STATIC_KEY_FALSE(brcmnand_soc_has_ops_key);
+
struct brcmnand_controller {
struct device *dev;
struct nand_controller controller;
void __iomem *nand_base;
void __iomem *nand_fc; /* flash cache */
void __iomem *flash_dma_base;
- unsigned int irq;
+ int irq;
unsigned int dma_irq;
int nand_version;
@@ -592,15 +596,29 @@ enum {
INTFC_CTLR_READY = BIT(31),
};
+static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl)
+{
+#if IS_ENABLED(CONFIG_MTD_NAND_BRCMNAND_BCMA)
+ return static_branch_unlikely(&brcmnand_soc_has_ops_key);
+#else
+ return false;
+#endif
+}
+
static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs)
{
+ if (brcmnand_non_mmio_ops(ctrl))
+ return brcmnand_soc_read(ctrl->soc, offs);
return brcmnand_readl(ctrl->nand_base + offs);
}
static inline void nand_writereg(struct brcmnand_controller *ctrl, u32 offs,
u32 val)
{
- brcmnand_writel(val, ctrl->nand_base + offs);
+ if (brcmnand_non_mmio_ops(ctrl))
+ brcmnand_soc_write(ctrl->soc, val, offs);
+ else
+ brcmnand_writel(val, ctrl->nand_base + offs);
}
static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
@@ -766,13 +784,18 @@ static inline void brcmnand_rmw_reg(struct brcmnand_controller *ctrl,
static inline u32 brcmnand_read_fc(struct brcmnand_controller *ctrl, int word)
{
+ if (brcmnand_non_mmio_ops(ctrl))
+ return brcmnand_soc_read(ctrl->soc, BRCMNAND_NON_MMIO_FC_ADDR);
return __raw_readl(ctrl->nand_fc + word * 4);
}
static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl,
int word, u32 val)
{
- __raw_writel(val, ctrl->nand_fc + word * 4);
+ if (brcmnand_non_mmio_ops(ctrl))
+ brcmnand_soc_write(ctrl->soc, val, BRCMNAND_NON_MMIO_FC_ADDR);
+ else
+ __raw_writel(val, ctrl->nand_fc + word * 4);
}
static inline void edu_writel(struct brcmnand_controller *ctrl,
@@ -897,6 +920,12 @@ static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val)
static inline int brcmnand_cmd_shift(struct brcmnand_controller *ctrl)
{
+ /* Kludge for the BCMA-based NAND controller which does not actually
+ * shift the command
+ */
+ if (ctrl->nand_version == 0x0304 && brcmnand_non_mmio_ops(ctrl))
+ return 0;
+
if (ctrl->nand_version < 0x0602)
return 24;
return 0;
@@ -1592,7 +1621,7 @@ static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip)
bool err = false;
int sts;
- if (mtd->oops_panic_write) {
+ if (mtd->oops_panic_write || ctrl->irq < 0) {
/* switch to interrupt polling and PIO mode */
disable_ctrl_irqs(ctrl);
sts = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY,
@@ -2750,33 +2779,27 @@ static const struct nand_controller_ops brcmnand_controller_ops = {
.attach_chip = brcmnand_attach_chip,
};
-static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn)
+static int brcmnand_init_cs(struct brcmnand_host *host,
+ const char * const *part_probe_types)
{
struct brcmnand_controller *ctrl = host->ctrl;
- struct platform_device *pdev = host->pdev;
+ struct device *dev = ctrl->dev;
struct mtd_info *mtd;
struct nand_chip *chip;
int ret;
u16 cfg_offs;
- ret = of_property_read_u32(dn, "reg", &host->cs);
- if (ret) {
- dev_err(&pdev->dev, "can't get chip-select\n");
- return -ENXIO;
- }
-
mtd = nand_to_mtd(&host->chip);
chip = &host->chip;
- nand_set_flash_node(chip, dn);
nand_set_controller_data(chip, host);
- mtd->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "brcmnand.%d",
+ mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d",
host->cs);
if (!mtd->name)
return -ENOMEM;
mtd->owner = THIS_MODULE;
- mtd->dev.parent = &pdev->dev;
+ mtd->dev.parent = dev;
chip->legacy.cmd_ctrl = brcmnand_cmd_ctrl;
chip->legacy.cmdfunc = brcmnand_cmdfunc;
@@ -2810,7 +2833,7 @@ static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn)
if (ret)
return ret;
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
if (ret)
nand_cleanup(chip);
@@ -2914,7 +2937,7 @@ const struct dev_pm_ops brcmnand_pm_ops = {
};
EXPORT_SYMBOL_GPL(brcmnand_pm_ops);
-static const struct of_device_id brcmnand_of_match[] = {
+static const struct of_device_id __maybe_unused brcmnand_of_match[] = {
{ .compatible = "brcm,brcmnand-v2.1" },
{ .compatible = "brcm,brcmnand-v2.2" },
{ .compatible = "brcm,brcmnand-v4.0" },
@@ -2979,17 +3002,15 @@ static int brcmnand_edu_setup(struct platform_device *pdev)
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
{
+ struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
struct device_node *dn = dev->of_node, *child;
struct brcmnand_controller *ctrl;
+ struct brcmnand_host *host;
struct resource *res;
int ret;
- /* We only support device-tree instantiation */
- if (!dn)
- return -ENODEV;
-
- if (!of_match_node(brcmnand_of_match, dn))
+ if (dn && !of_match_node(brcmnand_of_match, dn))
return -ENODEV;
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
@@ -2998,6 +3019,13 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
dev_set_drvdata(dev, ctrl);
ctrl->dev = dev;
+ ctrl->soc = soc;
+
+ /* Enable the static key if the soc provides I/O operations indicating
+ * that a non-memory mapped IO access path must be used
+ */
+ if (brcmnand_soc_has_ops(ctrl->soc))
+ static_branch_enable(&brcmnand_soc_has_ops_key);
init_completion(&ctrl->done);
init_completion(&ctrl->dma_done);
@@ -3009,7 +3037,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
/* NAND register range */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ctrl->nand_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(ctrl->nand_base))
+ if (IS_ERR(ctrl->nand_base) && !brcmnand_soc_has_ops(soc))
return PTR_ERR(ctrl->nand_base);
/* Enable clock before using NAND registers */
@@ -3126,40 +3154,33 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
}
/* IRQ */
- ctrl->irq = platform_get_irq(pdev, 0);
- if ((int)ctrl->irq < 0) {
- dev_err(dev, "no IRQ defined\n");
- ret = -ENODEV;
- goto err;
- }
-
- /*
- * Some SoCs integrate this controller (e.g., its interrupt bits) in
- * interesting ways
- */
- if (soc) {
- ctrl->soc = soc;
-
- ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0,
- DRV_NAME, ctrl);
+ ctrl->irq = platform_get_irq_optional(pdev, 0);
+ if (ctrl->irq > 0) {
+ /*
+ * Some SoCs integrate this controller (e.g., its interrupt bits) in
+ * interesting ways
+ */
+ if (soc) {
+ ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0,
+ DRV_NAME, ctrl);
- /* Enable interrupt */
- ctrl->soc->ctlrdy_ack(ctrl->soc);
- ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
- } else {
- /* Use standard interrupt infrastructure */
- ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0,
- DRV_NAME, ctrl);
- }
- if (ret < 0) {
- dev_err(dev, "can't allocate IRQ %d: error %d\n",
- ctrl->irq, ret);
- goto err;
+ /* Enable interrupt */
+ ctrl->soc->ctlrdy_ack(ctrl->soc);
+ ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
+ } else {
+ /* Use standard interrupt infrastructure */
+ ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0,
+ DRV_NAME, ctrl);
+ }
+ if (ret < 0) {
+ dev_err(dev, "can't allocate IRQ %d: error %d\n",
+ ctrl->irq, ret);
+ goto err;
+ }
}
for_each_available_child_of_node(dn, child) {
if (of_device_is_compatible(child, "brcm,nandcs")) {
- struct brcmnand_host *host;
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host) {
@@ -3170,7 +3191,16 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
host->pdev = pdev;
host->ctrl = ctrl;
- ret = brcmnand_init_cs(host, child);
+ ret = of_property_read_u32(child, "reg", &host->cs);
+ if (ret) {
+ dev_err(dev, "can't get chip-select\n");
+ devm_kfree(dev, host);
+ continue;
+ }
+
+ nand_set_flash_node(&host->chip, child);
+
+ ret = brcmnand_init_cs(host, NULL);
if (ret) {
devm_kfree(dev, host);
continue; /* Try all chip-selects */
@@ -3180,6 +3210,32 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
}
}
+ if (!list_empty(&ctrl->host_list))
+ return 0;
+
+ if (!pd) {
+ ret = -ENODEV;
+ goto err;
+ }
+
+ /* If we got there we must have been probing via platform data */
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ host->pdev = pdev;
+ host->ctrl = ctrl;
+ host->cs = pd->chip_select;
+ host->chip.ecc.size = pd->ecc_stepsize;
+ host->chip.ecc.strength = pd->ecc_strength;
+
+ ret = brcmnand_init_cs(host, pd->part_probe_types);
+ if (ret)
+ goto err;
+
+ list_add_tail(&host->node, &ctrl->host_list);
+
/* No chip-selects could initialize properly */
if (list_empty(&ctrl->host_list)) {
ret = -ENODEV;
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.h b/drivers/mtd/nand/raw/brcmnand/brcmnand.h
index eb498fbe505e..f1f93d85f50d 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.h
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.h
@@ -11,12 +11,25 @@
struct platform_device;
struct dev_pm_ops;
+struct brcmnand_io_ops;
+
+/* Special register offset constant to intercept a non-MMIO access
+ * to the flash cache register space. This is intentionally large
+ * not to overlap with an existing offset.
+ */
+#define BRCMNAND_NON_MMIO_FC_ADDR 0xffffffff
struct brcmnand_soc {
bool (*ctlrdy_ack)(struct brcmnand_soc *soc);
void (*ctlrdy_set_enabled)(struct brcmnand_soc *soc, bool en);
void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare,
bool is_param);
+ const struct brcmnand_io_ops *ops;
+};
+
+struct brcmnand_io_ops {
+ u32 (*read_reg)(struct brcmnand_soc *soc, u32 offset);
+ void (*write_reg)(struct brcmnand_soc *soc, u32 val, u32 offset);
};
static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc,
@@ -58,6 +71,22 @@ static inline void brcmnand_writel(u32 val, void __iomem *addr)
writel_relaxed(val, addr);
}
+static inline bool brcmnand_soc_has_ops(struct brcmnand_soc *soc)
+{
+ return soc && soc->ops && soc->ops->read_reg && soc->ops->write_reg;
+}
+
+static inline u32 brcmnand_soc_read(struct brcmnand_soc *soc, u32 offset)
+{
+ return soc->ops->read_reg(soc, offset);
+}
+
+static inline void brcmnand_soc_write(struct brcmnand_soc *soc, u32 val,
+ u32 offset)
+{
+ soc->ops->write_reg(soc, val, offset);
+}
+
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc);
int brcmnand_remove(struct platform_device *pdev);
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index ded4df473928..44b14c9dc9a7 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -644,10 +644,11 @@ err_out:
* RDN_DELAY = ----------------------- {3}
* RP
*/
-static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
- const struct nand_sdr_timings *sdr)
+static int gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
+ const struct nand_sdr_timings *sdr)
{
struct gpmi_nfc_hardware_timing *hw = &this->hw;
+ struct resources *r = &this->resources;
unsigned int dll_threshold_ps = this->devdata->max_chain_delay;
unsigned int period_ps, reference_period_ps;
unsigned int data_setup_cycles, data_hold_cycles, addr_setup_cycles;
@@ -656,21 +657,33 @@ static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
int sample_delay_ps, sample_delay_factor;
u16 busy_timeout_cycles;
u8 wrn_dly_sel;
+ unsigned long clk_rate, min_rate;
if (sdr->tRC_min >= 30000) {
/* ONFI non-EDO modes [0-3] */
hw->clk_rate = 22000000;
+ min_rate = 0;
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS;
} else if (sdr->tRC_min >= 25000) {
/* ONFI EDO mode 4 */
hw->clk_rate = 80000000;
+ min_rate = 22000000;
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
} else {
/* ONFI EDO mode 5 */
hw->clk_rate = 100000000;
+ min_rate = 80000000;
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
}
+ clk_rate = clk_round_rate(r->clock[0], hw->clk_rate);
+ if (clk_rate <= min_rate) {
+ dev_err(this->dev, "clock setting: expected %ld, got %ld\n",
+ hw->clk_rate, clk_rate);
+ return -ENOTSUPP;
+ }
+
+ hw->clk_rate = clk_rate;
/* SDR core timings are given in picoseconds */
period_ps = div_u64((u64)NSEC_PER_SEC * 1000, hw->clk_rate);
@@ -711,6 +724,7 @@ static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
hw->ctrl1n |= BF_GPMI_CTRL1_RDN_DELAY(sample_delay_factor) |
BM_GPMI_CTRL1_DLL_ENABLE |
(use_half_period ? BM_GPMI_CTRL1_HALF_PERIOD : 0);
+ return 0;
}
static int gpmi_nfc_apply_timings(struct gpmi_nand_data *this)
@@ -766,14 +780,15 @@ static int gpmi_setup_interface(struct nand_chip *chip, int chipnr,
{
struct gpmi_nand_data *this = nand_get_controller_data(chip);
const struct nand_sdr_timings *sdr;
+ int ret;
/* Retrieve required NAND timings */
sdr = nand_get_sdr_timings(conf);
if (IS_ERR(sdr))
return PTR_ERR(sdr);
- /* Only MX6 GPMI controller can reach EDO timings */
- if (sdr->tRC_min <= 25000 && !GPMI_IS_MX6(this))
+ /* Only MX28/MX6 GPMI controller can reach EDO timings */
+ if (sdr->tRC_min <= 25000 && !GPMI_IS_MX28(this) && !GPMI_IS_MX6(this))
return -ENOTSUPP;
/* Stop here if this call was just a check */
@@ -781,7 +796,9 @@ static int gpmi_setup_interface(struct nand_chip *chip, int chipnr,
return 0;
/* Do the actual derivation of the controller timings */
- gpmi_nfc_compute_timings(this, sdr);
+ ret = gpmi_nfc_compute_timings(this, sdr);
+ if (ret)
+ return ret;
this->hw.must_apply_timings = true;
diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
index b18861bdcdc8..ff26c10f295d 100644
--- a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
+++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
@@ -567,7 +567,7 @@ static struct platform_driver ingenic_nand_driver = {
.remove = ingenic_nand_remove,
.driver = {
.name = DRV_NAME,
- .of_match_table = of_match_ptr(ingenic_nand_dt_match),
+ .of_match_table = ingenic_nand_dt_match,
},
};
module_platform_driver(ingenic_nand_driver);
diff --git a/drivers/mtd/nand/raw/ingenic/jz4780_bch.c b/drivers/mtd/nand/raw/ingenic/jz4780_bch.c
index d67dbfff76cc..12b5b0484fe9 100644
--- a/drivers/mtd/nand/raw/ingenic/jz4780_bch.c
+++ b/drivers/mtd/nand/raw/ingenic/jz4780_bch.c
@@ -260,7 +260,7 @@ static struct platform_driver jz4780_bch_driver = {
.probe = jz4780_bch_probe,
.driver = {
.name = "jz4780-bch",
- .of_match_table = of_match_ptr(jz4780_bch_dt_match),
+ .of_match_table = jz4780_bch_dt_match,
},
};
module_platform_driver(jz4780_bch_driver);
diff --git a/drivers/mtd/nand/raw/mtk_ecc.c b/drivers/mtd/nand/raw/mtk_ecc.c
index 1b47964cb6da..e7df3dac705e 100644
--- a/drivers/mtd/nand/raw/mtk_ecc.c
+++ b/drivers/mtd/nand/raw/mtk_ecc.c
@@ -579,7 +579,7 @@ static struct platform_driver mtk_ecc_driver = {
.probe = mtk_ecc_probe,
.driver = {
.name = "mtk-ecc",
- .of_match_table = of_match_ptr(mtk_ecc_dt_match),
+ .of_match_table = mtk_ecc_dt_match,
#ifdef CONFIG_PM_SLEEP
.pm = &mtk_ecc_pm_ops,
#endif
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index e7b2ba016d8c..284fff62ac49 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -321,7 +321,7 @@ static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
if (nand_region_is_secured(chip, ofs, mtd->erasesize))
return -EIO;
- if (WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning))
+ if (mtd_check_expert_analysis_mode())
return 0;
if (chip->legacy.block_bad)
@@ -338,16 +338,19 @@ static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
*
* Return: -EBUSY if the chip has been suspended, 0 otherwise
*/
-static int nand_get_device(struct nand_chip *chip)
+static void nand_get_device(struct nand_chip *chip)
{
- mutex_lock(&chip->lock);
- if (chip->suspended) {
+ /* Wait until the device is resumed. */
+ while (1) {
+ mutex_lock(&chip->lock);
+ if (!chip->suspended) {
+ mutex_lock(&chip->controller->lock);
+ return;
+ }
mutex_unlock(&chip->lock);
- return -EBUSY;
- }
- mutex_lock(&chip->controller->lock);
- return 0;
+ wait_event(chip->resume_wq, !chip->suspended);
+ }
}
/**
@@ -576,9 +579,7 @@ static int nand_block_markbad_lowlevel(struct nand_chip *chip, loff_t ofs)
nand_erase_nand(chip, &einfo, 0);
/* Write bad block marker to OOB */
- ret = nand_get_device(chip);
- if (ret)
- return ret;
+ nand_get_device(chip);
ret = nand_markbad_bbm(chip, ofs);
nand_release_device(chip);
@@ -3826,9 +3827,7 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
ops->mode != MTD_OPS_RAW)
return -ENOTSUPP;
- ret = nand_get_device(chip);
- if (ret)
- return ret;
+ nand_get_device(chip);
if (!ops->datbuf)
ret = nand_do_read_oob(chip, from, ops);
@@ -4415,13 +4414,11 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
struct nand_chip *chip = mtd_to_nand(mtd);
- int ret;
+ int ret = 0;
ops->retlen = 0;
- ret = nand_get_device(chip);
- if (ret)
- return ret;
+ nand_get_device(chip);
switch (ops->mode) {
case MTD_OPS_PLACE_OOB:
@@ -4481,9 +4478,7 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr,
return -EIO;
/* Grab the lock and see if the device is available */
- ret = nand_get_device(chip);
- if (ret)
- return ret;
+ nand_get_device(chip);
/* Shift to get first page */
page = (int)(instr->addr >> chip->page_shift);
@@ -4570,7 +4565,7 @@ static void nand_sync(struct mtd_info *mtd)
pr_debug("%s: called\n", __func__);
/* Grab the lock and see if the device is available */
- WARN_ON(nand_get_device(chip));
+ nand_get_device(chip);
/* Release it and go back */
nand_release_device(chip);
}
@@ -4587,9 +4582,7 @@ static int nand_block_isbad(struct mtd_info *mtd, loff_t offs)
int ret;
/* Select the NAND device */
- ret = nand_get_device(chip);
- if (ret)
- return ret;
+ nand_get_device(chip);
nand_select_target(chip, chipnr);
@@ -4660,6 +4653,8 @@ static void nand_resume(struct mtd_info *mtd)
__func__);
}
mutex_unlock(&chip->lock);
+
+ wake_up_all(&chip->resume_wq);
}
/**
@@ -5274,25 +5269,24 @@ static void of_get_nand_ecc_legacy_user_config(struct nand_chip *chip)
user_conf->placement = of_get_rawnand_ecc_placement_legacy(dn);
}
-static int of_get_nand_bus_width(struct device_node *np)
+static int of_get_nand_bus_width(struct nand_chip *chip)
{
+ struct device_node *dn = nand_get_flash_node(chip);
u32 val;
+ int ret;
- if (of_property_read_u32(np, "nand-bus-width", &val))
- return 8;
-
- switch (val) {
- case 8:
- case 16:
- return val;
- default:
- return -EIO;
- }
-}
+ ret = of_property_read_u32(dn, "nand-bus-width", &val);
+ if (ret == -EINVAL)
+ /* Buswidth defaults to 8 if the property does not exist .*/
+ return 0;
+ else if (ret)
+ return ret;
-static bool of_get_nand_on_flash_bbt(struct device_node *np)
-{
- return of_property_read_bool(np, "nand-on-flash-bbt");
+ if (val == 16)
+ chip->options |= NAND_BUSWIDTH_16;
+ else if (val != 8)
+ return -EINVAL;
+ return 0;
}
static int of_get_nand_secure_regions(struct nand_chip *chip)
@@ -5368,17 +5362,19 @@ static int rawnand_dt_init(struct nand_chip *chip)
{
struct nand_device *nand = mtd_to_nanddev(nand_to_mtd(chip));
struct device_node *dn = nand_get_flash_node(chip);
+ int ret;
if (!dn)
return 0;
- if (of_get_nand_bus_width(dn) == 16)
- chip->options |= NAND_BUSWIDTH_16;
+ ret = of_get_nand_bus_width(chip);
+ if (ret)
+ return ret;
if (of_property_read_bool(dn, "nand-is-boot-medium"))
chip->options |= NAND_IS_BOOT_MEDIUM;
- if (of_get_nand_on_flash_bbt(dn))
+ if (of_property_read_bool(dn, "nand-on-flash-bbt"))
chip->bbt_options |= NAND_BBT_USE_FLASH;
of_get_nand_ecc_user_config(nand);
@@ -5437,6 +5433,7 @@ static int nand_scan_ident(struct nand_chip *chip, unsigned int maxchips,
chip->cur_cs = -1;
mutex_init(&chip->lock);
+ init_waitqueue_head(&chip->resume_wq);
/* Enforce the right timings for reset/detection */
chip->current_interface_config = nand_get_reset_interface_config();
diff --git a/drivers/mtd/nand/raw/nand_bbt.c b/drivers/mtd/nand/raw/nand_bbt.c
index ab630af3a309..a3723da2e0a0 100644
--- a/drivers/mtd/nand/raw/nand_bbt.c
+++ b/drivers/mtd/nand/raw/nand_bbt.c
@@ -1455,7 +1455,7 @@ int nand_isbad_bbt(struct nand_chip *this, loff_t offs, int allowbbt)
pr_debug("nand_isbad_bbt(): bbt info for offs 0x%08x: (block %d) 0x%02x\n",
(unsigned int)offs, block, res);
- if (WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning))
+ if (mtd_check_expert_analysis_mode())
return 0;
switch (res) {
diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c
index 0750121ac371..24beade95c7f 100644
--- a/drivers/mtd/nand/raw/nandsim.c
+++ b/drivers/mtd/nand/raw/nandsim.c
@@ -201,6 +201,9 @@ MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should "
/* Calculate the OOB offset in flash RAM image by (row, column) address */
#define NS_RAW_OFFSET_OOB(ns) (NS_RAW_OFFSET(ns) + ns->geom.pgsz)
+/* Calculate the byte shift in the next page to access */
+#define NS_PAGE_BYTE_SHIFT(ns) ((ns)->regs.column + (ns)->regs.off)
+
/* After a command is input, the simulator goes to one of the following states */
#define STATE_CMD_READ0 0x00000001 /* read data from the beginning of page */
#define STATE_CMD_READ1 0x00000002 /* read data from the second half of page */
@@ -979,15 +982,8 @@ static int ns_read_error(unsigned int page_no)
static int ns_setup_wear_reporting(struct mtd_info *mtd)
{
- size_t mem;
-
wear_eb_count = div_u64(mtd->size, mtd->erasesize);
- mem = wear_eb_count * sizeof(unsigned long);
- if (mem / sizeof(unsigned long) != wear_eb_count) {
- NS_ERR("Too many erase blocks for wear reporting\n");
- return -ENOMEM;
- }
- erase_block_wear = kzalloc(mem, GFP_KERNEL);
+ erase_block_wear = kcalloc(wear_eb_count, sizeof(unsigned long), GFP_KERNEL);
if (!erase_block_wear) {
NS_ERR("Too many erase blocks for wear reporting\n");
return -ENOMEM;
@@ -1389,7 +1385,7 @@ static inline union ns_mem *NS_GET_PAGE(struct nandsim *ns)
*/
static inline u_char *NS_PAGE_BYTE_OFF(struct nandsim *ns)
{
- return NS_GET_PAGE(ns)->byte + ns->regs.column + ns->regs.off;
+ return NS_GET_PAGE(ns)->byte + NS_PAGE_BYTE_SHIFT(ns);
}
static int ns_do_read_error(struct nandsim *ns, int num)
@@ -1415,7 +1411,7 @@ static void ns_do_bit_flips(struct nandsim *ns, int num)
ns->buf.byte[pos / 8] ^= (1 << (pos % 8));
NS_WARN("read_page: flipping bit %d in page %d "
"reading from %d ecc: corrected=%u failed=%u\n",
- pos, ns->regs.row, ns->regs.column + ns->regs.off,
+ pos, ns->regs.row, NS_PAGE_BYTE_SHIFT(ns),
nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed);
}
}
@@ -1437,7 +1433,7 @@ static void ns_read_page(struct nandsim *ns, int num)
ssize_t tx;
NS_DBG("read_page: page %d written, reading from %d\n",
- ns->regs.row, ns->regs.column + ns->regs.off);
+ ns->regs.row, NS_PAGE_BYTE_SHIFT(ns));
if (ns_do_read_error(ns, num))
return;
pos = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off;
@@ -1458,7 +1454,7 @@ static void ns_read_page(struct nandsim *ns, int num)
memset(ns->buf.byte, 0xFF, num);
} else {
NS_DBG("read_page: page %d allocated, reading from %d\n",
- ns->regs.row, ns->regs.column + ns->regs.off);
+ ns->regs.row, NS_PAGE_BYTE_SHIFT(ns));
if (ns_do_read_error(ns, num))
return;
memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num);
@@ -1509,7 +1505,7 @@ static int ns_prog_page(struct nandsim *ns, int num)
int all;
NS_DBG("prog_page: writing page %d\n", ns->regs.row);
- pg_off = ns->file_buf + ns->regs.column + ns->regs.off;
+ pg_off = ns->file_buf + NS_PAGE_BYTE_SHIFT(ns);
off = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off;
if (!test_bit(ns->regs.row, ns->pages_written)) {
all = 1;
@@ -1598,7 +1594,7 @@ static int ns_do_state_action(struct nandsim *ns, uint32_t action)
NS_ERR("do_state_action: column number is too large\n");
break;
}
- num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
+ num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns);
ns_read_page(ns, num);
NS_DBG("do_state_action: (ACTION_CPY:) copy %d bytes to int buf, raw offset %d\n",
@@ -1666,7 +1662,7 @@ static int ns_do_state_action(struct nandsim *ns, uint32_t action)
return -1;
}
- num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
+ num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns);
if (num != ns->regs.count) {
NS_ERR("do_state_action: too few bytes were input (%d instead of %d)\n",
ns->regs.count, num);
@@ -1738,14 +1734,6 @@ static void ns_switch_state(struct nandsim *ns)
"state: %s, nxstate: %s\n",
ns_get_state_name(ns->state),
ns_get_state_name(ns->nxstate));
-
- /* See, whether we need to do some action */
- if ((ns->state & ACTION_MASK) &&
- ns_do_state_action(ns, ns->state) < 0) {
- ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
- return;
- }
-
} else {
/*
* We don't yet know which operation we perform.
@@ -1762,12 +1750,13 @@ static void ns_switch_state(struct nandsim *ns)
if (ns_find_operation(ns, 0))
return;
+ }
- if ((ns->state & ACTION_MASK) &&
- ns_do_state_action(ns, ns->state) < 0) {
- ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
- return;
- }
+ /* See, whether we need to do some action */
+ if ((ns->state & ACTION_MASK) &&
+ ns_do_state_action(ns, ns->state) < 0) {
+ ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
}
/* For 16x devices column means the page offset in words */
@@ -1817,7 +1806,7 @@ static void ns_switch_state(struct nandsim *ns)
switch (NS_STATE(ns->state)) {
case STATE_DATAIN:
case STATE_DATAOUT:
- ns->regs.num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
+ ns->regs.num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns);
break;
case STATE_DATAOUT_ID:
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index f0bbbe401e76..58c32a11792e 100644
--- a/drivers/mtd/nand/raw/omap2.c
+++ b/drivers/mtd/nand/raw/omap2.c
@@ -2298,7 +2298,7 @@ static struct platform_driver omap_nand_driver = {
.remove = omap_nand_remove,
.driver = {
.name = DRIVER_NAME,
- .of_match_table = of_match_ptr(omap_nand_ids),
+ .of_match_table = omap_nand_ids,
},
};
diff --git a/drivers/mtd/nand/raw/omap_elm.c b/drivers/mtd/nand/raw/omap_elm.c
index db105d9b560c..893e9979c4a2 100644
--- a/drivers/mtd/nand/raw/omap_elm.c
+++ b/drivers/mtd/nand/raw/omap_elm.c
@@ -282,7 +282,7 @@ static void elm_start_processing(struct elm_info *info,
static void elm_error_correction(struct elm_info *info,
struct elm_errorvec *err_vec)
{
- int i, j, errors = 0;
+ int i, j;
int offset;
u32 reg_val;
@@ -312,8 +312,6 @@ static void elm_error_correction(struct elm_info *info,
/* Update error location register */
offset += 4;
}
-
- errors += err_vec[i].error_count;
} else {
err_vec[i].error_uncorrectable = true;
}
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 8a91e069ee2e..3c6f6aff649f 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -1062,7 +1062,7 @@ static int pl35x_nand_chip_init(struct pl35x_nandc *nfc,
chip->controller = &nfc->controller;
mtd = nand_to_mtd(chip);
mtd->dev.parent = nfc->dev;
- nand_set_flash_node(chip, nfc->dev->of_node);
+ nand_set_flash_node(chip, np);
if (!mtd->name) {
mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL,
"%s", PL35X_NANDC_DRIVER_NAME);
diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c
index 428e08362956..6db063b230a9 100644
--- a/drivers/mtd/nand/raw/renesas-nand-controller.c
+++ b/drivers/mtd/nand/raw/renesas-nand-controller.c
@@ -1412,7 +1412,7 @@ MODULE_DEVICE_TABLE(of, rnandc_id_table);
static struct platform_driver rnandc_driver = {
.driver = {
.name = "renesas-nandc",
- .of_match_table = of_match_ptr(rnandc_id_table),
+ .of_match_table = rnandc_id_table,
},
.probe = rnandc_probe,
.remove = rnandc_remove,
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
index b5405bc7ca3a..cbaa4f1c83da 100644
--- a/drivers/mtd/nand/raw/rockchip-nand-controller.c
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -1403,7 +1403,6 @@ static int rk_nfc_probe(struct platform_device *pdev)
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
- dev_err(dev, "no NFC irq resource\n");
ret = -EINVAL;
goto clk_disable;
}
diff --git a/drivers/mtd/nand/raw/sh_flctl.c b/drivers/mtd/nand/raw/sh_flctl.c
index 13df4bdf792a..b85b9c6fcc42 100644
--- a/drivers/mtd/nand/raw/sh_flctl.c
+++ b/drivers/mtd/nand/raw/sh_flctl.c
@@ -1220,7 +1220,7 @@ static struct platform_driver flctl_driver = {
.remove = flctl_remove,
.driver = {
.name = "sh_flctl",
- .of_match_table = of_match_ptr(of_flctl_match),
+ .of_match_table = of_flctl_match,
},
};
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index 97b4e02e43e4..87c1c7dd97eb 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -9,6 +9,7 @@
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/mfd/syscon.h>
@@ -231,6 +232,7 @@ struct stm32_fmc2_timings {
struct stm32_fmc2_nand {
struct nand_chip chip;
+ struct gpio_desc *wp_gpio;
struct stm32_fmc2_timings timings;
int ncs;
int cs_used[FMC2_MAX_CE];
@@ -1747,6 +1749,18 @@ static const struct nand_controller_ops stm32_fmc2_nfc_controller_ops = {
.setup_interface = stm32_fmc2_nfc_setup_interface,
};
+static void stm32_fmc2_nfc_wp_enable(struct stm32_fmc2_nand *nand)
+{
+ if (nand->wp_gpio)
+ gpiod_set_value(nand->wp_gpio, 1);
+}
+
+static void stm32_fmc2_nfc_wp_disable(struct stm32_fmc2_nand *nand)
+{
+ if (nand->wp_gpio)
+ gpiod_set_value(nand->wp_gpio, 0);
+}
+
static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc,
struct device_node *dn)
{
@@ -1785,6 +1799,18 @@ static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc,
nand->cs_used[i] = cs;
}
+ nand->wp_gpio = devm_gpiod_get_from_of_node(nfc->dev, dn,
+ "wp-gpios", 0,
+ GPIOD_OUT_HIGH, "wp");
+ if (IS_ERR(nand->wp_gpio)) {
+ ret = PTR_ERR(nand->wp_gpio);
+ if (ret != -ENOENT)
+ return dev_err_probe(nfc->dev, ret,
+ "failed to request WP GPIO\n");
+
+ nand->wp_gpio = NULL;
+ }
+
nand_set_flash_node(&nand->chip, dn);
return 0;
@@ -1956,10 +1982,12 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
chip->options |= NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE |
NAND_USES_DMA;
+ stm32_fmc2_nfc_wp_disable(nand);
+
/* Scan to find existence of the device */
ret = nand_scan(chip, nand->ncs);
if (ret)
- goto err_release_dma;
+ goto err_wp_enable;
ret = mtd_device_register(mtd, NULL, 0);
if (ret)
@@ -1972,6 +2000,9 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
err_nand_cleanup:
nand_cleanup(chip);
+err_wp_enable:
+ stm32_fmc2_nfc_wp_enable(nand);
+
err_release_dma:
if (nfc->dma_ecc_ch)
dma_release_channel(nfc->dma_ecc_ch);
@@ -2012,15 +2043,20 @@ static int stm32_fmc2_nfc_remove(struct platform_device *pdev)
clk_disable_unprepare(nfc->clk);
+ stm32_fmc2_nfc_wp_enable(nand);
+
return 0;
}
static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev)
{
struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev);
+ struct stm32_fmc2_nand *nand = &nfc->nand;
clk_disable_unprepare(nfc->clk);
+ stm32_fmc2_nfc_wp_enable(nand);
+
pinctrl_pm_select_sleep_state(dev);
return 0;
@@ -2042,6 +2078,8 @@ static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev)
stm32_fmc2_nfc_init(nfc);
+ stm32_fmc2_nfc_wp_disable(nand);
+
for (chip_cs = 0; chip_cs < FMC2_MAX_CE; chip_cs++) {
if (!(nfc->cs_assigned & BIT(chip_cs)))
continue;
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 2c8685f1f2fa..ff8336870bc0 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -381,7 +381,10 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
}
}
- rdesc = spinand->dirmaps[req->pos.plane].rdesc;
+ if (req->mode == MTD_OPS_RAW)
+ rdesc = spinand->dirmaps[req->pos.plane].rdesc;
+ else
+ rdesc = spinand->dirmaps[req->pos.plane].rdesc_ecc;
while (nbytes) {
ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf);
@@ -452,7 +455,10 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand,
req->ooblen);
}
- wdesc = spinand->dirmaps[req->pos.plane].wdesc;
+ if (req->mode == MTD_OPS_RAW)
+ wdesc = spinand->dirmaps[req->pos.plane].wdesc;
+ else
+ wdesc = spinand->dirmaps[req->pos.plane].wdesc_ecc;
while (nbytes) {
ret = spi_mem_dirmap_write(wdesc, column, nbytes, buf);
@@ -865,6 +871,31 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
spinand->dirmaps[plane].rdesc = desc;
+ if (nand->ecc.engine->integration != NAND_ECC_ENGINE_INTEGRATION_PIPELINED) {
+ spinand->dirmaps[plane].wdesc_ecc = spinand->dirmaps[plane].wdesc;
+ spinand->dirmaps[plane].rdesc_ecc = spinand->dirmaps[plane].rdesc;
+
+ return 0;
+ }
+
+ info.op_tmpl = *spinand->op_templates.update_cache;
+ info.op_tmpl.data.ecc = true;
+ desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+ spinand->spimem, &info);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ spinand->dirmaps[plane].wdesc_ecc = desc;
+
+ info.op_tmpl = *spinand->op_templates.read_cache;
+ info.op_tmpl.data.ecc = true;
+ desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+ spinand->spimem, &info);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ spinand->dirmaps[plane].rdesc_ecc = desc;
+
return 0;
}
@@ -1208,14 +1239,6 @@ static int spinand_init(struct spinand_device *spinand)
if (ret)
goto err_free_bufs;
- ret = spinand_create_dirmaps(spinand);
- if (ret) {
- dev_err(dev,
- "Failed to create direct mappings for read/write operations (err = %d)\n",
- ret);
- goto err_manuf_cleanup;
- }
-
ret = nanddev_init(nand, &spinand_ops, THIS_MODULE);
if (ret)
goto err_manuf_cleanup;
@@ -1250,6 +1273,14 @@ static int spinand_init(struct spinand_device *spinand)
mtd->ecc_strength = nanddev_get_ecc_conf(nand)->strength;
mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size;
+ ret = spinand_create_dirmaps(spinand);
+ if (ret) {
+ dev_err(dev,
+ "Failed to create direct mappings for read/write operations (err = %d)\n",
+ ret);
+ goto err_cleanup_ecc_engine;
+ }
+
return 0;
err_cleanup_ecc_engine:
diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c
index 3f31f1381a62..dce835132a1e 100644
--- a/drivers/mtd/nand/spi/macronix.c
+++ b/drivers/mtd/nand/spi/macronix.c
@@ -20,7 +20,7 @@ static SPINAND_OP_VARIANTS(read_cache_variants,
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD(false, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),