From 96489c1c0b53131b0e1ec33e2060538379ad6152 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Dec 2021 12:16:38 +0100 Subject: mtd: nand: ecc: Add infrastructure to support hardware engines Add the necessary helpers to register/unregister hardware ECC engines that will be called from ECC engine drivers. Also add helpers to get the right engine from the user perspective. Keep a reference of the in use ECC engine in order to prevent modules to be unloaded. Put the reference when the engine gets retired. A static list of hardware (only) ECC engines is setup to keep track of the registered engines. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20211216111654.238086-13-miquel.raynal@bootlin.com --- include/linux/mtd/nand.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'include/linux/mtd') diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 32fc7edf65b3..4ddd20fe9c9e 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -263,12 +263,36 @@ struct nand_ecc_engine_ops { struct nand_page_io_req *req); }; +/** + * enum nand_ecc_engine_integration - How the NAND ECC engine is integrated + * @NAND_ECC_ENGINE_INTEGRATION_INVALID: Invalid value + * @NAND_ECC_ENGINE_INTEGRATION_PIPELINED: Pipelined engine, performs on-the-fly + * correction, does not need to copy + * data around + * @NAND_ECC_ENGINE_INTEGRATION_EXTERNAL: External engine, needs to bring the + * data into its own area before use + */ +enum nand_ecc_engine_integration { + NAND_ECC_ENGINE_INTEGRATION_INVALID, + NAND_ECC_ENGINE_INTEGRATION_PIPELINED, + NAND_ECC_ENGINE_INTEGRATION_EXTERNAL, +}; + /** * struct nand_ecc_engine - ECC engine abstraction for NAND devices + * @dev: Host device + * @node: Private field for registration time * @ops: ECC engine operations + * @integration: How the engine is integrated with the host + * (only relevant on %NAND_ECC_ENGINE_TYPE_ON_HOST engines) + * @priv: Private data */ struct nand_ecc_engine { + struct device *dev; + struct list_head node; struct nand_ecc_engine_ops *ops; + enum nand_ecc_engine_integration integration; + void *priv; }; void of_get_nand_ecc_user_config(struct nand_device *nand); @@ -279,8 +303,12 @@ int nand_ecc_prepare_io_req(struct nand_device *nand, int nand_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req); bool nand_ecc_is_strong_enough(struct nand_device *nand); +int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine); +int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine); struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand); struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand); +struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand); +void nand_ecc_put_on_host_hw_engine(struct nand_device *nand); #if IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING) struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void); -- cgit v1.2.3 From cda32a618debd3fad8e42757b198719ae180f8f4 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Dec 2021 12:16:39 +0100 Subject: mtd: nand: Add a new helper to retrieve the ECC context Introduce nand_to_ecc_ctx() which will allow to easily jump to the private pointer of an ECC context given a NAND device. This is very handy, from the prepare or finish ECC hook, to get the internal context out of the NAND device object. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20211216111654.238086-14-miquel.raynal@bootlin.com --- include/linux/mtd/nand.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include/linux/mtd') diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 4ddd20fe9c9e..b617efa0a881 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -990,6 +990,11 @@ int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos); int nanddev_ecc_engine_init(struct nand_device *nand); void nanddev_ecc_engine_cleanup(struct nand_device *nand); +static inline void *nand_to_ecc_ctx(struct nand_device *nand) +{ + return nand->ecc.ctx.priv; +} + /* BBT related functions */ enum nand_bbt_block_status { NAND_BBT_BLOCK_STATUS_UNKNOWN, -- cgit v1.2.3 From ad5e35f58384179bbd3cdb349904e150f364c4f3 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 28 Jan 2022 12:34:14 +0100 Subject: mtd: Replace the expert mode symbols with a single helper Reduce the number of exported symbols by replacing: - mtd_expert_analysis_warning (the error string) - mtd_expert_analysis_mode (the boolean) with a single helper: - mtd_check_expert_analysis_mode Calling this helper will both check/return the content of the internal boolean -which is not exported anymore- and as well conditionally WARN_ONCE() the user, like it was done before. While on this function, make the error string local to the helper and set it const. Only export this helper when CONFIG_DEBUG_FS is defined to limit the growth of the Linux kernel size only for a debug feature on production kernels. Mechanically update all the consumers. Suggested-by: Geert Uytterhoeven Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220128113414.1121924-1-miquel.raynal@bootlin.com --- drivers/mtd/mtdcore.c | 23 +++++++++++++++-------- drivers/mtd/nand/core.c | 2 +- drivers/mtd/nand/raw/nand_base.c | 2 +- drivers/mtd/nand/raw/nand_bbt.c | 2 +- include/linux/mtd/mtd.h | 8 ++++++-- 5 files changed, 24 insertions(+), 13 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 70f492dce158..1e7f3bbf847e 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -358,6 +358,21 @@ static int mtd_partname_debug_show(struct seq_file *s, void *p) DEFINE_SHOW_ATTRIBUTE(mtd_partname_debug); +static bool mtd_expert_analysis_mode; + +#ifdef CONFIG_DEBUG_FS +bool mtd_check_expert_analysis_mode(void) +{ + 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 WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning); +} +EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode); +#endif + static struct dentry *dfs_dir_mtd; static void mtd_debugfs_populate(struct mtd_info *mtd) @@ -2370,14 +2385,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/core.c b/drivers/mtd/nand/core.c index 416947f28b67..3ff99a42a63f 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)) { diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index e7b2ba016d8c..068ecf979033 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) 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/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 1ffa933121f6..1b3fc8c71ab3 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -711,7 +711,11 @@ static inline int mtd_is_bitflip_or_eccerr(int err) { unsigned mtd_mmap_capabilities(struct mtd_info *mtd); -extern char *mtd_expert_analysis_warning; -extern bool mtd_expert_analysis_mode; +#ifdef CONFIG_DEBUG_FS +bool mtd_check_expert_analysis_mode(void); +#else +static inline bool mtd_check_expert_analysis_mode(void) { return false; } +#endif + #endif /* __MTD_MTD_H__ */ -- cgit v1.2.3 From 8cba323437a49a45756d661f500b324fc2d486fe Mon Sep 17 00:00:00 2001 From: Sean Nyekjaer Date: Tue, 8 Feb 2022 09:52:13 +0100 Subject: mtd: rawnand: protect access to rawnand devices while in suspend Prevent rawnand access while in a suspended state. Commit 013e6292aaf5 ("mtd: rawnand: Simplify the locking") allows the rawnand layer to return errors rather than waiting in a blocking wait. Tested on a iMX6ULL. Fixes: 013e6292aaf5 ("mtd: rawnand: Simplify the locking") Signed-off-by: Sean Nyekjaer Reviewed-by: Boris Brezillon Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220208085213.1838273-1-sean@geanix.com --- drivers/mtd/nand/raw/nand_base.c | 44 ++++++++++++++++++---------------------- include/linux/mtd/rawnand.h | 2 ++ 2 files changed, 22 insertions(+), 24 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 3e4a525ac3ca..612ae60e9763 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -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); } /** @@ -5438,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/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index 5b88cd51fadb..dcf90144d70b 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -1240,6 +1240,7 @@ struct nand_secure_region { * @lock: Lock protecting the suspended field. Also used to serialize accesses * to the NAND device * @suspended: Set to 1 when the device is suspended, 0 when it's not + * @resume_wq: wait queue to sleep if rawnand is in suspended state. * @cur_cs: Currently selected target. -1 means no target selected, otherwise we * should always have cur_cs >= 0 && cur_cs < nanddev_ntargets(). * NAND Controller drivers should not modify this value, but they're @@ -1294,6 +1295,7 @@ struct nand_chip { /* Internals */ struct mutex lock; unsigned int suspended : 1; + wait_queue_head_t resume_wq; int cur_cs; int read_retries; struct nand_secure_region *secure_regions; -- cgit v1.2.3 From 5145abeb0649acf810a32e63bd762e617a9b3309 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Dec 2021 12:16:41 +0100 Subject: mtd: nand: ecc: Provide a helper to retrieve a pilelined engine device In a pipelined engine situation, we might either have the host which internally has support for error correction, or have it using an external hardware block for this purpose. In the former case, the host is also the ECC engine. In the latter case, it is not. In order to get the right pointers on the right devices (for example: in order to devm_* allocate variables), let's introduce this helper which can safely be called by pipelined ECC engines in order to retrieve the right device structure. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20211216111654.238086-16-miquel.raynal@bootlin.com --- drivers/mtd/nand/ecc.c | 31 +++++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 1 + 2 files changed, 32 insertions(+) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c index 078f5ec38de3..5250764cedee 100644 --- a/drivers/mtd/nand/ecc.c +++ b/drivers/mtd/nand/ecc.c @@ -699,6 +699,37 @@ void nand_ecc_put_on_host_hw_engine(struct nand_device *nand) } 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 "); MODULE_DESCRIPTION("Generic ECC engine"); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index b617efa0a881..615b3e3a3920 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -309,6 +309,7 @@ struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand); struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand); struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand); void nand_ecc_put_on_host_hw_engine(struct nand_device *nand); +struct device *nand_ecc_get_engine_dev(struct device *host); #if IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING) struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void); -- cgit v1.2.3 From 70e038f89b467995708207fb57bbf46aec32dc2c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Dec 2021 12:16:42 +0100 Subject: mtd: nand: mxic-ecc: Support SPI pipelined mode Introduce the support for another possible configuration: the ECC engine may work as DMA master (pipelined) and move itself the data to/from the NAND chip into the buffer, applying the necessary corrections/computations on the fly. This driver offers an ECC engine implementation that must be instatiated from a SPI controller driver. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20211216111654.238086-17-miquel.raynal@bootlin.com --- drivers/mtd/nand/ecc-mxic.c | 186 +++++++++++++++++++++++++++++++++++++- include/linux/mtd/nand-ecc-mxic.h | 49 ++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 include/linux/mtd/nand-ecc-mxic.h (limited to 'include/linux/mtd') diff --git a/drivers/mtd/nand/ecc-mxic.c b/drivers/mtd/nand/ecc-mxic.c index 3e8fbe62547e..c122139255e5 100644 --- a/drivers/mtd/nand/ecc-mxic.c +++ b/drivers/mtd/nand/ecc-mxic.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -40,7 +41,9 @@ #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 @@ -87,6 +90,7 @@ struct mxic_ecc_engine { int irq; struct completion complete; struct nand_ecc_engine external_engine; + struct nand_ecc_engine pipelined_engine; struct mutex lock; }; @@ -104,6 +108,7 @@ struct mxic_ecc_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) @@ -111,11 +116,19 @@ 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; - return ext_ecc_eng_to_mxic(eng); + 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, @@ -364,6 +377,38 @@ static int mxic_ecc_init_ctx_external(struct nand_device *nand) 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); @@ -419,6 +464,18 @@ static int mxic_ecc_process_data(struct mxic_ecc_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; @@ -592,6 +649,11 @@ static int mxic_ecc_finish_io_req_external(struct nand_device *nand, 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); @@ -601,6 +663,65 @@ static int mxic_ecc_finish_io_req_external(struct nand_device *nand, 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, @@ -608,6 +729,69 @@ static struct nand_ecc_engine_ops mxic_ecc_engine_external_ops = { .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; diff --git a/include/linux/mtd/nand-ecc-mxic.h b/include/linux/mtd/nand-ecc-mxic.h new file mode 100644 index 000000000000..f3aa1ac82aed --- /dev/null +++ b/include/linux/mtd/nand-ecc-mxic.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright © 2019 Macronix + * Author: Miquèl Raynal + * + * Header for the Macronix external ECC engine. + */ + +#ifndef __MTD_NAND_ECC_MXIC_H__ +#define __MTD_NAND_ECC_MXIC_H__ + +#include +#include + +struct mxic_ecc_engine; + +#if IS_ENABLED(CONFIG_MTD_NAND_ECC_MXIC) + +struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void); +struct nand_ecc_engine *mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev); +void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng); +int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng, + unsigned int direction, dma_addr_t dirmap); + +#else /* !CONFIG_MTD_NAND_ECC_MXIC */ + +static inline struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void) +{ + return NULL; +} + +static inline struct nand_ecc_engine * +mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng) {} + +static inline int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng, + unsigned int direction, + dma_addr_t dirmap) +{ + return -EOPNOTSUPP; +} + +#endif /* CONFIG_MTD_NAND_ECC_MXIC */ + +#endif /* __MTD_NAND_ECC_MXIC_H__ */ -- cgit v1.2.3 From f9d7c7265bcff7d9a17425a8cddf702e8fe159c2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 27 Jan 2022 10:18:03 +0100 Subject: mtd: spinand: Create direct mapping descriptors for ECC operations In order for pipelined ECC engines to be able to enable/disable the ECC engine only when needed and avoid races when future parallel-operations will be supported, we need to provide the information about the use of the ECC engine in the direct mapping hooks. As direct mapping configurations are meant to be static, it is best to create two new mappings: one for regular 'raw' accesses and one for accesses involving correction. It is up to the driver to use or not the new ECC enable boolean contained in the spi-mem operation. As dirmaps are not free (they consume a few pages of MMIO address space) and because these extra entries are only meant to be used by pipelined engines, let's limit their use to this specific type of engine and save a bit of memory with all the other setups. Signed-off-by: Miquel Raynal Reviewed-by: Boris Brezillon Link: https://lore.kernel.org/linux-mtd/20220127091808.1043392-9-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/core.c | 35 +++++++++++++++++++++++++++++++++-- include/linux/mtd/spinand.h | 2 ++ 2 files changed, 35 insertions(+), 2 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index bb6b026b558b..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; } diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 6988956b8492..3aa28240a77f 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -389,6 +389,8 @@ struct spinand_info { struct spinand_dirmap { struct spi_mem_dirmap_desc *wdesc; struct spi_mem_dirmap_desc *rdesc; + struct spi_mem_dirmap_desc *wdesc_ecc; + struct spi_mem_dirmap_desc *rdesc_ecc; }; /** -- cgit v1.2.3 From 00360ebae483e603d55ec9a7231b787cb80ffe13 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 2 Feb 2022 15:45:36 +0100 Subject: spi: mxic: Add support for pipelined ECC operations Some SPI-NAND chips do not have a proper on-die ECC engine providing error correction/detection. This is particularly an issue on embedded devices with limited resources because all the computations must happen in software, unless an external hardware engine is provided. These external engines are new and can be of two categories: external or pipelined. Macronix is providing both, the former being already supported. The second, however, is very SoC implementation dependent and must be instantiated by the SPI host controller directly. An entire subsystem has been contributed to support these engines which makes the insertion into another subsystem such as SPI quite straightforward without the need for a lot of specific functions. Signed-off-by: Miquel Raynal Reviewed-by: Mark Brown Link: https://lore.kernel.org/linux-mtd/20220202144536.393792-1-miquel.raynal@bootlin.com --- drivers/spi/Kconfig | 1 + drivers/spi/spi-mxic.c | 113 +++++++++++++++++++++++++++++++++++++- include/linux/mtd/nand-ecc-mxic.h | 2 +- include/linux/mtd/nand.h | 15 +++++ 4 files changed, 128 insertions(+), 3 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b2a8821971e1..269613b03e40 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -879,6 +879,7 @@ config SPI_SYNQUACER config SPI_MXIC tristate "Macronix MX25F0A SPI controller" depends on SPI_MASTER + imply MTD_NAND_ECC_MXIC help This selects the Macronix MX25F0A SPI controller driver. diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index 0d3390964c6f..55c092069301 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -167,6 +169,7 @@ #define HW_TEST(x) (0xe0 + ((x) * 4)) struct mxic_spi { + struct device *dev; struct clk *ps_clk; struct clk *send_clk; struct clk *send_dly_clk; @@ -177,6 +180,12 @@ struct mxic_spi { dma_addr_t dma; size_t size; } linear; + + struct { + bool use_pipelined_conf; + struct nand_ecc_engine *pipelined_engine; + void *ctx; + } ecc; }; static int mxic_spi_clk_enable(struct mxic_spi *mxic) @@ -400,7 +409,15 @@ static ssize_t mxic_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc, LMODE_EN, mxic->regs + LRD_CTRL); - memcpy_fromio(buf, mxic->linear.map, len); + if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.data.ecc) { + ret = mxic_ecc_process_data_pipelined(mxic->ecc.pipelined_engine, + NAND_PAGE_READ, + mxic->linear.dma + offs); + if (ret) + return ret; + } else { + memcpy_fromio(buf, mxic->linear.map, len); + } writel(INT_LRD_DIS, mxic->regs + INT_STS); writel(0, mxic->regs + LRD_CTRL); @@ -436,7 +453,15 @@ static ssize_t mxic_spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc, LMODE_EN, mxic->regs + LWR_CTRL); - memcpy_toio(mxic->linear.map, buf, len); + if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.data.ecc) { + ret = mxic_ecc_process_data_pipelined(mxic->ecc.pipelined_engine, + NAND_PAGE_WRITE, + mxic->linear.dma + offs); + if (ret) + return ret; + } else { + memcpy_toio(mxic->linear.map, buf, len); + } writel(INT_LWR_DIS, mxic->regs + INT_STS); writel(0, mxic->regs + LWR_CTRL); @@ -547,6 +572,7 @@ static const struct spi_controller_mem_ops mxic_spi_mem_ops = { static const struct spi_controller_mem_caps mxic_spi_mem_caps = { .dtr = true, + .ecc = true, }; static void mxic_spi_set_cs(struct spi_device *spi, bool lvl) @@ -611,6 +637,80 @@ static int mxic_spi_transfer_one(struct spi_master *master, return 0; } +/* ECC wrapper */ +static int mxic_spi_mem_ecc_init_ctx(struct nand_device *nand) +{ + struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops(); + struct mxic_spi *mxic = nand->ecc.engine->priv; + + mxic->ecc.use_pipelined_conf = true; + + return ops->init_ctx(nand); +} + +static void mxic_spi_mem_ecc_cleanup_ctx(struct nand_device *nand) +{ + struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops(); + struct mxic_spi *mxic = nand->ecc.engine->priv; + + mxic->ecc.use_pipelined_conf = false; + + ops->cleanup_ctx(nand); +} + +static int mxic_spi_mem_ecc_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops(); + + return ops->prepare_io_req(nand, req); +} + +static int mxic_spi_mem_ecc_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops(); + + return ops->finish_io_req(nand, req); +} + +static struct nand_ecc_engine_ops mxic_spi_mem_ecc_engine_pipelined_ops = { + .init_ctx = mxic_spi_mem_ecc_init_ctx, + .cleanup_ctx = mxic_spi_mem_ecc_cleanup_ctx, + .prepare_io_req = mxic_spi_mem_ecc_prepare_io_req, + .finish_io_req = mxic_spi_mem_ecc_finish_io_req, +}; + +static void mxic_spi_mem_ecc_remove(struct mxic_spi *mxic) +{ + if (mxic->ecc.pipelined_engine) { + mxic_ecc_put_pipelined_engine(mxic->ecc.pipelined_engine); + nand_ecc_unregister_on_host_hw_engine(mxic->ecc.pipelined_engine); + } +} + +static int mxic_spi_mem_ecc_probe(struct platform_device *pdev, + struct mxic_spi *mxic) +{ + struct nand_ecc_engine *eng; + + if (!mxic_ecc_get_pipelined_ops()) + return -EOPNOTSUPP; + + eng = mxic_ecc_get_pipelined_engine(pdev); + if (IS_ERR(eng)) + return PTR_ERR(eng); + + eng->dev = &pdev->dev; + eng->integration = NAND_ECC_ENGINE_INTEGRATION_PIPELINED; + eng->ops = &mxic_spi_mem_ecc_engine_pipelined_ops; + eng->priv = mxic; + mxic->ecc.pipelined_engine = eng; + nand_ecc_register_on_host_hw_engine(eng); + + return 0; +} + static int __maybe_unused mxic_spi_runtime_suspend(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); @@ -656,6 +756,7 @@ static int mxic_spi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, master); mxic = spi_master_get_devdata(master); + mxic->dev = &pdev->dev; master->dev.of_node = pdev->dev.of_node; @@ -702,6 +803,12 @@ static int mxic_spi_probe(struct platform_device *pdev) mxic_spi_hw_init(mxic); + ret = mxic_spi_mem_ecc_probe(pdev, mxic); + if (ret == -EPROBE_DEFER) { + pm_runtime_disable(&pdev->dev); + return ret; + } + ret = spi_register_master(master); if (ret) { dev_err(&pdev->dev, "spi_register_master failed\n"); @@ -714,8 +821,10 @@ static int mxic_spi_probe(struct platform_device *pdev) static int mxic_spi_remove(struct platform_device *pdev) { struct spi_master *master = platform_get_drvdata(pdev); + struct mxic_spi *mxic = spi_master_get_devdata(master); pm_runtime_disable(&pdev->dev); + mxic_spi_mem_ecc_remove(mxic); spi_unregister_master(master); return 0; diff --git a/include/linux/mtd/nand-ecc-mxic.h b/include/linux/mtd/nand-ecc-mxic.h index f3aa1ac82aed..b125926e458c 100644 --- a/include/linux/mtd/nand-ecc-mxic.h +++ b/include/linux/mtd/nand-ecc-mxic.h @@ -14,7 +14,7 @@ struct mxic_ecc_engine; -#if IS_ENABLED(CONFIG_MTD_NAND_ECC_MXIC) +#if IS_ENABLED(CONFIG_MTD_NAND_ECC_MXIC) && IS_REACHABLE(CONFIG_MTD_NAND_CORE) struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void); struct nand_ecc_engine *mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 615b3e3a3920..c3693bb87b4c 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -303,8 +303,23 @@ int nand_ecc_prepare_io_req(struct nand_device *nand, int nand_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req); bool nand_ecc_is_strong_enough(struct nand_device *nand); + +#if IS_REACHABLE(CONFIG_MTD_NAND_CORE) int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine); int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine); +#else +static inline int +nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine) +{ + return -ENOTSUPP; +} +static inline int +nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine) +{ + return -ENOTSUPP; +} +#endif + struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand); struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand); struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand); -- cgit v1.2.3 From 8b4195cd6dc3f1f0ab457d23d21e9f72fde0760a Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Wed, 23 Feb 2022 14:43:47 +0100 Subject: mtd: spi-nor: move all xilinx specifics into xilinx.c Mechanically move all the xilinx functions to its own module. Then register the new flash specific ready() function. Signed-off-by: Michael Walle Signed-off-by: Tudor Ambarus Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/r/20220223134358.1914798-22-michael@walle.cc --- drivers/mtd/spi-nor/core.c | 64 +------------------------------------- drivers/mtd/spi-nor/core.h | 18 ----------- drivers/mtd/spi-nor/xilinx.c | 73 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 9 ------ 4 files changed, 74 insertions(+), 90 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 8481272533a3..ae1560250c48 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -598,57 +598,6 @@ int spi_nor_write_ear(struct spi_nor *nor, u8 ear) return ret; } -/** - * spi_nor_xread_sr() - Read the Status Register on S3AN flashes. - * @nor: pointer to 'struct spi_nor'. - * @sr: pointer to a DMA-able buffer where the value of the - * Status Register will be written. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr, 0)); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_XRDSR, sr, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading XRDSR\n", ret); - - return ret; -} - -/** - * spi_nor_xsr_ready() - Query the Status Register of the S3AN flash to see if - * the flash is ready for new commands. - * @nor: pointer to 'struct spi_nor'. - * - * Return: 1 if ready, 0 if not ready, -errno on errors. - */ -static int spi_nor_xsr_ready(struct spi_nor *nor) -{ - int ret; - - ret = spi_nor_xread_sr(nor, nor->bouncebuf); - if (ret) - return ret; - - return !!(nor->bouncebuf[0] & XSR_RDY); -} - /** * spi_nor_clear_sr() - Clear the Status Register. * @nor: pointer to 'struct spi_nor'. @@ -798,10 +747,7 @@ static int spi_nor_ready(struct spi_nor *nor) if (nor->params->ready) return nor->params->ready(nor); - if (nor->flags & SNOR_F_READY_XSR_RDY) - sr = spi_nor_xsr_ready(nor); - else - sr = spi_nor_sr_ready(nor); + sr = spi_nor_sr_ready(nor); if (sr < 0) return sr; fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1; @@ -2677,14 +2623,6 @@ static void spi_nor_init_flags(struct spi_nor *nor) if (flags & USE_FSR) nor->flags |= SNOR_F_USE_FSR; - - /* - * Make sure the XSR_RDY flag is set before calling - * spi_nor_wait_till_ready(). Xilinx S3AN share MFR - * with Atmel SPI NOR. - */ - if (flags & SPI_NOR_XSR_RDY) - nor->flags |= SNOR_F_READY_XSR_RDY; } /** diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 3c37b46d60d5..fabc01ae9a81 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -15,7 +15,6 @@ enum spi_nor_option_flags { SNOR_F_USE_FSR = BIT(0), SNOR_F_HAS_SR_TB = BIT(1), SNOR_F_NO_OP_CHIP_ERASE = BIT(2), - SNOR_F_READY_XSR_RDY = BIT(3), SNOR_F_USE_CLSR = BIT(4), SNOR_F_BROKEN_RESET = BIT(5), SNOR_F_4B_OPCODES = BIT(6), @@ -351,8 +350,6 @@ struct spi_nor_fixups { * SPI_NOR_NO_FR: can't do fastread. * USE_CLSR: use CLSR command. * USE_FSR: use flag status register - * SPI_NOR_XSR_RDY: S3AN flashes have specific opcode to read the - * status register. * * @no_sfdp_flags: flags that indicate support that can be discovered via SFDP. * Used when SFDP tables are not defined in the flash. These @@ -405,7 +402,6 @@ struct flash_info { #define SPI_NOR_NO_FR BIT(8) #define USE_CLSR BIT(9) #define USE_FSR BIT(10) -#define SPI_NOR_XSR_RDY BIT(11) u8 no_sfdp_flags; #define SPI_NOR_SKIP_SFDP BIT(0) @@ -462,19 +458,6 @@ struct flash_info { .addr_width = (_addr_width), \ .flags = SPI_NOR_NO_ERASE | SPI_NOR_NO_FR, \ -#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \ - .id = { \ - ((_jedec_id) >> 16) & 0xff, \ - ((_jedec_id) >> 8) & 0xff, \ - (_jedec_id) & 0xff \ - }, \ - .id_len = 3, \ - .sector_size = (8 * (_page_size)), \ - .n_sectors = (_n_sectors), \ - .page_size = (_page_size), \ - .addr_width = 3, \ - .flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY, - #define OTP_INFO(_len, _n_regions, _base, _offset) \ .otp_org = { \ .len = (_len), \ @@ -564,7 +547,6 @@ int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len); int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1); int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr); -int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr); ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf); ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, diff --git a/drivers/mtd/spi-nor/xilinx.c b/drivers/mtd/spi-nor/xilinx.c index 07dd11788df5..05c7fe843a7d 100644 --- a/drivers/mtd/spi-nor/xilinx.c +++ b/drivers/mtd/spi-nor/xilinx.c @@ -8,6 +8,27 @@ #include "core.h" +#define SPINOR_OP_XSE 0x50 /* Sector erase */ +#define SPINOR_OP_XPP 0x82 /* Page program */ +#define SPINOR_OP_XRDSR 0xd7 /* Read status register */ + +#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */ +#define XSR_RDY BIT(7) /* Ready */ + +#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \ + .id = { \ + ((_jedec_id) >> 16) & 0xff, \ + ((_jedec_id) >> 8) & 0xff, \ + (_jedec_id) & 0xff \ + }, \ + .id_len = 3, \ + .sector_size = (8 * (_page_size)), \ + .n_sectors = (_n_sectors), \ + .page_size = (_page_size), \ + .addr_width = 3, \ + .flags = SPI_NOR_NO_FR + +/* Xilinx S3AN share MFR with Atmel SPI NOR */ static const struct flash_info xilinx_nor_parts[] = { /* Xilinx S3AN Internal Flash */ { "3S50AN", S3AN_INFO(0x1f2200, 64, 264) }, @@ -38,6 +59,57 @@ static u32 s3an_nor_convert_addr(struct spi_nor *nor, u32 addr) return page | offset; } +/** + * spi_nor_xread_sr() - Read the Status Register on S3AN flashes. + * @nor: pointer to 'struct spi_nor'. + * @sr: pointer to a DMA-able buffer where the value of the + * Status Register will be written. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 0), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, sr, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_XRDSR, sr, + 1); + } + + if (ret) + dev_dbg(nor->dev, "error %d reading XRDSR\n", ret); + + return ret; +} + +/** + * spi_nor_xsr_ready() - Query the Status Register of the S3AN flash to see if + * the flash is ready for new commands. + * @nor: pointer to 'struct spi_nor'. + * + * Return: 1 if ready, 0 if not ready, -errno on errors. + */ +static int spi_nor_xsr_ready(struct spi_nor *nor) +{ + int ret; + + ret = spi_nor_xread_sr(nor, nor->bouncebuf); + if (ret) + return ret; + + return !!(nor->bouncebuf[0] & XSR_RDY); +} + static int xilinx_nor_setup(struct spi_nor *nor, const struct spi_nor_hwcaps *hwcaps) { @@ -83,6 +155,7 @@ static int xilinx_nor_setup(struct spi_nor *nor, static void xilinx_nor_late_init(struct spi_nor *nor) { nor->params->setup = xilinx_nor_setup; + nor->params->ready = spi_nor_xsr_ready; } static const struct spi_nor_fixups xilinx_nor_fixups = { diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index fc90fce26e33..b44b05a6f934 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -86,15 +86,6 @@ #define SPINOR_OP_BP 0x02 /* Byte program */ #define SPINOR_OP_AAI_WP 0xad /* Auto address increment word program */ -/* Used for S3AN flashes only */ -#define SPINOR_OP_XSE 0x50 /* Sector erase */ -#define SPINOR_OP_XPP 0x82 /* Page program */ -#define SPINOR_OP_XRDSR 0xd7 /* Read status register */ - -#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */ -#define XSR_RDY BIT(7) /* Ready */ - - /* Used for Macronix and Winbond flashes. */ #define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */ #define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */ -- cgit v1.2.3 From c770abe52d81089a8b8ecd1fe42722e29bbab5f5 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Wed, 23 Feb 2022 14:43:50 +0100 Subject: mtd: spi-nor: move all micron-st specifics into micron-st.c The flag status register is only available on micron flashes. Move all the functions around that into the micron module. This is almost a mechanical move except for the spi_nor_fsr_ready() which now also checks the normal status register. Previously, this was done in spi_nor_ready(). Signed-off-by: Michael Walle Signed-off-by: Tudor Ambarus Tested-by: Pratyush Yadav # on mt35xu512aba, s28hs512t Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/r/20220223134358.1914798-25-michael@walle.cc --- drivers/mtd/spi-nor/core.c | 123 +----------------------------------- drivers/mtd/spi-nor/micron-st.c | 134 ++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 8 --- 3 files changed, 135 insertions(+), 130 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ae1560250c48..5b56d718692b 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -412,50 +412,6 @@ int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) return ret; } -/** - * spi_nor_read_fsr() - Read the Flag Status Register. - * @nor: pointer to 'struct spi_nor' - * @fsr: pointer to a DMA-able buffer where the value of the - * Flag Status Register will be written. Should be at least 2 - * bytes. - * - * Return: 0 on success, -errno otherwise. - */ -static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, fsr, 0)); - - if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { - op.addr.nbytes = nor->params->rdsr_addr_nbytes; - op.dummy.nbytes = nor->params->rdsr_dummy; - /* - * We don't want to read only one byte in DTR mode. So, - * read 2 and then discard the second byte. - */ - op.data.nbytes = 2; - } - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDFSR, fsr, - 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d reading FSR\n", ret); - - return ret; -} - /** * spi_nor_read_cr() - Read the Configuration Register using the * SPINOR_OP_RDCR (35h) command. @@ -664,75 +620,6 @@ int spi_nor_sr_ready(struct spi_nor *nor) return !(nor->bouncebuf[0] & SR_WIP); } -/** - * spi_nor_clear_fsr() - Clear the Flag Status Register. - * @nor: pointer to 'struct spi_nor'. - */ -static void spi_nor_clear_fsr(struct spi_nor *nor) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLFSR, - NULL, 0); - } - - if (ret) - dev_dbg(nor->dev, "error %d clearing FSR\n", ret); -} - -/** - * spi_nor_fsr_ready() - Query the Flag Status Register to see if the flash is - * ready for new commands. - * @nor: pointer to 'struct spi_nor'. - * - * Return: 1 if ready, 0 if not ready, -errno on errors. - */ -static int spi_nor_fsr_ready(struct spi_nor *nor) -{ - int ret = spi_nor_read_fsr(nor, nor->bouncebuf); - - if (ret) - return ret; - - if (nor->bouncebuf[0] & (FSR_E_ERR | FSR_P_ERR)) { - if (nor->bouncebuf[0] & FSR_E_ERR) - dev_err(nor->dev, "Erase operation failed.\n"); - else - dev_err(nor->dev, "Program operation failed.\n"); - - if (nor->bouncebuf[0] & FSR_PT_ERR) - dev_err(nor->dev, - "Attempted to modify a protected sector.\n"); - - spi_nor_clear_fsr(nor); - - /* - * WEL bit remains set to one when an erase or page program - * error occurs. Issue a Write Disable command to protect - * against inadvertent writes that can possibly corrupt the - * contents of the memory. - */ - ret = spi_nor_write_disable(nor); - if (ret) - return ret; - - return -EIO; - } - - return !!(nor->bouncebuf[0] & FSR_READY); -} - /** * spi_nor_ready() - Query the flash to see if it is ready for new commands. * @nor: pointer to 'struct spi_nor'. @@ -741,19 +628,11 @@ static int spi_nor_fsr_ready(struct spi_nor *nor) */ static int spi_nor_ready(struct spi_nor *nor) { - int sr, fsr; - /* Flashes might override the standard routine. */ if (nor->params->ready) return nor->params->ready(nor); - sr = spi_nor_sr_ready(nor); - if (sr < 0) - return sr; - fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1; - if (fsr < 0) - return fsr; - return sr && fsr; + return spi_nor_sr_ready(nor); } /** diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 7a68f2ad3ea1..e580830ed70f 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -8,6 +8,8 @@ #include "core.h" +#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */ +#define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */ #define SPINOR_OP_MT_DTR_RD 0xfd /* Fast Read opcode in DTR mode */ #define SPINOR_OP_MT_RD_ANY_REG 0x85 /* Read volatile register */ #define SPINOR_OP_MT_WR_ANY_REG 0x81 /* Write volatile register */ @@ -17,6 +19,12 @@ #define SPINOR_MT_OCT_DTR 0xe7 /* Enable Octal DTR. */ #define SPINOR_MT_EXSPI 0xff /* Enable Extended SPI (default) */ +/* Flag Status Register bits */ +#define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */ +#define FSR_E_ERR BIT(5) /* Erase operation status */ +#define FSR_P_ERR BIT(4) /* Program operation status */ +#define FSR_PT_ERR BIT(1) /* Protection error bit */ + static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable) { struct spi_mem_op op; @@ -273,6 +281,125 @@ static int micron_st_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) return spi_nor_write_disable(nor); } +/** + * spi_nor_read_fsr() - Read the Flag Status Register. + * @nor: pointer to 'struct spi_nor' + * @fsr: pointer to a DMA-able buffer where the value of the + * Flag Status Register will be written. Should be at least 2 + * bytes. + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, fsr, 0)); + + if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { + op.addr.nbytes = nor->params->rdsr_addr_nbytes; + op.dummy.nbytes = nor->params->rdsr_dummy; + /* + * We don't want to read only one byte in DTR mode. So, + * read 2 and then discard the second byte. + */ + op.data.nbytes = 2; + } + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDFSR, fsr, + 1); + } + + if (ret) + dev_dbg(nor->dev, "error %d reading FSR\n", ret); + + return ret; +} + +/** + * spi_nor_clear_fsr() - Clear the Flag Status Register. + * @nor: pointer to 'struct spi_nor'. + */ +static void spi_nor_clear_fsr(struct spi_nor *nor) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLFSR, + NULL, 0); + } + + if (ret) + dev_dbg(nor->dev, "error %d clearing FSR\n", ret); +} + +/** + * spi_nor_fsr_ready() - Query the Status Register as well as the Flag Status + * Register to see if the flash is ready for new commands. If there are any + * errors in the FSR clear them. + * @nor: pointer to 'struct spi_nor'. + * + * Return: 1 if ready, 0 if not ready, -errno on errors. + */ +static int spi_nor_fsr_ready(struct spi_nor *nor) +{ + int sr_ready, ret; + + sr_ready = spi_nor_sr_ready(nor); + if (sr_ready < 0) + return sr_ready; + + ret = spi_nor_read_fsr(nor, nor->bouncebuf); + if (ret) + return ret; + + if (nor->bouncebuf[0] & (FSR_E_ERR | FSR_P_ERR)) { + if (nor->bouncebuf[0] & FSR_E_ERR) + dev_err(nor->dev, "Erase operation failed.\n"); + else + dev_err(nor->dev, "Program operation failed.\n"); + + if (nor->bouncebuf[0] & FSR_PT_ERR) + dev_err(nor->dev, + "Attempted to modify a protected sector.\n"); + + spi_nor_clear_fsr(nor); + + /* + * WEL bit remains set to one when an erase or page program + * error occurs. Issue a Write Disable command to protect + * against inadvertent writes that can possibly corrupt the + * contents of the memory. + */ + ret = spi_nor_write_disable(nor); + if (ret) + return ret; + + return -EIO; + } + + return sr_ready && !!(nor->bouncebuf[0] & FSR_READY); +} + static void micron_st_nor_default_init(struct spi_nor *nor) { nor->flags |= SNOR_F_HAS_LOCK; @@ -281,8 +408,15 @@ static void micron_st_nor_default_init(struct spi_nor *nor) nor->params->set_4byte_addr_mode = micron_st_nor_set_4byte_addr_mode; } +static void micron_st_nor_late_init(struct spi_nor *nor) +{ + if (nor->flags & SNOR_F_USE_FSR) + nor->params->ready = spi_nor_fsr_ready; +} + static const struct spi_nor_fixups micron_st_nor_fixups = { .default_init = micron_st_nor_default_init, + .late_init = micron_st_nor_late_init, }; const struct spi_nor_manufacturer spi_nor_micron = { diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index b44b05a6f934..4622251a79ff 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -47,8 +47,6 @@ #define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */ #define SPINOR_OP_RDSFDP 0x5a /* Read SFDP */ #define SPINOR_OP_RDCR 0x35 /* Read configuration register */ -#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */ -#define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */ #define SPINOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ #define SPINOR_OP_WREAR 0xc5 /* Write Extended Address Register */ #define SPINOR_OP_SRSTEN 0x66 /* Software Reset Enable */ @@ -126,12 +124,6 @@ /* Enhanced Volatile Configuration Register bits */ #define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */ -/* Flag Status Register bits */ -#define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */ -#define FSR_E_ERR BIT(5) /* Erase operation status */ -#define FSR_P_ERR BIT(4) /* Program operation status */ -#define FSR_PT_ERR BIT(1) /* Protection error bit */ - /* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT1 BIT(1) #define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */ -- cgit v1.2.3 From 837d5181beef068c16bb8424c2c1571a7d5d7966 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Wed, 23 Feb 2022 14:43:54 +0100 Subject: mtd: spi-nor: move all spansion specifics into spansion.c The clear status register flags is only available on spansion flashes. Move all the functions around that into the spanion module. Signed-off-by: Michael Walle Signed-off-by: Tudor Ambarus Tested-by: Pratyush Yadav # on mt35xu512aba, s28hs512t Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/r/20220223134358.1914798-29-michael@walle.cc --- drivers/mtd/spi-nor/core.c | 49 ----------------------------- drivers/mtd/spi-nor/spansion.c | 70 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 1 - 3 files changed, 70 insertions(+), 50 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ac0faedebafe..e2b8b0a438ce 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -554,33 +554,6 @@ int spi_nor_write_ear(struct spi_nor *nor, u8 ear) return ret; } -/** - * spi_nor_clear_sr() - Clear the Status Register. - * @nor: pointer to 'struct spi_nor'. - */ -static void spi_nor_clear_sr(struct spi_nor *nor) -{ - int ret; - - if (nor->spimem) { - struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0), - SPI_MEM_OP_NO_ADDR, - SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_NO_DATA); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLSR, - NULL, 0); - } - - if (ret) - dev_dbg(nor->dev, "error %d clearing SR\n", ret); -} - /** * spi_nor_sr_ready() - Query the Status Register to see if the flash is ready * for new commands. @@ -595,28 +568,6 @@ int spi_nor_sr_ready(struct spi_nor *nor) if (ret) return ret; - if (nor->flags & SNOR_F_USE_CLSR && - nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) { - if (nor->bouncebuf[0] & SR_E_ERR) - dev_err(nor->dev, "Erase Error occurred\n"); - else - dev_err(nor->dev, "Programming Error occurred\n"); - - spi_nor_clear_sr(nor); - - /* - * WEL bit remains set to one when an erase or page program - * error occurs. Issue a Write Disable command to protect - * against inadvertent writes that can possibly corrupt the - * contents of the memory. - */ - ret = spi_nor_write_disable(nor); - if (ret) - return ret; - - return -EIO; - } - return !(nor->bouncebuf[0] & SR_WIP); } diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 1a0e7214d9e5..dbafe97c2636 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -8,6 +8,7 @@ #include "core.h" +#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */ #define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */ #define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */ #define SPINOR_REG_CYPRESS_CFR2V 0x00800003 @@ -294,6 +295,72 @@ static const struct flash_info spansion_nor_parts[] = { }, }; +/** + * spi_nor_clear_sr() - Clear the Status Register. + * @nor: pointer to 'struct spi_nor'. + */ +static void spi_nor_clear_sr(struct spi_nor *nor) +{ + int ret; + + if (nor->spimem) { + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLSR, + NULL, 0); + } + + if (ret) + dev_dbg(nor->dev, "error %d clearing SR\n", ret); +} + +/** + * spi_nor_sr_ready_and_clear() - Query the Status Register to see if the flash + * is ready for new commands and clear it if there are any errors. + * @nor: pointer to 'struct spi_nor'. + * + * Return: 1 if ready, 0 if not ready, -errno on errors. + */ +static int spi_nor_sr_ready_and_clear(struct spi_nor *nor) +{ + int ret; + + ret = spi_nor_read_sr(nor, nor->bouncebuf); + if (ret) + return ret; + + if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) { + if (nor->bouncebuf[0] & SR_E_ERR) + dev_err(nor->dev, "Erase Error occurred\n"); + else + dev_err(nor->dev, "Programming Error occurred\n"); + + spi_nor_clear_sr(nor); + + /* + * WEL bit remains set to one when an erase or page program + * error occurs. Issue a Write Disable command to protect + * against inadvertent writes that can possibly corrupt the + * contents of the memory. + */ + ret = spi_nor_write_disable(nor); + if (ret) + return ret; + + return -EIO; + } + + return !(nor->bouncebuf[0] & SR_WIP); +} + static void spansion_nor_late_init(struct spi_nor *nor) { if (nor->params->size > SZ_16M) { @@ -302,6 +369,9 @@ static void spansion_nor_late_init(struct spi_nor *nor) nor->erase_opcode = SPINOR_OP_SE; nor->mtd.erasesize = nor->info->sector_size; } + + if (nor->flags & SNOR_F_USE_CLSR) + nor->params->ready = spi_nor_sr_ready_and_clear; } static const struct spi_nor_fixups spansion_nor_fixups = { diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 4622251a79ff..5e25a7b75ae2 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -90,7 +90,6 @@ /* Used for Spansion flashes only. */ #define SPINOR_OP_BRWR 0x17 /* Bank register write */ -#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */ /* Used for Micron flashes only. */ #define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */ -- cgit v1.2.3 From ec090a0392ff634e9304c19a8578d476a9e0c830 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 25 Feb 2022 16:46:56 +0200 Subject: mtd: core: Remove partid and partname debugfs files partid and partname debugfs files were used just by SPI NOR, but they were replaced by sysfs entries. Since these debugfs files are no longer used in mtd, remove dead code. The directory is kept as it is used by nandsim, mtdswap and docg3. Signed-off-by: Tudor Ambarus Reviewed-by: Pratyush Yadav Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220225144656.634682-1-tudor.ambarus@microchip.com --- drivers/mtd/mtdcore.c | 35 +---------------------------------- include/linux/mtd/mtd.h | 3 --- 2 files changed, 1 insertion(+), 37 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 1e7f3bbf847e..7dd105daf19d 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -336,28 +336,6 @@ 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); - - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(mtd_partid_debug); - -static int mtd_partname_debug_show(struct seq_file *s, void *p) -{ - struct mtd_info *mtd = s->private; - - seq_printf(s, "%s\n", mtd->dbg.partname); - - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(mtd_partname_debug); - static bool mtd_expert_analysis_mode; #ifdef CONFIG_DEBUG_FS @@ -377,23 +355,12 @@ 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 diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 1b3fc8c71ab3..151607e9d64a 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -188,9 +188,6 @@ struct module; /* only needed for owner field in mtd_info */ */ struct mtd_debug_info { struct dentry *dfs_dir; - - const char *partname; - const char *partid; }; /** -- cgit v1.2.3 From 37c5f9e80e015d0df17d0c377c18523002986851 Mon Sep 17 00:00:00 2001 From: Oleksandr Ocheretnyi Date: Sun, 17 Apr 2022 11:46:47 -0700 Subject: mtd: fix 'part' field data corruption in mtd_info Commit 46b5889cc2c5 ("mtd: implement proper partition handling") started using "mtd_get_master_ofs()" in mtd callbacks to determine memory offsets by means of 'part' field from mtd_info, what previously was smashed accessing 'master' field in the mtd_set_dev_defaults() method. That provides wrong offset what causes hardware access errors. Just make 'part', 'master' as separate fields, rather than using union type to avoid 'part' data corruption when mtd_set_dev_defaults() is called. Fixes: 46b5889cc2c5 ("mtd: implement proper partition handling") Signed-off-by: Oleksandr Ocheretnyi Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220417184649.449289-1-oocheret@cisco.com --- include/linux/mtd/mtd.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'include/linux/mtd') diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 151607e9d64a..955aee14b0f7 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -389,10 +389,8 @@ struct mtd_info { /* List of partitions attached to this MTD device */ struct list_head partitions; - union { - struct mtd_part part; - struct mtd_master master; - }; + struct mtd_part part; + struct mtd_master master; }; static inline struct mtd_info *mtd_get_master(struct mtd_info *mtd) -- cgit v1.2.3 From f4c5c7f9d2e5ab005d57826b740b694b042a737c Mon Sep 17 00:00:00 2001 From: Felix Matouschek Date: Mon, 18 Apr 2022 15:28:03 +0200 Subject: mtd: spinand: Add support for XTX XT26G0xA Add support for XTX Technology XT26G01AXXXXX, XTX26G02AXXXXX and XTX26G04AXXXXX SPI NAND. These are 3V, 1G/2G/4Gbit serial SLC NAND flash devices with on-die ECC (8bit strength per 512bytes). Tested on Teltonika RUTX10 flashed with OpenWrt. Links: - http://www.xtxtech.com/download/?AId=225 - https://datasheet.lcsc.com/szlcsc/2005251034_XTX-XT26G01AWSEGA_C558841.pdf Signed-off-by: Felix Matouschek Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220418132803.664103-1-felix@matouschek.org --- drivers/mtd/nand/spi/Makefile | 2 +- drivers/mtd/nand/spi/core.c | 1 + drivers/mtd/nand/spi/xtx.c | 129 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 1 + 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/xtx.c (limited to 'include/linux/mtd') diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index 9662b9c1d5a9..80dabe6ff0f3 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o +spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index ff8336870bc0..d5b685d1605e 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -933,6 +933,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = { ¶gon_spinand_manufacturer, &toshiba_spinand_manufacturer, &winbond_spinand_manufacturer, + &xtx_spinand_manufacturer, }; static int spinand_manufacturer_match(struct spinand_device *spinand, diff --git a/drivers/mtd/nand/spi/xtx.c b/drivers/mtd/nand/spi/xtx.c new file mode 100644 index 000000000000..3911520f718c --- /dev/null +++ b/drivers/mtd/nand/spi/xtx.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Felix Matouschek + */ + +#include +#include +#include + +#define SPINAND_MFR_XTX 0x0B + +#define XT26G0XA_STATUS_ECC_MASK GENMASK(5, 2) +#define XT26G0XA_STATUS_ECC_NO_DETECTED (0 << 2) +#define XT26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) +#define XT26G0XA_STATUS_ECC_UNCOR_ERROR (2 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 48; + region->length = 16; + + return 0; +} + +static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 1; + region->length = 47; + + return 0; +} + +static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = { + .ecc = xt26g0xa_ooblayout_ecc, + .free = xt26g0xa_ooblayout_free, +}; + +static int xt26g0xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + status = status & XT26G0XA_STATUS_ECC_MASK; + + switch (status) { + case XT26G0XA_STATUS_ECC_NO_DETECTED: + return 0; + case XT26G0XA_STATUS_ECC_8_CORRECTED: + return 8; + case XT26G0XA_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + default: + break; + } + + /* At this point values greater than (2 << 4) are invalid */ + if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR) + return -EINVAL; + + /* (1 << 2) through (7 << 2) are 1-7 corrected errors */ + return status >> 2; +} + +static const struct spinand_info xtx_spinand_table[] = { + SPINAND_INFO("XT26G01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G02A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G04A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3), + NAND_MEMORG(1, 2048, 64, 128, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer xtx_spinand_manufacturer = { + .id = SPINAND_MFR_XTX, + .name = "XTX", + .chips = xtx_spinand_table, + .nchips = ARRAY_SIZE(xtx_spinand_table), + .ops = &xtx_spinand_manuf_ops, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 3aa28240a77f..5584d3bb6556 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -266,6 +266,7 @@ extern const struct spinand_manufacturer micron_spinand_manufacturer; extern const struct spinand_manufacturer paragon_spinand_manufacturer; extern const struct spinand_manufacturer toshiba_spinand_manufacturer; extern const struct spinand_manufacturer winbond_spinand_manufacturer; +extern const struct spinand_manufacturer xtx_spinand_manufacturer; /** * struct spinand_op_variants - SPI NAND operation variants -- cgit v1.2.3 From 4fd62f15afa0d0da4823f429a2fb4c3492a84edf Mon Sep 17 00:00:00 2001 From: Chuanhong Guo Date: Sun, 24 Apr 2022 11:25:23 +0800 Subject: mtd: nand: make mtk_ecc.c a separated module this code will be used in mediatek snfi spi-mem controller with pipelined ECC engine. Signed-off-by: Chuanhong Guo Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220424032527.673605-2-gch981213@gmail.com --- drivers/mtd/nand/Kconfig | 7 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/ecc-mtk.c | 592 ++++++++++++++++++++++++++++++++++++++ drivers/mtd/nand/raw/Kconfig | 1 + drivers/mtd/nand/raw/Makefile | 2 +- drivers/mtd/nand/raw/mtk_ecc.c | 593 --------------------------------------- drivers/mtd/nand/raw/mtk_ecc.h | 47 ---- drivers/mtd/nand/raw/mtk_nand.c | 2 +- include/linux/mtd/nand-ecc-mtk.h | 47 ++++ 9 files changed, 650 insertions(+), 642 deletions(-) create mode 100644 drivers/mtd/nand/ecc-mtk.c delete mode 100644 drivers/mtd/nand/raw/mtk_ecc.c delete mode 100644 drivers/mtd/nand/raw/mtk_ecc.h create mode 100644 include/linux/mtd/nand-ecc-mtk.h (limited to 'include/linux/mtd') diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 9b249826ef93..2f3e02ab72ed 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -53,6 +53,13 @@ config MTD_NAND_ECC_MXIC help This enables support for the hardware ECC engine from Macronix. +config MTD_NAND_ECC_MEDIATEK + tristate "Mediatek hardware ECC engine" + depends on HAS_IOMEM + select MTD_NAND_ECC + help + This enables support for the hardware ECC engine from Mediatek. + endmenu endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index a4e6b7ae0614..19e1291ac4d5 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -2,6 +2,7 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o +obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o obj-y += onenand/ obj-y += raw/ diff --git a/drivers/mtd/nand/ecc-mtk.c b/drivers/mtd/nand/ecc-mtk.c new file mode 100644 index 000000000000..491c2d66f815 --- /dev/null +++ b/drivers/mtd/nand/ecc-mtk.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * MTK ECC controller driver. + * Copyright (C) 2016 MediaTek Inc. + * Authors: Xiaolei Li + * Jorge Ramirez-Ortiz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ECC_IDLE_MASK BIT(0) +#define ECC_IRQ_EN BIT(0) +#define ECC_PG_IRQ_SEL BIT(1) +#define ECC_OP_ENABLE (1) +#define ECC_OP_DISABLE (0) + +#define ECC_ENCCON (0x00) +#define ECC_ENCCNFG (0x04) +#define ECC_MS_SHIFT (16) +#define ECC_ENCDIADDR (0x08) +#define ECC_ENCIDLE (0x0C) +#define ECC_DECCON (0x100) +#define ECC_DECCNFG (0x104) +#define DEC_EMPTY_EN BIT(31) +#define DEC_CNFG_CORRECT (0x3 << 12) +#define ECC_DECIDLE (0x10C) +#define ECC_DECENUM0 (0x114) + +#define ECC_TIMEOUT (500000) + +#define ECC_IDLE_REG(op) ((op) == ECC_ENCODE ? ECC_ENCIDLE : ECC_DECIDLE) +#define ECC_CTL_REG(op) ((op) == ECC_ENCODE ? ECC_ENCCON : ECC_DECCON) + +struct mtk_ecc_caps { + u32 err_mask; + const u8 *ecc_strength; + const u32 *ecc_regs; + u8 num_ecc_strength; + u8 ecc_mode_shift; + u32 parity_bits; + int pg_irq_sel; +}; + +struct mtk_ecc { + struct device *dev; + const struct mtk_ecc_caps *caps; + void __iomem *regs; + struct clk *clk; + + struct completion done; + struct mutex lock; + u32 sectors; + + u8 *eccdata; +}; + +/* ecc strength that each IP supports */ +static const u8 ecc_strength_mt2701[] = { + 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, + 40, 44, 48, 52, 56, 60 +}; + +static const u8 ecc_strength_mt2712[] = { + 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, + 40, 44, 48, 52, 56, 60, 68, 72, 80 +}; + +static const u8 ecc_strength_mt7622[] = { + 4, 6, 8, 10, 12, 14, 16 +}; + +enum mtk_ecc_regs { + ECC_ENCPAR00, + ECC_ENCIRQ_EN, + ECC_ENCIRQ_STA, + ECC_DECDONE, + ECC_DECIRQ_EN, + ECC_DECIRQ_STA, +}; + +static int mt2701_ecc_regs[] = { + [ECC_ENCPAR00] = 0x10, + [ECC_ENCIRQ_EN] = 0x80, + [ECC_ENCIRQ_STA] = 0x84, + [ECC_DECDONE] = 0x124, + [ECC_DECIRQ_EN] = 0x200, + [ECC_DECIRQ_STA] = 0x204, +}; + +static int mt2712_ecc_regs[] = { + [ECC_ENCPAR00] = 0x300, + [ECC_ENCIRQ_EN] = 0x80, + [ECC_ENCIRQ_STA] = 0x84, + [ECC_DECDONE] = 0x124, + [ECC_DECIRQ_EN] = 0x200, + [ECC_DECIRQ_STA] = 0x204, +}; + +static int mt7622_ecc_regs[] = { + [ECC_ENCPAR00] = 0x10, + [ECC_ENCIRQ_EN] = 0x30, + [ECC_ENCIRQ_STA] = 0x34, + [ECC_DECDONE] = 0x11c, + [ECC_DECIRQ_EN] = 0x140, + [ECC_DECIRQ_STA] = 0x144, +}; + +static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc, + enum mtk_ecc_operation op) +{ + struct device *dev = ecc->dev; + u32 val; + int ret; + + ret = readl_poll_timeout_atomic(ecc->regs + ECC_IDLE_REG(op), val, + val & ECC_IDLE_MASK, + 10, ECC_TIMEOUT); + if (ret) + dev_warn(dev, "%s NOT idle\n", + op == ECC_ENCODE ? "encoder" : "decoder"); +} + +static irqreturn_t mtk_ecc_irq(int irq, void *id) +{ + struct mtk_ecc *ecc = id; + u32 dec, enc; + + dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]) + & ECC_IRQ_EN; + if (dec) { + dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]); + if (dec & ecc->sectors) { + /* + * Clear decode IRQ status once again to ensure that + * there will be no extra IRQ. + */ + readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]); + ecc->sectors = 0; + complete(&ecc->done); + } else { + return IRQ_HANDLED; + } + } else { + enc = readl(ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_STA]) + & ECC_IRQ_EN; + if (enc) + complete(&ecc->done); + else + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int mtk_ecc_config(struct mtk_ecc *ecc, struct mtk_ecc_config *config) +{ + u32 ecc_bit, dec_sz, enc_sz; + u32 reg, i; + + for (i = 0; i < ecc->caps->num_ecc_strength; i++) { + if (ecc->caps->ecc_strength[i] == config->strength) + break; + } + + if (i == ecc->caps->num_ecc_strength) { + dev_err(ecc->dev, "invalid ecc strength %d\n", + config->strength); + return -EINVAL; + } + + ecc_bit = i; + + if (config->op == ECC_ENCODE) { + /* configure ECC encoder (in bits) */ + enc_sz = config->len << 3; + + reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift); + reg |= (enc_sz << ECC_MS_SHIFT); + writel(reg, ecc->regs + ECC_ENCCNFG); + + if (config->mode != ECC_NFI_MODE) + writel(lower_32_bits(config->addr), + ecc->regs + ECC_ENCDIADDR); + + } else { + /* configure ECC decoder (in bits) */ + dec_sz = (config->len << 3) + + config->strength * ecc->caps->parity_bits; + + reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift); + reg |= (dec_sz << ECC_MS_SHIFT) | DEC_CNFG_CORRECT; + reg |= DEC_EMPTY_EN; + writel(reg, ecc->regs + ECC_DECCNFG); + + if (config->sectors) + ecc->sectors = 1 << (config->sectors - 1); + } + + return 0; +} + +void mtk_ecc_get_stats(struct mtk_ecc *ecc, struct mtk_ecc_stats *stats, + int sectors) +{ + u32 offset, i, err; + u32 bitflips = 0; + + stats->corrected = 0; + stats->failed = 0; + + for (i = 0; i < sectors; i++) { + offset = (i >> 2) << 2; + err = readl(ecc->regs + ECC_DECENUM0 + offset); + err = err >> ((i % 4) * 8); + err &= ecc->caps->err_mask; + if (err == ecc->caps->err_mask) { + /* uncorrectable errors */ + stats->failed++; + continue; + } + + stats->corrected += err; + bitflips = max_t(u32, bitflips, err); + } + + stats->bitflips = bitflips; +} +EXPORT_SYMBOL(mtk_ecc_get_stats); + +void mtk_ecc_release(struct mtk_ecc *ecc) +{ + clk_disable_unprepare(ecc->clk); + put_device(ecc->dev); +} +EXPORT_SYMBOL(mtk_ecc_release); + +static void mtk_ecc_hw_init(struct mtk_ecc *ecc) +{ + mtk_ecc_wait_idle(ecc, ECC_ENCODE); + writew(ECC_OP_DISABLE, ecc->regs + ECC_ENCCON); + + mtk_ecc_wait_idle(ecc, ECC_DECODE); + writel(ECC_OP_DISABLE, ecc->regs + ECC_DECCON); +} + +static struct mtk_ecc *mtk_ecc_get(struct device_node *np) +{ + struct platform_device *pdev; + struct mtk_ecc *ecc; + + pdev = of_find_device_by_node(np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + + ecc = platform_get_drvdata(pdev); + if (!ecc) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + clk_prepare_enable(ecc->clk); + mtk_ecc_hw_init(ecc); + + return ecc; +} + +struct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node) +{ + struct mtk_ecc *ecc = NULL; + struct device_node *np; + + np = of_parse_phandle(of_node, "ecc-engine", 0); + if (np) { + ecc = mtk_ecc_get(np); + of_node_put(np); + } + + return ecc; +} +EXPORT_SYMBOL(of_mtk_ecc_get); + +int mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config) +{ + enum mtk_ecc_operation op = config->op; + u16 reg_val; + int ret; + + ret = mutex_lock_interruptible(&ecc->lock); + if (ret) { + dev_err(ecc->dev, "interrupted when attempting to lock\n"); + return ret; + } + + mtk_ecc_wait_idle(ecc, op); + + ret = mtk_ecc_config(ecc, config); + if (ret) { + mutex_unlock(&ecc->lock); + return ret; + } + + if (config->mode != ECC_NFI_MODE || op != ECC_ENCODE) { + init_completion(&ecc->done); + reg_val = ECC_IRQ_EN; + /* + * For ECC_NFI_MODE, if ecc->caps->pg_irq_sel is 1, then it + * means this chip can only generate one ecc irq during page + * read / write. If is 0, generate one ecc irq each ecc step. + */ + if (ecc->caps->pg_irq_sel && config->mode == ECC_NFI_MODE) + reg_val |= ECC_PG_IRQ_SEL; + if (op == ECC_ENCODE) + writew(reg_val, ecc->regs + + ecc->caps->ecc_regs[ECC_ENCIRQ_EN]); + else + writew(reg_val, ecc->regs + + ecc->caps->ecc_regs[ECC_DECIRQ_EN]); + } + + writew(ECC_OP_ENABLE, ecc->regs + ECC_CTL_REG(op)); + + return 0; +} +EXPORT_SYMBOL(mtk_ecc_enable); + +void mtk_ecc_disable(struct mtk_ecc *ecc) +{ + enum mtk_ecc_operation op = ECC_ENCODE; + + /* find out the running operation */ + if (readw(ecc->regs + ECC_CTL_REG(op)) != ECC_OP_ENABLE) + op = ECC_DECODE; + + /* disable it */ + mtk_ecc_wait_idle(ecc, op); + if (op == ECC_DECODE) { + /* + * Clear decode IRQ status in case there is a timeout to wait + * decode IRQ. + */ + readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]); + writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_EN]); + } else { + writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_EN]); + } + + writew(ECC_OP_DISABLE, ecc->regs + ECC_CTL_REG(op)); + + mutex_unlock(&ecc->lock); +} +EXPORT_SYMBOL(mtk_ecc_disable); + +int mtk_ecc_wait_done(struct mtk_ecc *ecc, enum mtk_ecc_operation op) +{ + int ret; + + ret = wait_for_completion_timeout(&ecc->done, msecs_to_jiffies(500)); + if (!ret) { + dev_err(ecc->dev, "%s timeout - interrupt did not arrive)\n", + (op == ECC_ENCODE) ? "encoder" : "decoder"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL(mtk_ecc_wait_done); + +int mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config, + u8 *data, u32 bytes) +{ + dma_addr_t addr; + u32 len; + int ret; + + addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE); + ret = dma_mapping_error(ecc->dev, addr); + if (ret) { + dev_err(ecc->dev, "dma mapping error\n"); + return -EINVAL; + } + + config->op = ECC_ENCODE; + config->addr = addr; + ret = mtk_ecc_enable(ecc, config); + if (ret) { + dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE); + return ret; + } + + ret = mtk_ecc_wait_done(ecc, ECC_ENCODE); + if (ret) + goto timeout; + + mtk_ecc_wait_idle(ecc, ECC_ENCODE); + + /* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */ + len = (config->strength * ecc->caps->parity_bits + 7) >> 3; + + /* write the parity bytes generated by the ECC back to temp buffer */ + __ioread32_copy(ecc->eccdata, + ecc->regs + ecc->caps->ecc_regs[ECC_ENCPAR00], + round_up(len, 4)); + + /* copy into possibly unaligned OOB region with actual length */ + memcpy(data + bytes, ecc->eccdata, len); +timeout: + + dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE); + mtk_ecc_disable(ecc); + + return ret; +} +EXPORT_SYMBOL(mtk_ecc_encode); + +void mtk_ecc_adjust_strength(struct mtk_ecc *ecc, u32 *p) +{ + const u8 *ecc_strength = ecc->caps->ecc_strength; + int i; + + for (i = 0; i < ecc->caps->num_ecc_strength; i++) { + if (*p <= ecc_strength[i]) { + if (!i) + *p = ecc_strength[i]; + else if (*p != ecc_strength[i]) + *p = ecc_strength[i - 1]; + return; + } + } + + *p = ecc_strength[ecc->caps->num_ecc_strength - 1]; +} +EXPORT_SYMBOL(mtk_ecc_adjust_strength); + +unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc) +{ + return ecc->caps->parity_bits; +} +EXPORT_SYMBOL(mtk_ecc_get_parity_bits); + +static const struct mtk_ecc_caps mtk_ecc_caps_mt2701 = { + .err_mask = 0x3f, + .ecc_strength = ecc_strength_mt2701, + .ecc_regs = mt2701_ecc_regs, + .num_ecc_strength = 20, + .ecc_mode_shift = 5, + .parity_bits = 14, + .pg_irq_sel = 0, +}; + +static const struct mtk_ecc_caps mtk_ecc_caps_mt2712 = { + .err_mask = 0x7f, + .ecc_strength = ecc_strength_mt2712, + .ecc_regs = mt2712_ecc_regs, + .num_ecc_strength = 23, + .ecc_mode_shift = 5, + .parity_bits = 14, + .pg_irq_sel = 1, +}; + +static const struct mtk_ecc_caps mtk_ecc_caps_mt7622 = { + .err_mask = 0x3f, + .ecc_strength = ecc_strength_mt7622, + .ecc_regs = mt7622_ecc_regs, + .num_ecc_strength = 7, + .ecc_mode_shift = 4, + .parity_bits = 13, + .pg_irq_sel = 0, +}; + +static const struct of_device_id mtk_ecc_dt_match[] = { + { + .compatible = "mediatek,mt2701-ecc", + .data = &mtk_ecc_caps_mt2701, + }, { + .compatible = "mediatek,mt2712-ecc", + .data = &mtk_ecc_caps_mt2712, + }, { + .compatible = "mediatek,mt7622-ecc", + .data = &mtk_ecc_caps_mt7622, + }, + {}, +}; + +static int mtk_ecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_ecc *ecc; + u32 max_eccdata_size; + int irq, ret; + + ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); + if (!ecc) + return -ENOMEM; + + ecc->caps = of_device_get_match_data(dev); + + max_eccdata_size = ecc->caps->num_ecc_strength - 1; + max_eccdata_size = ecc->caps->ecc_strength[max_eccdata_size]; + max_eccdata_size = (max_eccdata_size * ecc->caps->parity_bits + 7) >> 3; + max_eccdata_size = round_up(max_eccdata_size, 4); + ecc->eccdata = devm_kzalloc(dev, max_eccdata_size, GFP_KERNEL); + if (!ecc->eccdata) + return -ENOMEM; + + ecc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ecc->regs)) + return PTR_ERR(ecc->regs); + + ecc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ecc->clk)) { + dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk)); + return PTR_ERR(ecc->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "failed to set DMA mask\n"); + return ret; + } + + ret = devm_request_irq(dev, irq, mtk_ecc_irq, 0x0, "mtk-ecc", ecc); + if (ret) { + dev_err(dev, "failed to request irq\n"); + return -EINVAL; + } + + ecc->dev = dev; + mutex_init(&ecc->lock); + platform_set_drvdata(pdev, ecc); + dev_info(dev, "probed\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_ecc_suspend(struct device *dev) +{ + struct mtk_ecc *ecc = dev_get_drvdata(dev); + + clk_disable_unprepare(ecc->clk); + + return 0; +} + +static int mtk_ecc_resume(struct device *dev) +{ + struct mtk_ecc *ecc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(ecc->clk); + if (ret) { + dev_err(dev, "failed to enable clk\n"); + return ret; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mtk_ecc_pm_ops, mtk_ecc_suspend, mtk_ecc_resume); +#endif + +MODULE_DEVICE_TABLE(of, mtk_ecc_dt_match); + +static struct platform_driver mtk_ecc_driver = { + .probe = mtk_ecc_probe, + .driver = { + .name = "mtk-ecc", + .of_match_table = mtk_ecc_dt_match, +#ifdef CONFIG_PM_SLEEP + .pm = &mtk_ecc_pm_ops, +#endif + }, +}; + +module_platform_driver(mtk_ecc_driver); + +MODULE_AUTHOR("Xiaolei Li "); +MODULE_DESCRIPTION("MTK Nand ECC Driver"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 9b078e78f3fa..8b6d7a515445 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -374,6 +374,7 @@ config MTD_NAND_QCOM config MTD_NAND_MTK tristate "MTK NAND controller" + depends on MTD_NAND_ECC_MEDIATEK depends on ARCH_MEDIATEK || COMPILE_TEST depends on HAS_IOMEM help diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index 88a566513c56..fa1d00120310 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -48,7 +48,7 @@ obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o -obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o +obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o diff --git a/drivers/mtd/nand/raw/mtk_ecc.c b/drivers/mtd/nand/raw/mtk_ecc.c deleted file mode 100644 index e7df3dac705e..000000000000 --- a/drivers/mtd/nand/raw/mtk_ecc.c +++ /dev/null @@ -1,593 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 OR MIT -/* - * MTK ECC controller driver. - * Copyright (C) 2016 MediaTek Inc. - * Authors: Xiaolei Li - * Jorge Ramirez-Ortiz - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mtk_ecc.h" - -#define ECC_IDLE_MASK BIT(0) -#define ECC_IRQ_EN BIT(0) -#define ECC_PG_IRQ_SEL BIT(1) -#define ECC_OP_ENABLE (1) -#define ECC_OP_DISABLE (0) - -#define ECC_ENCCON (0x00) -#define ECC_ENCCNFG (0x04) -#define ECC_MS_SHIFT (16) -#define ECC_ENCDIADDR (0x08) -#define ECC_ENCIDLE (0x0C) -#define ECC_DECCON (0x100) -#define ECC_DECCNFG (0x104) -#define DEC_EMPTY_EN BIT(31) -#define DEC_CNFG_CORRECT (0x3 << 12) -#define ECC_DECIDLE (0x10C) -#define ECC_DECENUM0 (0x114) - -#define ECC_TIMEOUT (500000) - -#define ECC_IDLE_REG(op) ((op) == ECC_ENCODE ? ECC_ENCIDLE : ECC_DECIDLE) -#define ECC_CTL_REG(op) ((op) == ECC_ENCODE ? ECC_ENCCON : ECC_DECCON) - -struct mtk_ecc_caps { - u32 err_mask; - const u8 *ecc_strength; - const u32 *ecc_regs; - u8 num_ecc_strength; - u8 ecc_mode_shift; - u32 parity_bits; - int pg_irq_sel; -}; - -struct mtk_ecc { - struct device *dev; - const struct mtk_ecc_caps *caps; - void __iomem *regs; - struct clk *clk; - - struct completion done; - struct mutex lock; - u32 sectors; - - u8 *eccdata; -}; - -/* ecc strength that each IP supports */ -static const u8 ecc_strength_mt2701[] = { - 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, - 40, 44, 48, 52, 56, 60 -}; - -static const u8 ecc_strength_mt2712[] = { - 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, - 40, 44, 48, 52, 56, 60, 68, 72, 80 -}; - -static const u8 ecc_strength_mt7622[] = { - 4, 6, 8, 10, 12, 14, 16 -}; - -enum mtk_ecc_regs { - ECC_ENCPAR00, - ECC_ENCIRQ_EN, - ECC_ENCIRQ_STA, - ECC_DECDONE, - ECC_DECIRQ_EN, - ECC_DECIRQ_STA, -}; - -static int mt2701_ecc_regs[] = { - [ECC_ENCPAR00] = 0x10, - [ECC_ENCIRQ_EN] = 0x80, - [ECC_ENCIRQ_STA] = 0x84, - [ECC_DECDONE] = 0x124, - [ECC_DECIRQ_EN] = 0x200, - [ECC_DECIRQ_STA] = 0x204, -}; - -static int mt2712_ecc_regs[] = { - [ECC_ENCPAR00] = 0x300, - [ECC_ENCIRQ_EN] = 0x80, - [ECC_ENCIRQ_STA] = 0x84, - [ECC_DECDONE] = 0x124, - [ECC_DECIRQ_EN] = 0x200, - [ECC_DECIRQ_STA] = 0x204, -}; - -static int mt7622_ecc_regs[] = { - [ECC_ENCPAR00] = 0x10, - [ECC_ENCIRQ_EN] = 0x30, - [ECC_ENCIRQ_STA] = 0x34, - [ECC_DECDONE] = 0x11c, - [ECC_DECIRQ_EN] = 0x140, - [ECC_DECIRQ_STA] = 0x144, -}; - -static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc, - enum mtk_ecc_operation op) -{ - struct device *dev = ecc->dev; - u32 val; - int ret; - - ret = readl_poll_timeout_atomic(ecc->regs + ECC_IDLE_REG(op), val, - val & ECC_IDLE_MASK, - 10, ECC_TIMEOUT); - if (ret) - dev_warn(dev, "%s NOT idle\n", - op == ECC_ENCODE ? "encoder" : "decoder"); -} - -static irqreturn_t mtk_ecc_irq(int irq, void *id) -{ - struct mtk_ecc *ecc = id; - u32 dec, enc; - - dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]) - & ECC_IRQ_EN; - if (dec) { - dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]); - if (dec & ecc->sectors) { - /* - * Clear decode IRQ status once again to ensure that - * there will be no extra IRQ. - */ - readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]); - ecc->sectors = 0; - complete(&ecc->done); - } else { - return IRQ_HANDLED; - } - } else { - enc = readl(ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_STA]) - & ECC_IRQ_EN; - if (enc) - complete(&ecc->done); - else - return IRQ_NONE; - } - - return IRQ_HANDLED; -} - -static int mtk_ecc_config(struct mtk_ecc *ecc, struct mtk_ecc_config *config) -{ - u32 ecc_bit, dec_sz, enc_sz; - u32 reg, i; - - for (i = 0; i < ecc->caps->num_ecc_strength; i++) { - if (ecc->caps->ecc_strength[i] == config->strength) - break; - } - - if (i == ecc->caps->num_ecc_strength) { - dev_err(ecc->dev, "invalid ecc strength %d\n", - config->strength); - return -EINVAL; - } - - ecc_bit = i; - - if (config->op == ECC_ENCODE) { - /* configure ECC encoder (in bits) */ - enc_sz = config->len << 3; - - reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift); - reg |= (enc_sz << ECC_MS_SHIFT); - writel(reg, ecc->regs + ECC_ENCCNFG); - - if (config->mode != ECC_NFI_MODE) - writel(lower_32_bits(config->addr), - ecc->regs + ECC_ENCDIADDR); - - } else { - /* configure ECC decoder (in bits) */ - dec_sz = (config->len << 3) + - config->strength * ecc->caps->parity_bits; - - reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift); - reg |= (dec_sz << ECC_MS_SHIFT) | DEC_CNFG_CORRECT; - reg |= DEC_EMPTY_EN; - writel(reg, ecc->regs + ECC_DECCNFG); - - if (config->sectors) - ecc->sectors = 1 << (config->sectors - 1); - } - - return 0; -} - -void mtk_ecc_get_stats(struct mtk_ecc *ecc, struct mtk_ecc_stats *stats, - int sectors) -{ - u32 offset, i, err; - u32 bitflips = 0; - - stats->corrected = 0; - stats->failed = 0; - - for (i = 0; i < sectors; i++) { - offset = (i >> 2) << 2; - err = readl(ecc->regs + ECC_DECENUM0 + offset); - err = err >> ((i % 4) * 8); - err &= ecc->caps->err_mask; - if (err == ecc->caps->err_mask) { - /* uncorrectable errors */ - stats->failed++; - continue; - } - - stats->corrected += err; - bitflips = max_t(u32, bitflips, err); - } - - stats->bitflips = bitflips; -} -EXPORT_SYMBOL(mtk_ecc_get_stats); - -void mtk_ecc_release(struct mtk_ecc *ecc) -{ - clk_disable_unprepare(ecc->clk); - put_device(ecc->dev); -} -EXPORT_SYMBOL(mtk_ecc_release); - -static void mtk_ecc_hw_init(struct mtk_ecc *ecc) -{ - mtk_ecc_wait_idle(ecc, ECC_ENCODE); - writew(ECC_OP_DISABLE, ecc->regs + ECC_ENCCON); - - mtk_ecc_wait_idle(ecc, ECC_DECODE); - writel(ECC_OP_DISABLE, ecc->regs + ECC_DECCON); -} - -static struct mtk_ecc *mtk_ecc_get(struct device_node *np) -{ - struct platform_device *pdev; - struct mtk_ecc *ecc; - - pdev = of_find_device_by_node(np); - if (!pdev) - return ERR_PTR(-EPROBE_DEFER); - - ecc = platform_get_drvdata(pdev); - if (!ecc) { - put_device(&pdev->dev); - return ERR_PTR(-EPROBE_DEFER); - } - - clk_prepare_enable(ecc->clk); - mtk_ecc_hw_init(ecc); - - return ecc; -} - -struct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node) -{ - struct mtk_ecc *ecc = NULL; - struct device_node *np; - - np = of_parse_phandle(of_node, "ecc-engine", 0); - if (np) { - ecc = mtk_ecc_get(np); - of_node_put(np); - } - - return ecc; -} -EXPORT_SYMBOL(of_mtk_ecc_get); - -int mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config) -{ - enum mtk_ecc_operation op = config->op; - u16 reg_val; - int ret; - - ret = mutex_lock_interruptible(&ecc->lock); - if (ret) { - dev_err(ecc->dev, "interrupted when attempting to lock\n"); - return ret; - } - - mtk_ecc_wait_idle(ecc, op); - - ret = mtk_ecc_config(ecc, config); - if (ret) { - mutex_unlock(&ecc->lock); - return ret; - } - - if (config->mode != ECC_NFI_MODE || op != ECC_ENCODE) { - init_completion(&ecc->done); - reg_val = ECC_IRQ_EN; - /* - * For ECC_NFI_MODE, if ecc->caps->pg_irq_sel is 1, then it - * means this chip can only generate one ecc irq during page - * read / write. If is 0, generate one ecc irq each ecc step. - */ - if (ecc->caps->pg_irq_sel && config->mode == ECC_NFI_MODE) - reg_val |= ECC_PG_IRQ_SEL; - if (op == ECC_ENCODE) - writew(reg_val, ecc->regs + - ecc->caps->ecc_regs[ECC_ENCIRQ_EN]); - else - writew(reg_val, ecc->regs + - ecc->caps->ecc_regs[ECC_DECIRQ_EN]); - } - - writew(ECC_OP_ENABLE, ecc->regs + ECC_CTL_REG(op)); - - return 0; -} -EXPORT_SYMBOL(mtk_ecc_enable); - -void mtk_ecc_disable(struct mtk_ecc *ecc) -{ - enum mtk_ecc_operation op = ECC_ENCODE; - - /* find out the running operation */ - if (readw(ecc->regs + ECC_CTL_REG(op)) != ECC_OP_ENABLE) - op = ECC_DECODE; - - /* disable it */ - mtk_ecc_wait_idle(ecc, op); - if (op == ECC_DECODE) { - /* - * Clear decode IRQ status in case there is a timeout to wait - * decode IRQ. - */ - readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]); - writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_EN]); - } else { - writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_EN]); - } - - writew(ECC_OP_DISABLE, ecc->regs + ECC_CTL_REG(op)); - - mutex_unlock(&ecc->lock); -} -EXPORT_SYMBOL(mtk_ecc_disable); - -int mtk_ecc_wait_done(struct mtk_ecc *ecc, enum mtk_ecc_operation op) -{ - int ret; - - ret = wait_for_completion_timeout(&ecc->done, msecs_to_jiffies(500)); - if (!ret) { - dev_err(ecc->dev, "%s timeout - interrupt did not arrive)\n", - (op == ECC_ENCODE) ? "encoder" : "decoder"); - return -ETIMEDOUT; - } - - return 0; -} -EXPORT_SYMBOL(mtk_ecc_wait_done); - -int mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config, - u8 *data, u32 bytes) -{ - dma_addr_t addr; - u32 len; - int ret; - - addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE); - ret = dma_mapping_error(ecc->dev, addr); - if (ret) { - dev_err(ecc->dev, "dma mapping error\n"); - return -EINVAL; - } - - config->op = ECC_ENCODE; - config->addr = addr; - ret = mtk_ecc_enable(ecc, config); - if (ret) { - dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE); - return ret; - } - - ret = mtk_ecc_wait_done(ecc, ECC_ENCODE); - if (ret) - goto timeout; - - mtk_ecc_wait_idle(ecc, ECC_ENCODE); - - /* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */ - len = (config->strength * ecc->caps->parity_bits + 7) >> 3; - - /* write the parity bytes generated by the ECC back to temp buffer */ - __ioread32_copy(ecc->eccdata, - ecc->regs + ecc->caps->ecc_regs[ECC_ENCPAR00], - round_up(len, 4)); - - /* copy into possibly unaligned OOB region with actual length */ - memcpy(data + bytes, ecc->eccdata, len); -timeout: - - dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE); - mtk_ecc_disable(ecc); - - return ret; -} -EXPORT_SYMBOL(mtk_ecc_encode); - -void mtk_ecc_adjust_strength(struct mtk_ecc *ecc, u32 *p) -{ - const u8 *ecc_strength = ecc->caps->ecc_strength; - int i; - - for (i = 0; i < ecc->caps->num_ecc_strength; i++) { - if (*p <= ecc_strength[i]) { - if (!i) - *p = ecc_strength[i]; - else if (*p != ecc_strength[i]) - *p = ecc_strength[i - 1]; - return; - } - } - - *p = ecc_strength[ecc->caps->num_ecc_strength - 1]; -} -EXPORT_SYMBOL(mtk_ecc_adjust_strength); - -unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc) -{ - return ecc->caps->parity_bits; -} -EXPORT_SYMBOL(mtk_ecc_get_parity_bits); - -static const struct mtk_ecc_caps mtk_ecc_caps_mt2701 = { - .err_mask = 0x3f, - .ecc_strength = ecc_strength_mt2701, - .ecc_regs = mt2701_ecc_regs, - .num_ecc_strength = 20, - .ecc_mode_shift = 5, - .parity_bits = 14, - .pg_irq_sel = 0, -}; - -static const struct mtk_ecc_caps mtk_ecc_caps_mt2712 = { - .err_mask = 0x7f, - .ecc_strength = ecc_strength_mt2712, - .ecc_regs = mt2712_ecc_regs, - .num_ecc_strength = 23, - .ecc_mode_shift = 5, - .parity_bits = 14, - .pg_irq_sel = 1, -}; - -static const struct mtk_ecc_caps mtk_ecc_caps_mt7622 = { - .err_mask = 0x3f, - .ecc_strength = ecc_strength_mt7622, - .ecc_regs = mt7622_ecc_regs, - .num_ecc_strength = 7, - .ecc_mode_shift = 4, - .parity_bits = 13, - .pg_irq_sel = 0, -}; - -static const struct of_device_id mtk_ecc_dt_match[] = { - { - .compatible = "mediatek,mt2701-ecc", - .data = &mtk_ecc_caps_mt2701, - }, { - .compatible = "mediatek,mt2712-ecc", - .data = &mtk_ecc_caps_mt2712, - }, { - .compatible = "mediatek,mt7622-ecc", - .data = &mtk_ecc_caps_mt7622, - }, - {}, -}; - -static int mtk_ecc_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct mtk_ecc *ecc; - u32 max_eccdata_size; - int irq, ret; - - ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); - if (!ecc) - return -ENOMEM; - - ecc->caps = of_device_get_match_data(dev); - - max_eccdata_size = ecc->caps->num_ecc_strength - 1; - max_eccdata_size = ecc->caps->ecc_strength[max_eccdata_size]; - max_eccdata_size = (max_eccdata_size * ecc->caps->parity_bits + 7) >> 3; - max_eccdata_size = round_up(max_eccdata_size, 4); - ecc->eccdata = devm_kzalloc(dev, max_eccdata_size, GFP_KERNEL); - if (!ecc->eccdata) - return -ENOMEM; - - ecc->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(ecc->regs)) - return PTR_ERR(ecc->regs); - - ecc->clk = devm_clk_get(dev, NULL); - if (IS_ERR(ecc->clk)) { - dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk)); - return PTR_ERR(ecc->clk); - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = dma_set_mask(dev, DMA_BIT_MASK(32)); - if (ret) { - dev_err(dev, "failed to set DMA mask\n"); - return ret; - } - - ret = devm_request_irq(dev, irq, mtk_ecc_irq, 0x0, "mtk-ecc", ecc); - if (ret) { - dev_err(dev, "failed to request irq\n"); - return -EINVAL; - } - - ecc->dev = dev; - mutex_init(&ecc->lock); - platform_set_drvdata(pdev, ecc); - dev_info(dev, "probed\n"); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int mtk_ecc_suspend(struct device *dev) -{ - struct mtk_ecc *ecc = dev_get_drvdata(dev); - - clk_disable_unprepare(ecc->clk); - - return 0; -} - -static int mtk_ecc_resume(struct device *dev) -{ - struct mtk_ecc *ecc = dev_get_drvdata(dev); - int ret; - - ret = clk_prepare_enable(ecc->clk); - if (ret) { - dev_err(dev, "failed to enable clk\n"); - return ret; - } - - return 0; -} - -static SIMPLE_DEV_PM_OPS(mtk_ecc_pm_ops, mtk_ecc_suspend, mtk_ecc_resume); -#endif - -MODULE_DEVICE_TABLE(of, mtk_ecc_dt_match); - -static struct platform_driver mtk_ecc_driver = { - .probe = mtk_ecc_probe, - .driver = { - .name = "mtk-ecc", - .of_match_table = mtk_ecc_dt_match, -#ifdef CONFIG_PM_SLEEP - .pm = &mtk_ecc_pm_ops, -#endif - }, -}; - -module_platform_driver(mtk_ecc_driver); - -MODULE_AUTHOR("Xiaolei Li "); -MODULE_DESCRIPTION("MTK Nand ECC Driver"); -MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/mtd/nand/raw/mtk_ecc.h b/drivers/mtd/nand/raw/mtk_ecc.h deleted file mode 100644 index 0e48c36e6ca0..000000000000 --- a/drivers/mtd/nand/raw/mtk_ecc.h +++ /dev/null @@ -1,47 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 OR MIT */ -/* - * MTK SDG1 ECC controller - * - * Copyright (c) 2016 Mediatek - * Authors: Xiaolei Li - * Jorge Ramirez-Ortiz - */ - -#ifndef __DRIVERS_MTD_NAND_MTK_ECC_H__ -#define __DRIVERS_MTD_NAND_MTK_ECC_H__ - -#include - -enum mtk_ecc_mode {ECC_DMA_MODE = 0, ECC_NFI_MODE = 1}; -enum mtk_ecc_operation {ECC_ENCODE, ECC_DECODE}; - -struct device_node; -struct mtk_ecc; - -struct mtk_ecc_stats { - u32 corrected; - u32 bitflips; - u32 failed; -}; - -struct mtk_ecc_config { - enum mtk_ecc_operation op; - enum mtk_ecc_mode mode; - dma_addr_t addr; - u32 strength; - u32 sectors; - u32 len; -}; - -int mtk_ecc_encode(struct mtk_ecc *, struct mtk_ecc_config *, u8 *, u32); -void mtk_ecc_get_stats(struct mtk_ecc *, struct mtk_ecc_stats *, int); -int mtk_ecc_wait_done(struct mtk_ecc *, enum mtk_ecc_operation); -int mtk_ecc_enable(struct mtk_ecc *, struct mtk_ecc_config *); -void mtk_ecc_disable(struct mtk_ecc *); -void mtk_ecc_adjust_strength(struct mtk_ecc *ecc, u32 *p); -unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc); - -struct mtk_ecc *of_mtk_ecc_get(struct device_node *); -void mtk_ecc_release(struct mtk_ecc *); - -#endif diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index 66f04c693c87..d540454cbbdf 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -17,7 +17,7 @@ #include #include #include -#include "mtk_ecc.h" +#include /* NAND controller register definition */ #define NFI_CNFG (0x00) diff --git a/include/linux/mtd/nand-ecc-mtk.h b/include/linux/mtd/nand-ecc-mtk.h new file mode 100644 index 000000000000..0e48c36e6ca0 --- /dev/null +++ b/include/linux/mtd/nand-ecc-mtk.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * MTK SDG1 ECC controller + * + * Copyright (c) 2016 Mediatek + * Authors: Xiaolei Li + * Jorge Ramirez-Ortiz + */ + +#ifndef __DRIVERS_MTD_NAND_MTK_ECC_H__ +#define __DRIVERS_MTD_NAND_MTK_ECC_H__ + +#include + +enum mtk_ecc_mode {ECC_DMA_MODE = 0, ECC_NFI_MODE = 1}; +enum mtk_ecc_operation {ECC_ENCODE, ECC_DECODE}; + +struct device_node; +struct mtk_ecc; + +struct mtk_ecc_stats { + u32 corrected; + u32 bitflips; + u32 failed; +}; + +struct mtk_ecc_config { + enum mtk_ecc_operation op; + enum mtk_ecc_mode mode; + dma_addr_t addr; + u32 strength; + u32 sectors; + u32 len; +}; + +int mtk_ecc_encode(struct mtk_ecc *, struct mtk_ecc_config *, u8 *, u32); +void mtk_ecc_get_stats(struct mtk_ecc *, struct mtk_ecc_stats *, int); +int mtk_ecc_wait_done(struct mtk_ecc *, enum mtk_ecc_operation); +int mtk_ecc_enable(struct mtk_ecc *, struct mtk_ecc_config *); +void mtk_ecc_disable(struct mtk_ecc *); +void mtk_ecc_adjust_strength(struct mtk_ecc *ecc, u32 *p); +unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc); + +struct mtk_ecc *of_mtk_ecc_get(struct device_node *); +void mtk_ecc_release(struct mtk_ecc *); + +#endif -- cgit v1.2.3 From 0a8e98305f63deaf0a799d5cf5532cc83af035d1 Mon Sep 17 00:00:00 2001 From: Tokunori Ikegami Date: Thu, 24 Mar 2022 02:04:56 +0900 Subject: mtd: cfi_cmdset_0002: Use chip_ready() for write on S29GL064N Since commit dfeae1073583("mtd: cfi_cmdset_0002: Change write buffer to check correct value") buffered writes fail on S29GL064N. This is because, on S29GL064N, reads return 0xFF at the end of DQ polling for write completion, where as, chip_good() check expects actual data written to the last location to be returned post DQ polling completion. Fix is to revert to using chip_good() for S29GL064N which only checks for DQ lines to settle down to determine write completion. Link: https://lore.kernel.org/r/b687c259-6413-26c9-d4c9-b3afa69ea124@pengutronix.de/ Fixes: dfeae1073583("mtd: cfi_cmdset_0002: Change write buffer to check correct value") Cc: stable@vger.kernel.org Signed-off-by: Tokunori Ikegami Acked-by: Vignesh Raghavendra Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20220323170458.5608-3-ikegami.t@gmail.com --- drivers/mtd/chips/cfi_cmdset_0002.c | 42 ++++++++++++++++++++++++++++++------- include/linux/mtd/cfi.h | 1 + 2 files changed, 35 insertions(+), 8 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index 9ccde90dc180..59334530dd46 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -59,6 +59,10 @@ #define CFI_SR_WBASB BIT(3) #define CFI_SR_SLSB BIT(1) +enum cfi_quirks { + CFI_QUIRK_DQ_TRUE_DATA = BIT(0), +}; + static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); #if !FORCE_WORD_WRITE @@ -436,6 +440,15 @@ static void fixup_s29ns512p_sectors(struct mtd_info *mtd) mtd->name); } +static void fixup_quirks(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if (cfi->mfr == CFI_MFR_AMD && cfi->id == 0x0c01) + cfi->quirks |= CFI_QUIRK_DQ_TRUE_DATA; +} + /* Used to fix CFI-Tables of chips without Extended Query Tables */ static struct cfi_fixup cfi_nopri_fixup_table[] = { { CFI_MFR_SST, 0x234a, fixup_sst39vf }, /* SST39VF1602 */ @@ -474,6 +487,7 @@ static struct cfi_fixup cfi_fixup_table[] = { #if !FORCE_WORD_WRITE { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, #endif + { CFI_MFR_ANY, CFI_ID_ANY, fixup_quirks }, { 0, 0, NULL } }; static struct cfi_fixup jedec_fixup_table[] = { @@ -846,6 +860,18 @@ static int __xipram chip_ready(struct map_info *map, struct flchip *chip, return map_word_equal(map, t, *expected); } +static int __xipram chip_good(struct map_info *map, struct flchip *chip, + unsigned long addr, map_word *expected) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word *datum = expected; + + if (cfi->quirks & CFI_QUIRK_DQ_TRUE_DATA) + datum = NULL; + + return chip_ready(map, chip, addr, datum); +} + static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) { DECLARE_WAITQUEUE(wait, current); @@ -1662,11 +1688,11 @@ static int __xipram do_write_oneword_once(struct map_info *map, } /* - * We check "time_after" and "!chip_ready" before checking - * "chip_ready" to avoid the failure due to scheduling. + * We check "time_after" and "!chip_good" before checking + * "chip_good" to avoid the failure due to scheduling. */ if (time_after(jiffies, timeo) && - !chip_ready(map, chip, adr, &datum)) { + !chip_good(map, chip, adr, &datum)) { xip_enable(map, chip, adr); printk(KERN_WARNING "MTD %s(): software timeout\n", __func__); xip_disable(map, chip, adr); @@ -1674,7 +1700,7 @@ static int __xipram do_write_oneword_once(struct map_info *map, break; } - if (chip_ready(map, chip, adr, &datum)) { + if (chip_good(map, chip, adr, &datum)) { if (cfi_check_err_status(map, chip, adr)) ret = -EIO; break; @@ -1942,18 +1968,18 @@ static int __xipram do_write_buffer_wait(struct map_info *map, } /* - * We check "time_after" and "!chip_ready" before checking - * "chip_ready" to avoid the failure due to scheduling. + * We check "time_after" and "!chip_good" before checking + * "chip_good" to avoid the failure due to scheduling. */ if (time_after(jiffies, timeo) && - !chip_ready(map, chip, adr, &datum)) { + !chip_good(map, chip, adr, &datum)) { pr_err("MTD %s(): software timeout, address:0x%.8lx.\n", __func__, adr); ret = -EIO; break; } - if (chip_ready(map, chip, adr, &datum)) { + if (chip_good(map, chip, adr, &datum)) { if (cfi_check_err_status(map, chip, adr)) ret = -EIO; break; diff --git a/include/linux/mtd/cfi.h b/include/linux/mtd/cfi.h index fd1ecb821106..d88bb56c18e2 100644 --- a/include/linux/mtd/cfi.h +++ b/include/linux/mtd/cfi.h @@ -286,6 +286,7 @@ struct cfi_private { map_word sector_erase_cmd; unsigned long chipshift; /* Because they're of the same type */ const char *im_name; /* inter_module name for cmdset_setup */ + unsigned long quirks; struct flchip chips[]; /* per-chip data structure for each chip */ }; -- cgit v1.2.3 From 94f697c5384bd7f9632acca483ba1ef9dd99ea97 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 29 Apr 2022 12:01:53 +0200 Subject: mtd: spi-nor: move spi_nor_write_ear() to winbond module The "Extended Address Register" is winbond specific. If the flash is larger than 16MiB and is used in 3 byte address mode, it is used to set the remaining address bits. Move the write_ear() function, the opcode macros and the spimem op template into the winbond module and rename them accordingly. Signed-off-by: Michael Walle Signed-off-by: Pratyush Yadav Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/r/20220429100153.2338501-1-michael@walle.cc --- drivers/mtd/spi-nor/core.c | 30 ------------------------------ drivers/mtd/spi-nor/core.h | 7 ------- drivers/mtd/spi-nor/winbond.c | 42 +++++++++++++++++++++++++++++++++++++++++- include/linux/mtd/spi-nor.h | 2 -- 4 files changed, 41 insertions(+), 40 deletions(-) (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 6bf3921541b5..94f9847858bd 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -570,36 +570,6 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable) return ret; } -/** - * spi_nor_write_ear() - Write Extended Address Register. - * @nor: pointer to 'struct spi_nor'. - * @ear: value to write to the Extended Address Register. - * - * Return: 0 on success, -errno otherwise. - */ -int spi_nor_write_ear(struct spi_nor *nor, u8 ear) -{ - int ret; - - nor->bouncebuf[0] = ear; - - if (nor->spimem) { - struct spi_mem_op op = SPI_NOR_WREAR_OP(nor->bouncebuf); - - spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); - - ret = spi_mem_exec_op(nor->spimem, &op); - } else { - ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WREAR, - nor->bouncebuf, 1); - } - - if (ret) - dev_dbg(nor->dev, "error %d writing EAR\n", ret); - - return ret; -} - /** * spi_nor_sr_ready() - Query the Status Register to see if the flash is ready * for new commands. diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index 80d4831a4f34..296bfa57f7c1 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -72,12 +72,6 @@ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_DATA_OUT(1, buf, 0)) -#define SPI_NOR_WREAR_OP(buf) \ - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 0), \ - SPI_MEM_OP_NO_ADDR, \ - SPI_MEM_OP_NO_DUMMY, \ - SPI_MEM_OP_DATA_OUT(1, buf, 0)) - #define SPI_NOR_GBULK_OP \ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_GBULK, 0), \ SPI_MEM_OP_NO_ADDR, \ @@ -636,7 +630,6 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor, int spi_nor_write_enable(struct spi_nor *nor); int spi_nor_write_disable(struct spi_nor *nor); int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable); -int spi_nor_write_ear(struct spi_nor *nor, u8 ear); int spi_nor_wait_till_ready(struct spi_nor *nor); int spi_nor_global_block_unlock(struct spi_nor *nor); int spi_nor_lock_and_prep(struct spi_nor *nor); diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index fe80dffc2e70..5a60468d897d 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -8,6 +8,15 @@ #include "core.h" +#define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ +#define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */ + +#define WINBOND_NOR_WREAR_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, buf, 0)) + static int w25q256_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, @@ -129,6 +138,37 @@ static const struct flash_info winbond_nor_parts[] = { SPI_NOR_QUAD_READ) }, }; +/** + * winbond_nor_write_ear() - Write Extended Address Register. + * @nor: pointer to 'struct spi_nor'. + * @ear: value to write to the Extended Address Register. + * + * Return: 0 on success, -errno otherwise. + */ +static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear) +{ + int ret; + + nor->bouncebuf[0] = ear; + + if (nor->spimem) { + struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, + WINBOND_NOR_OP_WREAR, + nor->bouncebuf, 1); + } + + if (ret) + dev_dbg(nor->dev, "error %d writing EAR\n", ret); + + return ret; +} + /** * winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond * flashes. @@ -155,7 +195,7 @@ static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) if (ret) return ret; - ret = spi_nor_write_ear(nor, 0); + ret = winbond_nor_write_ear(nor, 0); if (ret) return ret; diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 5e25a7b75ae2..e505c4a5c530 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -47,8 +47,6 @@ #define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */ #define SPINOR_OP_RDSFDP 0x5a /* Read SFDP */ #define SPINOR_OP_RDCR 0x35 /* Read configuration register */ -#define SPINOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ -#define SPINOR_OP_WREAR 0xc5 /* Write Extended Address Register */ #define SPINOR_OP_SRSTEN 0x66 /* Software Reset Enable */ #define SPINOR_OP_SRST 0x99 /* Software Reset */ #define SPINOR_OP_GBULK 0x98 /* Global Block Unlock */ -- cgit v1.2.3 From 0257be79fc4a16a3252ce80aa13b3640f728c425 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 29 Apr 2022 12:20:18 +0200 Subject: mtd: spi-nor: expose internal parameters via debugfs There is no way to gather all information to verify support for a new flash chip. Also if you want to convert an existing flash chip to the new SFDP parsing, there is not enough information to determine if the flash will work like before. To ease this development, expose internal parameters via the debugfs. Signed-off-by: Michael Walle Signed-off-by: Pratyush Yadav Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/r/20220429102018.2361038-2-michael@walle.cc --- drivers/mtd/spi-nor/Makefile | 1 + drivers/mtd/spi-nor/core.c | 2 + drivers/mtd/spi-nor/core.h | 7 ++ drivers/mtd/spi-nor/debugfs.c | 249 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spi-nor.h | 2 + 5 files changed, 261 insertions(+) create mode 100644 drivers/mtd/spi-nor/debugfs.c (limited to 'include/linux/mtd') diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 6b904e439372..e347b435a038 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -17,6 +17,7 @@ spi-nor-objs += sst.o spi-nor-objs += winbond.o spi-nor-objs += xilinx.o spi-nor-objs += xmc.o +spi-nor-$(CONFIG_DEBUG_FS) += debugfs.o obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_MTD_SPI_NOR) += controllers/ diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 8377bd1842da..502967c76c5f 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3132,6 +3132,8 @@ static int spi_nor_probe(struct spi_mem *spimem) if (ret) return ret; + spi_nor_debugfs_register(nor); + /* * None of the existing parts have > 512B pages, but let's play safe * and add this logic so that if anyone ever adds support for such diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h index ef37e179c615..3f841ec36e56 100644 --- a/drivers/mtd/spi-nor/core.h +++ b/drivers/mtd/spi-nor/core.h @@ -114,6 +114,7 @@ SPI_MEM_OP_NO_ADDR, \ SPI_MEM_OP_NO_DATA) +/* Keep these in sync with the list in debugfs.c */ enum spi_nor_option_flags { SNOR_F_HAS_SR_TB = BIT(0), SNOR_F_NO_OP_CHIP_ERASE = BIT(1), @@ -700,4 +701,10 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd) return container_of(mtd, struct spi_nor, mtd); } +#ifdef CONFIG_DEBUG_FS +void spi_nor_debugfs_register(struct spi_nor *nor); +#else +static inline void spi_nor_debugfs_register(struct spi_nor *nor) {} +#endif + #endif /* __LINUX_MTD_SPI_NOR_INTERNAL_H */ diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c new file mode 100644 index 000000000000..1949905eefea --- /dev/null +++ b/drivers/mtd/spi-nor/debugfs.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include "core.h" + +#define SPI_NOR_DEBUGFS_ROOT "spi-nor" + +#define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name +static const char *const snor_f_names[] = { + SNOR_F_NAME(HAS_SR_TB), + SNOR_F_NAME(NO_OP_CHIP_ERASE), + SNOR_F_NAME(BROKEN_RESET), + SNOR_F_NAME(4B_OPCODES), + SNOR_F_NAME(HAS_4BAIT), + SNOR_F_NAME(HAS_LOCK), + SNOR_F_NAME(HAS_16BIT_SR), + SNOR_F_NAME(NO_READ_CR), + SNOR_F_NAME(HAS_SR_TB_BIT6), + SNOR_F_NAME(HAS_4BIT_BP), + SNOR_F_NAME(HAS_SR_BP3_BIT6), + SNOR_F_NAME(IO_MODE_EN_VOLATILE), + SNOR_F_NAME(SOFT_RESET), + SNOR_F_NAME(SWP_IS_VOLATILE), +}; +#undef SNOR_F_NAME + +static const char *spi_nor_protocol_name(enum spi_nor_protocol proto) +{ + switch (proto) { + case SNOR_PROTO_1_1_1: return "1S-1S-1S"; + case SNOR_PROTO_1_1_2: return "1S-1S-2S"; + case SNOR_PROTO_1_1_4: return "1S-1S-4S"; + case SNOR_PROTO_1_1_8: return "1S-1S-8S"; + case SNOR_PROTO_1_2_2: return "1S-2S-2S"; + case SNOR_PROTO_1_4_4: return "1S-4S-4S"; + case SNOR_PROTO_1_8_8: return "1S-8S-8S"; + case SNOR_PROTO_2_2_2: return "2S-2S-2S"; + case SNOR_PROTO_4_4_4: return "4S-4S-4S"; + case SNOR_PROTO_8_8_8: return "8S-8S-8S"; + case SNOR_PROTO_1_1_1_DTR: return "1D-1D-1D"; + case SNOR_PROTO_1_2_2_DTR: return "1D-2D-2D"; + case SNOR_PROTO_1_4_4_DTR: return "1D-4D-4D"; + case SNOR_PROTO_1_8_8_DTR: return "1D-8D-8D"; + case SNOR_PROTO_8_8_8_DTR: return "8D-8D-8D"; + } + + return ""; +} + +static void spi_nor_print_flags(struct seq_file *s, unsigned long flags, + const char *const *names, int names_len) +{ + bool sep = false; + int i; + + for (i = 0; i < sizeof(flags) * BITS_PER_BYTE; i++) { + if (!(flags & BIT(i))) + continue; + if (sep) + seq_puts(s, " | "); + sep = true; + if (i < names_len && names[i]) + seq_puts(s, names[i]); + else + seq_printf(s, "1<<%d", i); + } +} + +static int spi_nor_params_show(struct seq_file *s, void *data) +{ + struct spi_nor *nor = s->private; + struct spi_nor_flash_parameter *params = nor->params; + struct spi_nor_erase_map *erase_map = ¶ms->erase_map; + struct spi_nor_erase_region *region; + const struct flash_info *info = nor->info; + char buf[16], *str; + int i; + + seq_printf(s, "name\t\t%s\n", info->name); + seq_printf(s, "id\t\t%*phn\n", info->id_len, info->id); + string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(s, "size\t\t%s\n", buf); + seq_printf(s, "write size\t%u\n", params->writesize); + seq_printf(s, "page size\t%u\n", params->page_size); + seq_printf(s, "address width\t%u\n", nor->addr_width); + + seq_puts(s, "flags\t\t"); + spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names)); + seq_puts(s, "\n"); + + seq_puts(s, "\nopcodes\n"); + seq_printf(s, " read\t\t0x%02x\n", nor->read_opcode); + seq_printf(s, " dummy cycles\t%u\n", nor->read_dummy); + seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode); + seq_printf(s, " program\t0x%02x\n", nor->program_opcode); + + switch (nor->cmd_ext_type) { + case SPI_NOR_EXT_NONE: + str = "none"; + break; + case SPI_NOR_EXT_REPEAT: + str = "repeat"; + break; + case SPI_NOR_EXT_INVERT: + str = "invert"; + break; + default: + str = ""; + break; + } + seq_printf(s, " 8D extension\t%s\n", str); + + seq_puts(s, "\nprotocols\n"); + seq_printf(s, " read\t\t%s\n", + spi_nor_protocol_name(nor->read_proto)); + seq_printf(s, " write\t\t%s\n", + spi_nor_protocol_name(nor->write_proto)); + seq_printf(s, " register\t%s\n", + spi_nor_protocol_name(nor->reg_proto)); + + seq_puts(s, "\nerase commands\n"); + for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { + struct spi_nor_erase_type *et = &erase_map->erase_type[i]; + + if (et->size) { + string_get_size(et->size, 1, STRING_UNITS_2, buf, + sizeof(buf)); + seq_printf(s, " %02x (%s) [%d]\n", et->opcode, buf, i); + } + } + + if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { + string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(s, " %02x (%s)\n", SPINOR_OP_CHIP_ERASE, buf); + } + + seq_puts(s, "\nsector map\n"); + seq_puts(s, " region (in hex) | erase mask | flags\n"); + seq_puts(s, " ------------------+------------+----------\n"); + for (region = erase_map->regions; + region; + region = spi_nor_region_next(region)) { + u64 start = region->offset & ~SNOR_ERASE_FLAGS_MASK; + u64 flags = region->offset & SNOR_ERASE_FLAGS_MASK; + u64 end = start + region->size - 1; + + seq_printf(s, " %08llx-%08llx | [%c%c%c%c] | %s\n", + start, end, + flags & BIT(0) ? '0' : ' ', + flags & BIT(1) ? '1' : ' ', + flags & BIT(2) ? '2' : ' ', + flags & BIT(3) ? '3' : ' ', + flags & SNOR_OVERLAID_REGION ? "overlaid" : ""); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(spi_nor_params); + +static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, + struct spi_nor_read_command *cmd) +{ + seq_printf(s, " %s%s\n", spi_nor_protocol_name(cmd->proto), + cap == SNOR_HWCAPS_READ_FAST ? " (fast read)" : ""); + seq_printf(s, " opcode\t0x%02x\n", cmd->opcode); + seq_printf(s, " mode cycles\t%u\n", cmd->num_mode_clocks); + seq_printf(s, " dummy cycles\t%u\n", cmd->num_wait_states); +} + +static void spi_nor_print_pp_cmd(struct seq_file *s, + struct spi_nor_pp_command *cmd) +{ + seq_printf(s, " %s\n", spi_nor_protocol_name(cmd->proto)); + seq_printf(s, " opcode\t0x%02x\n", cmd->opcode); +} + +static int spi_nor_capabilities_show(struct seq_file *s, void *data) +{ + struct spi_nor *nor = s->private; + struct spi_nor_flash_parameter *params = nor->params; + u32 hwcaps = params->hwcaps.mask; + int i, cmd; + + seq_puts(s, "Supported read modes by the flash\n"); + for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { + if (!(hwcaps & BIT(i))) + continue; + + cmd = spi_nor_hwcaps_read2cmd(BIT(i)); + if (cmd < 0) + continue; + + spi_nor_print_read_cmd(s, BIT(i), ¶ms->reads[cmd]); + hwcaps &= ~BIT(i); + } + + seq_puts(s, "\nSupported page program modes by the flash\n"); + for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { + if (!(hwcaps & BIT(i))) + continue; + + cmd = spi_nor_hwcaps_pp2cmd(BIT(i)); + if (cmd < 0) + continue; + + spi_nor_print_pp_cmd(s, ¶ms->page_programs[cmd]); + hwcaps &= ~BIT(i); + } + + if (hwcaps) + seq_printf(s, "\nunknown hwcaps 0x%x\n", hwcaps); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(spi_nor_capabilities); + +static void spi_nor_debugfs_unregister(void *data) +{ + struct spi_nor *nor = data; + + debugfs_remove(nor->debugfs_root); + nor->debugfs_root = NULL; +} + +void spi_nor_debugfs_register(struct spi_nor *nor) +{ + struct dentry *rootdir, *d; + int ret; + + /* Create rootdir once. Will never be deleted again. */ + rootdir = debugfs_lookup(SPI_NOR_DEBUGFS_ROOT, NULL); + if (!rootdir) + rootdir = debugfs_create_dir(SPI_NOR_DEBUGFS_ROOT, NULL); + + ret = devm_add_action(nor->dev, spi_nor_debugfs_unregister, nor); + if (ret) + return; + + d = debugfs_create_dir(dev_name(nor->dev), rootdir); + nor->debugfs_root = d; + + debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops); + debugfs_create_file("capabilities", 0444, d, nor, + &spi_nor_capabilities_fops); +} diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index e505c4a5c530..1ede4c89805a 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -363,6 +363,7 @@ struct spi_nor_flash_parameter; * @write_proto: the SPI protocol for write operations * @reg_proto: the SPI protocol for read_reg/write_reg/erase operations * @sfdp: the SFDP data of the flash + * @debugfs_root: pointer to the debugfs directory * @controller_ops: SPI NOR controller driver specific operations. * @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings. * The structure includes legacy flash parameters and @@ -392,6 +393,7 @@ struct spi_nor { u32 flags; enum spi_nor_cmd_ext cmd_ext_type; struct sfdp *sfdp; + struct dentry *debugfs_root; const struct spi_nor_controller_ops *controller_ops; -- cgit v1.2.3