From 596cf083da34c2007f8ec760c8b077f6f28ee655 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:29:59 +0200 Subject: [PATCH 01/37] mtd: Fallback to ->_read/write_oob() when ->_read/write() is missing Some MTD sublayers/drivers are implementing ->_read/write_oob() and provide dummy wrappers for their ->_read/write() implementations. Let the core handle this case instead of duplicating the logic. Signed-off-by: Boris Brezillon Acked-by: Robert Jarzmik Acked-by: Brian Norris Reviewed-by: Miquel Raynal Tested-by: Ladislav Michl Signed-off-by: Miquel Raynal Reviewed-by: Jagan Teki --- drivers/mtd/mtdcore.c | 31 +++++++++++++++++++-- drivers/mtd/mtdpart.c | 6 ++-- drivers/mtd/nand/nand_base.c | 56 -------------------------------------- drivers/mtd/onenand/onenand_base.c | 2 -- 4 files changed, 33 insertions(+), 62 deletions(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 6217be2..60ad28e 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -937,7 +937,20 @@ int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, * representing the maximum number of bitflips that were corrected on * any one ecc region (if applicable; zero otherwise). */ - ret_code = mtd->_read(mtd, from, len, retlen, buf); + if (mtd->_read) { + ret_code = mtd->_read(mtd, from, len, retlen, buf); + } else if (mtd->_read_oob) { + struct mtd_oob_ops ops = { + .len = len, + .datbuf = buf, + }; + + ret_code = mtd->_read_oob(mtd, from, &ops); + *retlen = ops.retlen; + } else { + return -ENOTSUPP; + } + if (unlikely(ret_code < 0)) return ret_code; if (mtd->ecc_strength == 0) @@ -952,10 +965,24 @@ int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, *retlen = 0; if (to < 0 || to > mtd->size || len > mtd->size - to) return -EINVAL; - if (!mtd->_write || !(mtd->flags & MTD_WRITEABLE)) + if ((!mtd->_write && !mtd->_write_oob) || + !(mtd->flags & MTD_WRITEABLE)) return -EROFS; if (!len) return 0; + + if (!mtd->_write) { + struct mtd_oob_ops ops = { + .len = len, + .datbuf = (u8 *)buf, + }; + int ret; + + ret = mtd->_write_oob(mtd, to, &ops); + *retlen = ops.retlen; + return ret; + } + return mtd->_write(mtd, to, len, retlen, buf); } EXPORT_SYMBOL_GPL(mtd_write); diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index f87c962..ccbb175 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -417,8 +417,10 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, slave->mtd.dev.parent = master->dev.parent; #endif - slave->mtd._read = part_read; - slave->mtd._write = part_write; + if (master->_read) + slave->mtd._read = part_read; + if (master->_write) + slave->mtd._write = part_write; if (master->_panic_write) slave->mtd._panic_write = part_panic_write; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 9094f85..92daebe 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -1864,33 +1864,6 @@ read_retry: } /** - * nand_read - [MTD Interface] MTD compatibility function for nand_do_read_ecc - * @mtd: MTD device structure - * @from: offset to read from - * @len: number of bytes to read - * @retlen: pointer to variable to store the number of read bytes - * @buf: the databuffer to put data - * - * Get hold of the chip and call nand_do_read. - */ -static int nand_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, uint8_t *buf) -{ - struct mtd_oob_ops ops; - int ret; - - nand_get_device(mtd, FL_READING); - memset(&ops, 0, sizeof(ops)); - ops.len = len; - ops.datbuf = buf; - ops.mode = MTD_OPS_PLACE_OOB; - ret = nand_do_read_ops(mtd, from, &ops); - *retlen = ops.retlen; - nand_release_device(mtd); - return ret; -} - -/** * nand_read_oob_std - [REPLACEABLE] the most common OOB data read function * @mtd: mtd info structure * @chip: nand chip info structure @@ -2675,33 +2648,6 @@ static int panic_nand_write(struct mtd_info *mtd, loff_t to, size_t len, } /** - * nand_write - [MTD Interface] NAND write with ECC - * @mtd: MTD device structure - * @to: offset to write to - * @len: number of bytes to write - * @retlen: pointer to variable to store the number of written bytes - * @buf: the data to write - * - * NAND write with ECC. - */ -static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const uint8_t *buf) -{ - struct mtd_oob_ops ops; - int ret; - - nand_get_device(mtd, FL_WRITING); - memset(&ops, 0, sizeof(ops)); - ops.len = len; - ops.datbuf = (uint8_t *)buf; - ops.mode = MTD_OPS_PLACE_OOB; - ret = nand_do_write_ops(mtd, to, &ops); - *retlen = ops.retlen; - nand_release_device(mtd); - return ret; -} - -/** * nand_do_write_oob - [MTD Interface] NAND write out-of-band * @mtd: MTD device structure * @to: offset to write to @@ -4620,8 +4566,6 @@ int nand_scan_tail(struct mtd_info *mtd) mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM : MTD_CAP_NANDFLASH; mtd->_erase = nand_erase; - mtd->_read = nand_read; - mtd->_write = nand_write; mtd->_panic_write = panic_nand_write; mtd->_read_oob = nand_read_oob; mtd->_write_oob = nand_write_oob; diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 86b1640..371e2ec 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -2656,8 +2656,6 @@ int onenand_probe(struct mtd_info *mtd) mtd->flags = MTD_CAP_NANDFLASH; mtd->_erase = onenand_erase; - mtd->_read = onenand_read; - mtd->_write = onenand_write; mtd->_read_oob = onenand_read_oob; mtd->_write_oob = onenand_write_oob; mtd->_sync = onenand_sync; From 5f50d82d8918b711717b4bbd96c6f348eb6e2a2c Mon Sep 17 00:00:00 2001 From: Ezequiel Garcia Date: Thu, 16 Aug 2018 17:30:00 +0200 Subject: [PATCH 02/37] mtd: Uninline mtd_write_oob and move it to mtdcore.c There's no reason for having mtd_write_oob inlined in mtd.h header. Move it to mtdcore.c where it belongs. Signed-off-by: Ezequiel Garcia Acked-by: Boris Brezillon Signed-off-by: Jacek Anaszewski Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.c | 12 ++++++++++++ include/linux/mtd/mtd.h | 12 +----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 60ad28e..ad61406 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1031,6 +1031,18 @@ int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) } EXPORT_SYMBOL_GPL(mtd_read_oob); +int mtd_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + ops->retlen = ops->oobretlen = 0; + if (!mtd->_write_oob) + return -EOPNOTSUPP; + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + return mtd->_write_oob(mtd, to, ops); +} +EXPORT_SYMBOL_GPL(mtd_write_oob); + /** * mtd_ooblayout_ecc - Get the OOB region definition of a specific ECC section * @mtd: MTD device structure diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 823e535..eb39f38 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -351,17 +351,7 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops); - -static inline int mtd_write_oob(struct mtd_info *mtd, loff_t to, - struct mtd_oob_ops *ops) -{ - ops->retlen = ops->oobretlen = 0; - if (!mtd->_write_oob) - return -EOPNOTSUPP; - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - return mtd->_write_oob(mtd, to, ops); -} +int mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops); int mtd_get_fact_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, struct otp_info *buf); From 8fad769f1eab88a2bcf0b714694158968f58715a Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:30:01 +0200 Subject: [PATCH 03/37] mtd: Add sanity checks in mtd_write/read_oob() Unlike what's done in mtd_read/write(), there are no checks to make sure the parameters passed to mtd_read/write_oob() are consistent, which forces implementers of ->_read/write_oob() to do it, which in turn leads to code duplication and possibly errors in the logic. Do general sanity checks, like ops fields consistency and range checking. Signed-off-by: Boris Brezillon Cc: Peter Pan Signed-off-by: Richard Weinberger [Miquel: squashed the fix about the chip's size check] Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index ad61406..9a3efe9 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1010,12 +1010,50 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, } EXPORT_SYMBOL_GPL(mtd_panic_write); +static int mtd_check_oob_ops(struct mtd_info *mtd, loff_t offs, + struct mtd_oob_ops *ops) +{ + /* + * Some users are setting ->datbuf or ->oobbuf to NULL, but are leaving + * ->len or ->ooblen uninitialized. Force ->len and ->ooblen to 0 in + * this case. + */ + if (!ops->datbuf) + ops->len = 0; + + if (!ops->oobbuf) + ops->ooblen = 0; + + if (offs < 0 || offs + ops->len > mtd->size) + return -EINVAL; + + if (ops->ooblen) { + u64 maxooblen; + + if (ops->ooboffs >= mtd_oobavail(mtd, ops)) + return -EINVAL; + + maxooblen = ((mtd_div_by_ws(mtd->size, mtd) - + mtd_div_by_ws(offs, mtd)) * + mtd_oobavail(mtd, ops)) - ops->ooboffs; + if (ops->ooblen > maxooblen) + return -EINVAL; + } + + return 0; +} + int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { int ret_code; ops->retlen = ops->oobretlen = 0; if (!mtd->_read_oob) return -EOPNOTSUPP; + + ret_code = mtd_check_oob_ops(mtd, from, ops); + if (ret_code) + return ret_code; + /* * In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics * similar to mtd->_read(), returning a non-negative integer @@ -1034,11 +1072,18 @@ EXPORT_SYMBOL_GPL(mtd_read_oob); int mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { + int ret; + ops->retlen = ops->oobretlen = 0; if (!mtd->_write_oob) return -EOPNOTSUPP; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; + + ret = mtd_check_oob_ops(mtd, to, ops); + if (ret) + return ret; + return mtd->_write_oob(mtd, to, ops); } EXPORT_SYMBOL_GPL(mtd_write_oob); From ca040d8512f4d93a7eb83f7b8fec8f4b8b1d3192 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:02 +0200 Subject: [PATCH 04/37] mtd: Fallback to ->_read/write() when ->_read/write_oob() is missing Some MTD sublayers/drivers are implementing ->_read/write() and not ->_read/write_oob(). While for NAND devices both are usually valid, for NOR devices, using the _oob variant has no real meaning. But, as the MTD layer is supposed to hide as much as possible the flash complexity to the user, there is no reason to error out while it is just a matter of rewritting things internally. Add a fallback on mtd->_read() (resp. mtd->_write()) when the user calls mtd_read_oob() (resp. mtd_write_oob()) while mtd->_read_oob() (resp. mtd->_write_oob) is not implemented. There is already a fallback on the _oob variant if the former is used. Signed-off-by: Miquel Raynal Reviewed-by: Boris Brezillon --- drivers/mtd/mtdcore.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 9a3efe9..fb1d68d 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1047,20 +1047,27 @@ int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { int ret_code; ops->retlen = ops->oobretlen = 0; - if (!mtd->_read_oob) - return -EOPNOTSUPP; ret_code = mtd_check_oob_ops(mtd, from, ops); if (ret_code) return ret_code; + /* Check the validity of a potential fallback on mtd->_read */ + if (!mtd->_read_oob && (!mtd->_read || ops->oobbuf)) + return -EOPNOTSUPP; + + if (mtd->_read_oob) + ret_code = mtd->_read_oob(mtd, from, ops); + else + ret_code = mtd->_read(mtd, from, ops->len, &ops->retlen, + ops->datbuf); + /* * In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics * similar to mtd->_read(), returning a non-negative integer * representing max bitflips. In other cases, mtd->_read_oob() may * return -EUCLEAN. In all cases, perform similar logic to mtd_read(). */ - ret_code = mtd->_read_oob(mtd, from, ops); if (unlikely(ret_code < 0)) return ret_code; if (mtd->ecc_strength == 0) @@ -1075,8 +1082,7 @@ int mtd_write_oob(struct mtd_info *mtd, loff_t to, int ret; ops->retlen = ops->oobretlen = 0; - if (!mtd->_write_oob) - return -EOPNOTSUPP; + if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; @@ -1084,7 +1090,15 @@ int mtd_write_oob(struct mtd_info *mtd, loff_t to, if (ret) return ret; - return mtd->_write_oob(mtd, to, ops); + /* Check the validity of a potential fallback on mtd->_write */ + if (!mtd->_write_oob && (!mtd->_write || ops->oobbuf)) + return -EOPNOTSUPP; + + if (mtd->_write_oob) + return mtd->_write_oob(mtd, to, ops); + else + return mtd->_write(mtd, to, ops->len, &ops->retlen, + ops->datbuf); } EXPORT_SYMBOL_GPL(mtd_write_oob); From 9bfc3fde9c64320f7165560a64888fad09bc0fee Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Thu, 16 Aug 2018 17:30:03 +0200 Subject: [PATCH 05/37] mtd: add get/set of_node/flash_node helpers We are going to begin using the mtd->dev.of_node field for MTD device nodes, so let's add helpers for it. Also, we'll be making some conversions on spi_nor (and nand_chip eventually) too, so get that ready with their own helpers. Signed-off-by: Brian Norris Reviewed-by: Boris Brezillon Signed-off-by: Miquel Raynal Reviewed-by: Jagan Teki --- include/linux/mtd/mtd.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index eb39f38..272c646 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -21,6 +21,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_DM) +#include +#endif #define MAX_MTD_DEVICES 32 #endif @@ -306,6 +309,31 @@ struct mtd_info { int usecount; }; +#if IS_ENABLED(CONFIG_DM) +static inline void mtd_set_of_node(struct mtd_info *mtd, + const struct device_node *np) +{ + mtd->dev->node.np = np; +} + +static inline const struct device_node *mtd_get_of_node(struct mtd_info *mtd) +{ + return mtd->dev->node.np; +} +#else +struct device_node; + +static inline void mtd_set_of_node(struct mtd_info *mtd, + const struct device_node *np) +{ +} + +static inline const struct device_node *mtd_get_of_node(struct mtd_info *mtd) +{ + return NULL; +} +#endif + int mtd_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *oobecc); int mtd_ooblayout_find_eccregion(struct mtd_info *mtd, int eccbyte, From d02f1d36ec6fe6bfadd77fa71b1df228010ddaa8 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:05 +0200 Subject: [PATCH 06/37] mtd: move definitions to enlarge their range Some helpers might be useful in a future 'mtd' U-Boot command to parse MTD device list. Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.h | 6 ------ include/linux/mtd/mtd.h | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/mtdcore.h b/drivers/mtd/mtdcore.h index 7b03533..1d181a1 100644 --- a/drivers/mtd/mtdcore.h +++ b/drivers/mtd/mtdcore.h @@ -5,7 +5,6 @@ extern struct mutex mtd_table_mutex; -struct mtd_info *__mtd_next_device(int i); int add_mtd_device(struct mtd_info *mtd); int del_mtd_device(struct mtd_info *mtd); int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int); @@ -16,8 +15,3 @@ int parse_mtd_partitions(struct mtd_info *master, const char * const *types, int __init init_mtdchar(void); void __exit cleanup_mtdchar(void); - -#define mtd_for_each_device(mtd) \ - for ((mtd) = __mtd_next_device(0); \ - (mtd) != NULL; \ - (mtd) = __mtd_next_device(mtd->index + 1)) diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 272c646..b8c2c3f 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -533,6 +533,12 @@ int del_mtd_device(struct mtd_info *mtd); int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int); int del_mtd_partitions(struct mtd_info *); +struct mtd_info *__mtd_next_device(int i); +#define mtd_for_each_device(mtd) \ + for ((mtd) = __mtd_next_device(0); \ + (mtd) != NULL; \ + (mtd) = __mtd_next_device(mtd->index + 1)) + int mtd_arg_off(const char *arg, int *idx, loff_t *off, loff_t *size, loff_t *maxsize, int devtype, uint64_t chipsize); int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off, From ce9bdc87436ef91129876c9b16fcf5111eea69aa Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:06 +0200 Subject: [PATCH 07/37] mtd: move all flash categories inside MTD submenu There is no reason to have NAND, SPI flashes and UBI sections outside of the MTD submenu in Kconfig. Signed-off-by: Miquel Raynal Reviewed-by: Jagan Teki --- drivers/mtd/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 41f8883..9341d51 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -59,10 +59,10 @@ config RENESAS_RPC_HF This enables access to Hyperflash memory through the Renesas RCar Gen3 RPC controller. -endmenu - source "drivers/mtd/nand/Kconfig" source "drivers/mtd/spi/Kconfig" source "drivers/mtd/ubi/Kconfig" + +endmenu From a430fa06a4ac50e785fdbfb7f43c3cb14b35619c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:07 +0200 Subject: [PATCH 08/37] mtd: move NAND files into a raw/ subdirectory NAND flavors, like serial and parallel, have a lot in common and would benefit to share code. Let's move raw (parallel) NAND specific code in a raw/ subdirectory, to ease the addition of a core file in nand/ and the introduction of a spi/ subdirectory specific to SPI NANDs. Signed-off-by: Miquel Raynal --- MAINTAINERS | 6 +- Makefile | 2 +- README | 6 +- arch/arm/mach-uniphier/board_late_init.c | 2 +- common/spl/Kconfig | 2 +- common/spl/spl_spi.c | 2 +- doc/README.SPL | 4 +- doc/README.arm-relocation | 2 +- doc/README.nand | 6 +- doc/README.zynq | 2 +- drivers/Makefile | 2 +- drivers/mtd/Makefile | 2 + drivers/mtd/nand/Kconfig | 298 +------------------------- drivers/mtd/nand/Makefile | 75 ------- drivers/mtd/nand/raw/Kconfig | 297 +++++++++++++++++++++++++ drivers/mtd/nand/raw/Makefile | 77 +++++++ drivers/mtd/nand/{ => raw}/am335x_spl_bch.c | 0 drivers/mtd/nand/{ => raw}/arasan_nfc.c | 0 drivers/mtd/nand/{ => raw}/atmel_nand.c | 0 drivers/mtd/nand/{ => raw}/atmel_nand_ecc.h | 0 drivers/mtd/nand/{ => raw}/davinci_nand.c | 2 +- drivers/mtd/nand/{ => raw}/denali.c | 0 drivers/mtd/nand/{ => raw}/denali.h | 0 drivers/mtd/nand/{ => raw}/denali_dt.c | 0 drivers/mtd/nand/{ => raw}/denali_spl.c | 0 drivers/mtd/nand/{ => raw}/fsl_elbc_nand.c | 0 drivers/mtd/nand/{ => raw}/fsl_elbc_spl.c | 0 drivers/mtd/nand/{ => raw}/fsl_ifc_nand.c | 0 drivers/mtd/nand/{ => raw}/fsl_ifc_spl.c | 0 drivers/mtd/nand/{ => raw}/fsl_upm.c | 0 drivers/mtd/nand/{ => raw}/fsmc_nand.c | 0 drivers/mtd/nand/{ => raw}/kb9202_nand.c | 0 drivers/mtd/nand/{ => raw}/kirkwood_nand.c | 0 drivers/mtd/nand/{ => raw}/kmeter1_nand.c | 0 drivers/mtd/nand/{ => raw}/lpc32xx_nand_mlc.c | 0 drivers/mtd/nand/{ => raw}/lpc32xx_nand_slc.c | 0 drivers/mtd/nand/{ => raw}/mxc_nand.c | 0 drivers/mtd/nand/{ => raw}/mxc_nand.h | 0 drivers/mtd/nand/{ => raw}/mxc_nand_spl.c | 0 drivers/mtd/nand/{ => raw}/mxs_nand.c | 0 drivers/mtd/nand/{ => raw}/mxs_nand.h | 0 drivers/mtd/nand/{ => raw}/mxs_nand_dt.c | 0 drivers/mtd/nand/{ => raw}/mxs_nand_spl.c | 0 drivers/mtd/nand/{ => raw}/nand.c | 0 drivers/mtd/nand/{ => raw}/nand_base.c | 0 drivers/mtd/nand/{ => raw}/nand_bbt.c | 0 drivers/mtd/nand/{ => raw}/nand_bch.c | 0 drivers/mtd/nand/{ => raw}/nand_ecc.c | 2 +- drivers/mtd/nand/{ => raw}/nand_ids.c | 0 drivers/mtd/nand/{ => raw}/nand_plat.c | 0 drivers/mtd/nand/{ => raw}/nand_spl_load.c | 0 drivers/mtd/nand/{ => raw}/nand_spl_loaders.c | 0 drivers/mtd/nand/{ => raw}/nand_spl_simple.c | 0 drivers/mtd/nand/{ => raw}/nand_timings.c | 0 drivers/mtd/nand/{ => raw}/nand_util.c | 2 +- drivers/mtd/nand/{ => raw}/omap_elm.c | 0 drivers/mtd/nand/{ => raw}/omap_gpmc.c | 0 drivers/mtd/nand/{ => raw}/pxa3xx_nand.c | 2 +- drivers/mtd/nand/{ => raw}/pxa3xx_nand.h | 0 drivers/mtd/nand/{ => raw}/sunxi_nand.c | 0 drivers/mtd/nand/{ => raw}/sunxi_nand_spl.c | 0 drivers/mtd/nand/{ => raw}/tegra_nand.c | 0 drivers/mtd/nand/{ => raw}/tegra_nand.h | 0 drivers/mtd/nand/{ => raw}/vf610_nfc.c | 0 drivers/mtd/nand/{ => raw}/zynq_nand.c | 0 include/configs/MPC8313ERDB.h | 2 +- 66 files changed, 400 insertions(+), 395 deletions(-) create mode 100644 drivers/mtd/nand/raw/Kconfig create mode 100644 drivers/mtd/nand/raw/Makefile rename drivers/mtd/nand/{ => raw}/am335x_spl_bch.c (100%) rename drivers/mtd/nand/{ => raw}/arasan_nfc.c (100%) rename drivers/mtd/nand/{ => raw}/atmel_nand.c (100%) rename drivers/mtd/nand/{ => raw}/atmel_nand_ecc.h (100%) rename drivers/mtd/nand/{ => raw}/davinci_nand.c (99%) rename drivers/mtd/nand/{ => raw}/denali.c (100%) rename drivers/mtd/nand/{ => raw}/denali.h (100%) rename drivers/mtd/nand/{ => raw}/denali_dt.c (100%) rename drivers/mtd/nand/{ => raw}/denali_spl.c (100%) rename drivers/mtd/nand/{ => raw}/fsl_elbc_nand.c (100%) rename drivers/mtd/nand/{ => raw}/fsl_elbc_spl.c (100%) rename drivers/mtd/nand/{ => raw}/fsl_ifc_nand.c (100%) rename drivers/mtd/nand/{ => raw}/fsl_ifc_spl.c (100%) rename drivers/mtd/nand/{ => raw}/fsl_upm.c (100%) rename drivers/mtd/nand/{ => raw}/fsmc_nand.c (100%) rename drivers/mtd/nand/{ => raw}/kb9202_nand.c (100%) rename drivers/mtd/nand/{ => raw}/kirkwood_nand.c (100%) rename drivers/mtd/nand/{ => raw}/kmeter1_nand.c (100%) rename drivers/mtd/nand/{ => raw}/lpc32xx_nand_mlc.c (100%) rename drivers/mtd/nand/{ => raw}/lpc32xx_nand_slc.c (100%) rename drivers/mtd/nand/{ => raw}/mxc_nand.c (100%) rename drivers/mtd/nand/{ => raw}/mxc_nand.h (100%) rename drivers/mtd/nand/{ => raw}/mxc_nand_spl.c (100%) rename drivers/mtd/nand/{ => raw}/mxs_nand.c (100%) rename drivers/mtd/nand/{ => raw}/mxs_nand.h (100%) rename drivers/mtd/nand/{ => raw}/mxs_nand_dt.c (100%) rename drivers/mtd/nand/{ => raw}/mxs_nand_spl.c (100%) rename drivers/mtd/nand/{ => raw}/nand.c (100%) rename drivers/mtd/nand/{ => raw}/nand_base.c (100%) rename drivers/mtd/nand/{ => raw}/nand_bbt.c (100%) rename drivers/mtd/nand/{ => raw}/nand_bch.c (100%) rename drivers/mtd/nand/{ => raw}/nand_ecc.c (99%) rename drivers/mtd/nand/{ => raw}/nand_ids.c (100%) rename drivers/mtd/nand/{ => raw}/nand_plat.c (100%) rename drivers/mtd/nand/{ => raw}/nand_spl_load.c (100%) rename drivers/mtd/nand/{ => raw}/nand_spl_loaders.c (100%) rename drivers/mtd/nand/{ => raw}/nand_spl_simple.c (100%) rename drivers/mtd/nand/{ => raw}/nand_timings.c (100%) rename drivers/mtd/nand/{ => raw}/nand_util.c (99%) rename drivers/mtd/nand/{ => raw}/omap_elm.c (100%) rename drivers/mtd/nand/{ => raw}/omap_gpmc.c (100%) rename drivers/mtd/nand/{ => raw}/pxa3xx_nand.c (99%) rename drivers/mtd/nand/{ => raw}/pxa3xx_nand.h (100%) rename drivers/mtd/nand/{ => raw}/sunxi_nand.c (100%) rename drivers/mtd/nand/{ => raw}/sunxi_nand_spl.c (100%) rename drivers/mtd/nand/{ => raw}/tegra_nand.c (100%) rename drivers/mtd/nand/{ => raw}/tegra_nand.h (100%) rename drivers/mtd/nand/{ => raw}/vf610_nfc.c (100%) rename drivers/mtd/nand/{ => raw}/zynq_nand.c (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 64fb41e..20a0a5e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -298,7 +298,7 @@ F: drivers/i2c/i2c-cdns.c F: drivers/i2c/muxes/pca954x.c F: drivers/i2c/zynq_i2c.c F: drivers/mmc/zynq_sdhci.c -F: drivers/mtd/nand/zynq_nand.c +F: drivers/mtd/nand/raw/zynq_nand.c F: drivers/net/phy/xilinx_phy.c F: drivers/net/zynq_gem.c F: drivers/serial/serial_zynq.c @@ -322,7 +322,7 @@ F: drivers/i2c/i2c-cdns.c F: drivers/i2c/muxes/pca954x.c F: drivers/i2c/zynq_i2c.c F: drivers/mmc/zynq_sdhci.c -F: drivers/mtd/nand/zynq_nand.c +F: drivers/mtd/nand/raw/zynq_nand.c F: drivers/net/phy/xilinx_phy.c F: drivers/net/zynq_gem.c F: drivers/serial/serial_zynq.c @@ -467,7 +467,7 @@ NAND FLASH #M: Scott Wood S: Orphaned (Since 2018-07) T: git git://git.denx.de/u-boot-nand-flash.git -F: drivers/mtd/nand/ +F: drivers/mtd/nand/raw/ NDS32 M: Macpaul Lin diff --git a/Makefile b/Makefile index e38966e..807e803 100644 --- a/Makefile +++ b/Makefile @@ -689,7 +689,7 @@ libs-y += drivers/dma/ libs-y += drivers/gpio/ libs-y += drivers/i2c/ libs-y += drivers/mtd/ -libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/ +libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/raw/ libs-y += drivers/mtd/onenand/ libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/ libs-y += drivers/mtd/spi/ diff --git a/README b/README index 09822a3..35305a6 100644 --- a/README +++ b/README @@ -3256,8 +3256,8 @@ Low Level (hardware related) configuration options: a 16 bit bus. Not all NAND drivers use this symbol. Example of drivers that use it: - - drivers/mtd/nand/ndfc.c - - drivers/mtd/nand/mxc_nand.c + - drivers/mtd/nand/raw/ndfc.c + - drivers/mtd/nand/raw/mxc_nand.c - CONFIG_SYS_NDFC_EBC0_CFG Sets the EBC0_CFG register for the NDFC. If not defined @@ -3374,7 +3374,7 @@ Low Level (hardware related) configuration options: - CONFIG_SYS_NAND_NO_SUBPAGE_WRITE Option to disable subpage write in NAND driver driver that uses this: - drivers/mtd/nand/davinci_nand.c + drivers/mtd/nand/raw/davinci_nand.c Freescale QE/FMAN Firmware Support: ----------------------------------- diff --git a/arch/arm/mach-uniphier/board_late_init.c b/arch/arm/mach-uniphier/board_late_init.c index 8ffb9a8..1b871c6 100644 --- a/arch/arm/mach-uniphier/board_late_init.c +++ b/arch/arm/mach-uniphier/board_late_init.c @@ -12,7 +12,7 @@ #include #include #include -#include <../drivers/mtd/nand/denali.h> +#include <../drivers/mtd/nand/raw/denali.h> #include "init.h" diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 280496f..18dbc23 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -487,7 +487,7 @@ config SPL_NAND_SUPPORT help Enable support for NAND (Negative AND) flash in SPL. NAND flash can be used to allow SPL to load U-Boot from supported devices. - This enables the drivers in drivers/mtd/nand as part of an SPL + This enables the drivers in drivers/mtd/nand/raw as part of an SPL build. config SPL_NET_SUPPORT diff --git a/common/spl/spl_spi.c b/common/spl/spl_spi.c index ba60a3a..0016d49 100644 --- a/common/spl/spl_spi.c +++ b/common/spl/spl_spi.c @@ -2,7 +2,7 @@ /* * Copyright (C) 2011 OMICRON electronics GmbH * - * based on drivers/mtd/nand/nand_spl_load.c + * based on drivers/mtd/nand/raw/nand_spl_load.c * * Copyright (C) 2011 * Heiko Schocher, DENX Software Engineering, hs@denx.de. diff --git a/doc/README.SPL b/doc/README.SPL index 3ba313c..fc1ca1a 100644 --- a/doc/README.SPL +++ b/doc/README.SPL @@ -57,11 +57,11 @@ CONFIG_SPL_FAT_SUPPORT (fs/fat/libfat.o) CONFIG_SPL_EXT_SUPPORT CONFIG_SPL_LIBGENERIC_SUPPORT (lib/libgeneric.o) CONFIG_SPL_POWER_SUPPORT (drivers/power/libpower.o) -CONFIG_SPL_NAND_SUPPORT (drivers/mtd/nand/libnand.o) +CONFIG_SPL_NAND_SUPPORT (drivers/mtd/nand/raw/libnand.o) CONFIG_SPL_DRIVERS_MISC_SUPPORT (drivers/misc) CONFIG_SPL_DMA_SUPPORT (drivers/dma/libdma.o) CONFIG_SPL_POST_MEM_SUPPORT (post/drivers/memory.o) -CONFIG_SPL_NAND_LOAD (drivers/mtd/nand/nand_spl_load.o) +CONFIG_SPL_NAND_LOAD (drivers/mtd/nand/raw/nand_spl_load.o) CONFIG_SPL_SPI_LOAD (drivers/mtd/spi/spi_spl_load.o) CONFIG_SPL_RAM_DEVICE (common/spl/spl.c) CONFIG_SPL_WATCHDOG_SUPPORT (drivers/watchdog/libwatchdog.o) diff --git a/doc/README.arm-relocation b/doc/README.arm-relocation index 645b374..d2a7e81 100644 --- a/doc/README.arm-relocation +++ b/doc/README.arm-relocation @@ -84,7 +84,7 @@ Relocation with SPL (example for the tx25 booting from NAND Flash): - cpu copies the first page from NAND to 0xbb000000 (IMX_NFC_BASE) and start with code execution on this address. -- The First page contains u-boot code from drivers/mtd/nand/mxc_nand_spl.c +- The First page contains u-boot code from drivers/mtd/nand/raw/mxc_nand_spl.c which inits the dram, cpu registers, reloacte itself to CONFIG_SPL_TEXT_BASE and loads the "real" u-boot to CONFIG_SYS_NAND_U_BOOT_DST and starts execution @CONFIG_SYS_NAND_U_BOOT_START diff --git a/doc/README.nand b/doc/README.nand index cda11b4..ec461b2 100644 --- a/doc/README.nand +++ b/doc/README.nand @@ -116,7 +116,7 @@ Configuration Options: The maximum number of NAND chips per device to be supported. CONFIG_SYS_NAND_SELF_INIT - Traditionally, glue code in drivers/mtd/nand/nand.c has driven + Traditionally, glue code in drivers/mtd/nand/raw/nand.c has driven the initialization process -- it provides the mtd and nand structs, calls a board init function for a specific device, calls nand_scan(), and registers with mtd. @@ -125,7 +125,7 @@ Configuration Options: run code between nand_scan_ident() and nand_scan_tail(), or other deviations from the "normal" flow. - If a board defines CONFIG_SYS_NAND_SELF_INIT, drivers/mtd/nand/nand.c + If a board defines CONFIG_SYS_NAND_SELF_INIT, drivers/mtd/nand/raw/nand.c will make one call to board_nand_init(), with no arguments. That function is responsible for calling a driver init function for each NAND device on the board, that performs all initialization @@ -280,7 +280,7 @@ NOTE: ===== The Disk On Chip driver is currently broken and has been for some time. -There is a driver in drivers/mtd/nand, taken from Linux, that works with +There is a driver in drivers/mtd/nand/raw, taken from Linux, that works with the current NAND system but has not yet been adapted to the u-boot environment. diff --git a/doc/README.zynq b/doc/README.zynq index 5b0672c..da977b2 100644 --- a/doc/README.zynq +++ b/doc/README.zynq @@ -63,7 +63,7 @@ bootmode strings at runtime. spi - drivers/spi/zynq_spi.c qspi - drivers/spi/zynq_qspi.c i2c - drivers/i2c/zynq_i2c.c - nand - drivers/mtd/nand/zynq_nand.c + nand - drivers/mtd/nand/raw/zynq_nand.c - Done proper cleanups on board configurations - Added basic FDT support for zynq boards - d-cache support for zynq_gem.c diff --git a/drivers/Makefile b/drivers/Makefile index 23ea609..fe8d40d 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_$(SPL_TPL_)DRIVERS_MISC_SUPPORT) += misc/ sysreset/ firmware/ obj-$(CONFIG_$(SPL_TPL_)I2C_SUPPORT) += i2c/ obj-$(CONFIG_$(SPL_TPL_)LED) += led/ obj-$(CONFIG_$(SPL_TPL_)MMC_SUPPORT) += mmc/ -obj-$(CONFIG_$(SPL_TPL_)NAND_SUPPORT) += mtd/nand/ +obj-$(CONFIG_$(SPL_TPL_)NAND_SUPPORT) += mtd/nand/raw/ obj-$(CONFIG_$(SPL_TPL_)PHY) += phy/ obj-$(CONFIG_$(SPL_TPL_)PINCTRL) += pinctrl/ obj-$(CONFIG_$(SPL_TPL_)RAM) += ram/ diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 9cec065..cdf5152 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -18,3 +18,5 @@ obj-$(CONFIG_FLASH_PIC32) += pic32_flash.o obj-$(CONFIG_ST_SMI) += st_smi.o obj-$(CONFIG_STM32_FLASH) += stm32_flash.o obj-$(CONFIG_RENESAS_RPC_HF) += renesas_rpc_hf.o + +obj-y += nand/ diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 1e4ea7b..6d53734 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -1,297 +1 @@ - -menuconfig NAND - bool "NAND Device Support" -if NAND - -config SYS_NAND_SELF_INIT - bool - help - This option, if enabled, provides more flexible and linux-like - NAND initialization process. - -config NAND_ATMEL - bool "Support Atmel NAND controller" - imply SYS_NAND_USE_FLASH_BBT - help - Enable this driver for NAND flash platforms using an Atmel NAND - controller. - -config NAND_DAVINCI - bool "Support TI Davinci NAND controller" - help - Enable this driver for NAND flash controllers available in TI Davinci - and Keystone2 platforms - -config NAND_DENALI - bool - select SYS_NAND_SELF_INIT - imply CMD_NAND - -config NAND_DENALI_DT - bool "Support Denali NAND controller as a DT device" - select NAND_DENALI - depends on OF_CONTROL && DM - help - Enable the driver for NAND flash on platforms using a Denali NAND - controller as a DT device. - -config NAND_DENALI_SPARE_AREA_SKIP_BYTES - int "Number of bytes skipped in OOB area" - depends on NAND_DENALI - range 0 63 - help - This option specifies the number of bytes to skip from the beginning - of OOB area before last ECC sector data starts. This is potentially - used to preserve the bad block marker in the OOB area. - -config NAND_LPC32XX_SLC - bool "Support LPC32XX_SLC controller" - help - Enable the LPC32XX SLC NAND controller. - -config NAND_OMAP_GPMC - bool "Support OMAP GPMC NAND controller" - depends on ARCH_OMAP2PLUS - help - Enables omap_gpmc.c driver for OMAPx and AMxxxx platforms. - GPMC controller is used for parallel NAND flash devices, and can - do ECC calculation (not ECC error detection) for HAM1, BCH4, BCH8 - and BCH16 ECC algorithms. - -config NAND_OMAP_GPMC_PREFETCH - bool "Enable GPMC Prefetch" - depends on NAND_OMAP_GPMC - default y - help - On OMAP platforms that use the GPMC controller - (CONFIG_NAND_OMAP_GPMC_PREFETCH), this options enables the code that - uses the prefetch mode to speed up read operations. - -config NAND_OMAP_ELM - bool "Enable ELM driver for OMAPxx and AMxx platforms." - depends on NAND_OMAP_GPMC && !OMAP34XX - help - ELM controller is used for ECC error detection (not ECC calculation) - of BCH4, BCH8 and BCH16 ECC algorithms. - Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine, - thus such SoC platforms need to depend on software library for ECC error - detection. However ECC calculation on such plaforms would still be - done by GPMC controller. - -config NAND_VF610_NFC - bool "Support for Freescale NFC for VF610" - select SYS_NAND_SELF_INIT - imply CMD_NAND - help - Enables support for NAND Flash Controller on some Freescale - processors like the VF610, MCF54418 or Kinetis K70. - The driver supports a maximum 2k page size. The driver - currently does not support hardware ECC. - -choice - prompt "Hardware ECC strength" - depends on NAND_VF610_NFC - default SYS_NAND_VF610_NFC_45_ECC_BYTES - help - Select the ECC strength used in the hardware BCH ECC block. - -config SYS_NAND_VF610_NFC_45_ECC_BYTES - bool "24-error correction (45 ECC bytes)" - -config SYS_NAND_VF610_NFC_60_ECC_BYTES - bool "32-error correction (60 ECC bytes)" - -endchoice - -config NAND_PXA3XX - bool "Support for NAND on PXA3xx and Armada 370/XP/38x" - select SYS_NAND_SELF_INIT - imply CMD_NAND - help - This enables the driver for the NAND flash device found on - PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2). - -config NAND_SUNXI - bool "Support for NAND on Allwinner SoCs" - default ARCH_SUNXI - depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I - select SYS_NAND_SELF_INIT - select SYS_NAND_U_BOOT_LOCATIONS - select SPL_NAND_SUPPORT - imply CMD_NAND - ---help--- - Enable support for NAND. This option enables the standard and - SPL drivers. - The SPL driver only supports reading from the NAND using DMA - transfers. - -if NAND_SUNXI - -config NAND_SUNXI_SPL_ECC_STRENGTH - int "Allwinner NAND SPL ECC Strength" - default 64 - -config NAND_SUNXI_SPL_ECC_SIZE - int "Allwinner NAND SPL ECC Step Size" - default 1024 - -config NAND_SUNXI_SPL_USABLE_PAGE_SIZE - int "Allwinner NAND SPL Usable Page Size" - default 1024 - -endif - -config NAND_ARASAN - bool "Configure Arasan Nand" - select SYS_NAND_SELF_INIT - imply CMD_NAND - help - This enables Nand driver support for Arasan nand flash - controller. This uses the hardware ECC for read and - write operations. - -config NAND_MXC - bool "MXC NAND support" - depends on CPU_ARM926EJS || CPU_ARM1136 || MX5 - imply CMD_NAND - help - This enables the NAND driver for the NAND flash controller on the - i.MX27 / i.MX31 / i.MX5 rocessors. - -config NAND_MXS - bool "MXS NAND support" - depends on MX23 || MX28 || MX6 || MX7 - select SYS_NAND_SELF_INIT - imply CMD_NAND - select APBH_DMA - select APBH_DMA_BURST if ARCH_MX6 || ARCH_MX7 - select APBH_DMA_BURST8 if ARCH_MX6 || ARCH_MX7 - help - This enables NAND driver for the NAND flash controller on the - MXS processors. - -if NAND_MXS - -config NAND_MXS_DT - bool "Support MXS NAND controller as a DT device" - depends on OF_CONTROL && MTD - help - Enable the driver for MXS NAND flash on platforms using - device tree. - -config NAND_MXS_USE_MINIMUM_ECC - bool "Use minimum ECC strength supported by the controller" - default false - -endif - -config NAND_ZYNQ - bool "Support for Zynq Nand controller" - select SYS_NAND_SELF_INIT - imply CMD_NAND - help - This enables Nand driver support for Nand flash controller - found on Zynq SoC. - -config NAND_ZYNQ_USE_BOOTLOADER1_TIMINGS - bool "Enable use of 1st stage bootloader timing for NAND" - depends on NAND_ZYNQ - help - This flag prevent U-boot reconfigure NAND flash controller and reuse - the NAND timing from 1st stage bootloader. - -comment "Generic NAND options" - -config SYS_NAND_BLOCK_SIZE - hex "NAND chip eraseblock size" - depends on ARCH_SUNXI - help - Number of data bytes in one eraseblock for the NAND chip on the - board. This is the multiple of NAND_PAGE_SIZE and the number of - pages. - -config SYS_NAND_PAGE_SIZE - hex "NAND chip page size" - depends on ARCH_SUNXI - help - Number of data bytes in one page for the NAND chip on the - board, not including the OOB area. - -config SYS_NAND_OOBSIZE - hex "NAND chip OOB size" - depends on ARCH_SUNXI - help - Number of bytes in the Out-Of-Band area for the NAND chip on - the board. - -# Enhance depends when converting drivers to Kconfig which use this config -# option (mxc_nand, ndfc, omap_gpmc). -config SYS_NAND_BUSWIDTH_16BIT - bool "Use 16-bit NAND interface" - depends on NAND_VF610_NFC || NAND_OMAP_GPMC || NAND_MXC || ARCH_DAVINCI - help - Indicates that NAND device has 16-bit wide data-bus. In absence of this - config, bus-width of NAND device is assumed to be either 8-bit and later - determined by reading ONFI params. - Above config is useful when NAND device's bus-width information cannot - be determined from on-chip ONFI params, like in following scenarios: - - SPL boot does not support reading of ONFI parameters. This is done to - keep SPL code foot-print small. - - In current U-Boot flow using nand_init(), driver initialization - happens in board_nand_init() which is called before any device probe - (nand_scan_ident + nand_scan_tail), thus device's ONFI parameters are - not available while configuring controller. So a static CONFIG_NAND_xx - is needed to know the device's bus-width in advance. - -if SPL - -config SYS_NAND_U_BOOT_LOCATIONS - bool "Define U-boot binaries locations in NAND" - help - Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig. - This option should not be enabled when compiling U-boot for boards - defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/.h - file. - -config SYS_NAND_U_BOOT_OFFS - hex "Location in NAND to read U-Boot from" - default 0x800000 if NAND_SUNXI - depends on SYS_NAND_U_BOOT_LOCATIONS - help - Set the offset from the start of the nand where u-boot should be - loaded from. - -config SYS_NAND_U_BOOT_OFFS_REDUND - hex "Location in NAND to read U-Boot from" - default SYS_NAND_U_BOOT_OFFS - depends on SYS_NAND_U_BOOT_LOCATIONS - help - Set the offset from the start of the nand where the redundant u-boot - should be loaded from. - -config SPL_NAND_AM33XX_BCH - bool "Enables SPL-NAND driver which supports ELM based" - depends on NAND_OMAP_GPMC && !OMAP34XX - default y - help - Hardware ECC correction. This is useful for platforms which have ELM - hardware engine and use NAND boot mode. - Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine, - so those platforms should use CONFIG_SPL_NAND_SIMPLE for enabling - SPL-NAND driver with software ECC correction support. - -config SPL_NAND_DENALI - bool "Support Denali NAND controller for SPL" - help - This is a small implementation of the Denali NAND controller - for use on SPL. - -config SPL_NAND_SIMPLE - bool "Use simple SPL NAND driver" - depends on !SPL_NAND_AM33XX_BCH - help - Support for NAND boot using simple NAND drivers that - expose the cmd_ctrl() interface. -endif - -endif # if NAND +source "drivers/mtd/nand/raw/Kconfig" diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index c61e3f3..69f40d1 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -1,77 +1,2 @@ # SPDX-License-Identifier: GPL-2.0+ -# -# (C) Copyright 2006 -# Wolfgang Denk, DENX Software Engineering, wd@denx.de. -ifdef CONFIG_SPL_BUILD - -ifdef CONFIG_SPL_NAND_DRIVERS -NORMAL_DRIVERS=y -endif - -obj-$(CONFIG_SPL_NAND_AM33XX_BCH) += am335x_spl_bch.o -obj-$(CONFIG_SPL_NAND_DENALI) += denali_spl.o -obj-$(CONFIG_SPL_NAND_SIMPLE) += nand_spl_simple.o -obj-$(CONFIG_SPL_NAND_LOAD) += nand_spl_load.o -obj-$(CONFIG_SPL_NAND_ECC) += nand_ecc.o -obj-$(CONFIG_SPL_NAND_BASE) += nand_base.o -obj-$(CONFIG_SPL_NAND_IDENT) += nand_ids.o nand_timings.o -obj-$(CONFIG_SPL_NAND_INIT) += nand.o -ifeq ($(CONFIG_SPL_ENV_SUPPORT),y) -obj-$(CONFIG_ENV_IS_IN_NAND) += nand_util.o -endif - -else # not spl - -NORMAL_DRIVERS=y - -obj-y += nand.o -obj-y += nand_bbt.o -obj-y += nand_ids.o -obj-y += nand_util.o -obj-y += nand_ecc.o -obj-y += nand_base.o -obj-y += nand_timings.o - -endif # not spl - -ifdef NORMAL_DRIVERS - -obj-$(CONFIG_NAND_ECC_BCH) += nand_bch.o - -obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o -obj-$(CONFIG_NAND_ARASAN) += arasan_nfc.o -obj-$(CONFIG_NAND_DAVINCI) += davinci_nand.o -obj-$(CONFIG_NAND_DENALI) += denali.o -obj-$(CONFIG_NAND_DENALI_DT) += denali_dt.o -obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o -obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_nand.o -obj-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o -obj-$(CONFIG_NAND_FSMC) += fsmc_nand.o -obj-$(CONFIG_NAND_KB9202) += kb9202_nand.o -obj-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o -obj-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o -obj-$(CONFIG_NAND_LPC32XX_MLC) += lpc32xx_nand_mlc.o -obj-$(CONFIG_NAND_LPC32XX_SLC) += lpc32xx_nand_slc.o -obj-$(CONFIG_NAND_VF610_NFC) += vf610_nfc.o -obj-$(CONFIG_NAND_MXC) += mxc_nand.o -obj-$(CONFIG_NAND_MXS) += mxs_nand.o -obj-$(CONFIG_NAND_MXS_DT) += mxs_nand_dt.o -obj-$(CONFIG_NAND_PXA3XX) += pxa3xx_nand.o -obj-$(CONFIG_NAND_SPEAR) += spr_nand.o -obj-$(CONFIG_TEGRA_NAND) += tegra_nand.o -obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o -obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o -obj-$(CONFIG_NAND_PLAT) += nand_plat.o -obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o -obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o - -else # minimal SPL drivers - -obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_spl.o -obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_spl.o -obj-$(CONFIG_NAND_MXC) += mxc_nand_spl.o -obj-$(CONFIG_NAND_MXS) += mxs_nand_spl.o mxs_nand.o -obj-$(CONFIG_NAND_SUNXI) += sunxi_nand_spl.o - -endif # drivers diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig new file mode 100644 index 0000000..1e4ea7b --- /dev/null +++ b/drivers/mtd/nand/raw/Kconfig @@ -0,0 +1,297 @@ + +menuconfig NAND + bool "NAND Device Support" +if NAND + +config SYS_NAND_SELF_INIT + bool + help + This option, if enabled, provides more flexible and linux-like + NAND initialization process. + +config NAND_ATMEL + bool "Support Atmel NAND controller" + imply SYS_NAND_USE_FLASH_BBT + help + Enable this driver for NAND flash platforms using an Atmel NAND + controller. + +config NAND_DAVINCI + bool "Support TI Davinci NAND controller" + help + Enable this driver for NAND flash controllers available in TI Davinci + and Keystone2 platforms + +config NAND_DENALI + bool + select SYS_NAND_SELF_INIT + imply CMD_NAND + +config NAND_DENALI_DT + bool "Support Denali NAND controller as a DT device" + select NAND_DENALI + depends on OF_CONTROL && DM + help + Enable the driver for NAND flash on platforms using a Denali NAND + controller as a DT device. + +config NAND_DENALI_SPARE_AREA_SKIP_BYTES + int "Number of bytes skipped in OOB area" + depends on NAND_DENALI + range 0 63 + help + This option specifies the number of bytes to skip from the beginning + of OOB area before last ECC sector data starts. This is potentially + used to preserve the bad block marker in the OOB area. + +config NAND_LPC32XX_SLC + bool "Support LPC32XX_SLC controller" + help + Enable the LPC32XX SLC NAND controller. + +config NAND_OMAP_GPMC + bool "Support OMAP GPMC NAND controller" + depends on ARCH_OMAP2PLUS + help + Enables omap_gpmc.c driver for OMAPx and AMxxxx platforms. + GPMC controller is used for parallel NAND flash devices, and can + do ECC calculation (not ECC error detection) for HAM1, BCH4, BCH8 + and BCH16 ECC algorithms. + +config NAND_OMAP_GPMC_PREFETCH + bool "Enable GPMC Prefetch" + depends on NAND_OMAP_GPMC + default y + help + On OMAP platforms that use the GPMC controller + (CONFIG_NAND_OMAP_GPMC_PREFETCH), this options enables the code that + uses the prefetch mode to speed up read operations. + +config NAND_OMAP_ELM + bool "Enable ELM driver for OMAPxx and AMxx platforms." + depends on NAND_OMAP_GPMC && !OMAP34XX + help + ELM controller is used for ECC error detection (not ECC calculation) + of BCH4, BCH8 and BCH16 ECC algorithms. + Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine, + thus such SoC platforms need to depend on software library for ECC error + detection. However ECC calculation on such plaforms would still be + done by GPMC controller. + +config NAND_VF610_NFC + bool "Support for Freescale NFC for VF610" + select SYS_NAND_SELF_INIT + imply CMD_NAND + help + Enables support for NAND Flash Controller on some Freescale + processors like the VF610, MCF54418 or Kinetis K70. + The driver supports a maximum 2k page size. The driver + currently does not support hardware ECC. + +choice + prompt "Hardware ECC strength" + depends on NAND_VF610_NFC + default SYS_NAND_VF610_NFC_45_ECC_BYTES + help + Select the ECC strength used in the hardware BCH ECC block. + +config SYS_NAND_VF610_NFC_45_ECC_BYTES + bool "24-error correction (45 ECC bytes)" + +config SYS_NAND_VF610_NFC_60_ECC_BYTES + bool "32-error correction (60 ECC bytes)" + +endchoice + +config NAND_PXA3XX + bool "Support for NAND on PXA3xx and Armada 370/XP/38x" + select SYS_NAND_SELF_INIT + imply CMD_NAND + help + This enables the driver for the NAND flash device found on + PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2). + +config NAND_SUNXI + bool "Support for NAND on Allwinner SoCs" + default ARCH_SUNXI + depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I + select SYS_NAND_SELF_INIT + select SYS_NAND_U_BOOT_LOCATIONS + select SPL_NAND_SUPPORT + imply CMD_NAND + ---help--- + Enable support for NAND. This option enables the standard and + SPL drivers. + The SPL driver only supports reading from the NAND using DMA + transfers. + +if NAND_SUNXI + +config NAND_SUNXI_SPL_ECC_STRENGTH + int "Allwinner NAND SPL ECC Strength" + default 64 + +config NAND_SUNXI_SPL_ECC_SIZE + int "Allwinner NAND SPL ECC Step Size" + default 1024 + +config NAND_SUNXI_SPL_USABLE_PAGE_SIZE + int "Allwinner NAND SPL Usable Page Size" + default 1024 + +endif + +config NAND_ARASAN + bool "Configure Arasan Nand" + select SYS_NAND_SELF_INIT + imply CMD_NAND + help + This enables Nand driver support for Arasan nand flash + controller. This uses the hardware ECC for read and + write operations. + +config NAND_MXC + bool "MXC NAND support" + depends on CPU_ARM926EJS || CPU_ARM1136 || MX5 + imply CMD_NAND + help + This enables the NAND driver for the NAND flash controller on the + i.MX27 / i.MX31 / i.MX5 rocessors. + +config NAND_MXS + bool "MXS NAND support" + depends on MX23 || MX28 || MX6 || MX7 + select SYS_NAND_SELF_INIT + imply CMD_NAND + select APBH_DMA + select APBH_DMA_BURST if ARCH_MX6 || ARCH_MX7 + select APBH_DMA_BURST8 if ARCH_MX6 || ARCH_MX7 + help + This enables NAND driver for the NAND flash controller on the + MXS processors. + +if NAND_MXS + +config NAND_MXS_DT + bool "Support MXS NAND controller as a DT device" + depends on OF_CONTROL && MTD + help + Enable the driver for MXS NAND flash on platforms using + device tree. + +config NAND_MXS_USE_MINIMUM_ECC + bool "Use minimum ECC strength supported by the controller" + default false + +endif + +config NAND_ZYNQ + bool "Support for Zynq Nand controller" + select SYS_NAND_SELF_INIT + imply CMD_NAND + help + This enables Nand driver support for Nand flash controller + found on Zynq SoC. + +config NAND_ZYNQ_USE_BOOTLOADER1_TIMINGS + bool "Enable use of 1st stage bootloader timing for NAND" + depends on NAND_ZYNQ + help + This flag prevent U-boot reconfigure NAND flash controller and reuse + the NAND timing from 1st stage bootloader. + +comment "Generic NAND options" + +config SYS_NAND_BLOCK_SIZE + hex "NAND chip eraseblock size" + depends on ARCH_SUNXI + help + Number of data bytes in one eraseblock for the NAND chip on the + board. This is the multiple of NAND_PAGE_SIZE and the number of + pages. + +config SYS_NAND_PAGE_SIZE + hex "NAND chip page size" + depends on ARCH_SUNXI + help + Number of data bytes in one page for the NAND chip on the + board, not including the OOB area. + +config SYS_NAND_OOBSIZE + hex "NAND chip OOB size" + depends on ARCH_SUNXI + help + Number of bytes in the Out-Of-Band area for the NAND chip on + the board. + +# Enhance depends when converting drivers to Kconfig which use this config +# option (mxc_nand, ndfc, omap_gpmc). +config SYS_NAND_BUSWIDTH_16BIT + bool "Use 16-bit NAND interface" + depends on NAND_VF610_NFC || NAND_OMAP_GPMC || NAND_MXC || ARCH_DAVINCI + help + Indicates that NAND device has 16-bit wide data-bus. In absence of this + config, bus-width of NAND device is assumed to be either 8-bit and later + determined by reading ONFI params. + Above config is useful when NAND device's bus-width information cannot + be determined from on-chip ONFI params, like in following scenarios: + - SPL boot does not support reading of ONFI parameters. This is done to + keep SPL code foot-print small. + - In current U-Boot flow using nand_init(), driver initialization + happens in board_nand_init() which is called before any device probe + (nand_scan_ident + nand_scan_tail), thus device's ONFI parameters are + not available while configuring controller. So a static CONFIG_NAND_xx + is needed to know the device's bus-width in advance. + +if SPL + +config SYS_NAND_U_BOOT_LOCATIONS + bool "Define U-boot binaries locations in NAND" + help + Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig. + This option should not be enabled when compiling U-boot for boards + defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/.h + file. + +config SYS_NAND_U_BOOT_OFFS + hex "Location in NAND to read U-Boot from" + default 0x800000 if NAND_SUNXI + depends on SYS_NAND_U_BOOT_LOCATIONS + help + Set the offset from the start of the nand where u-boot should be + loaded from. + +config SYS_NAND_U_BOOT_OFFS_REDUND + hex "Location in NAND to read U-Boot from" + default SYS_NAND_U_BOOT_OFFS + depends on SYS_NAND_U_BOOT_LOCATIONS + help + Set the offset from the start of the nand where the redundant u-boot + should be loaded from. + +config SPL_NAND_AM33XX_BCH + bool "Enables SPL-NAND driver which supports ELM based" + depends on NAND_OMAP_GPMC && !OMAP34XX + default y + help + Hardware ECC correction. This is useful for platforms which have ELM + hardware engine and use NAND boot mode. + Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine, + so those platforms should use CONFIG_SPL_NAND_SIMPLE for enabling + SPL-NAND driver with software ECC correction support. + +config SPL_NAND_DENALI + bool "Support Denali NAND controller for SPL" + help + This is a small implementation of the Denali NAND controller + for use on SPL. + +config SPL_NAND_SIMPLE + bool "Use simple SPL NAND driver" + depends on !SPL_NAND_AM33XX_BCH + help + Support for NAND boot using simple NAND drivers that + expose the cmd_ctrl() interface. +endif + +endif # if NAND diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile new file mode 100644 index 0000000..c61e3f3 --- /dev/null +++ b/drivers/mtd/nand/raw/Makefile @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2006 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +ifdef CONFIG_SPL_BUILD + +ifdef CONFIG_SPL_NAND_DRIVERS +NORMAL_DRIVERS=y +endif + +obj-$(CONFIG_SPL_NAND_AM33XX_BCH) += am335x_spl_bch.o +obj-$(CONFIG_SPL_NAND_DENALI) += denali_spl.o +obj-$(CONFIG_SPL_NAND_SIMPLE) += nand_spl_simple.o +obj-$(CONFIG_SPL_NAND_LOAD) += nand_spl_load.o +obj-$(CONFIG_SPL_NAND_ECC) += nand_ecc.o +obj-$(CONFIG_SPL_NAND_BASE) += nand_base.o +obj-$(CONFIG_SPL_NAND_IDENT) += nand_ids.o nand_timings.o +obj-$(CONFIG_SPL_NAND_INIT) += nand.o +ifeq ($(CONFIG_SPL_ENV_SUPPORT),y) +obj-$(CONFIG_ENV_IS_IN_NAND) += nand_util.o +endif + +else # not spl + +NORMAL_DRIVERS=y + +obj-y += nand.o +obj-y += nand_bbt.o +obj-y += nand_ids.o +obj-y += nand_util.o +obj-y += nand_ecc.o +obj-y += nand_base.o +obj-y += nand_timings.o + +endif # not spl + +ifdef NORMAL_DRIVERS + +obj-$(CONFIG_NAND_ECC_BCH) += nand_bch.o + +obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o +obj-$(CONFIG_NAND_ARASAN) += arasan_nfc.o +obj-$(CONFIG_NAND_DAVINCI) += davinci_nand.o +obj-$(CONFIG_NAND_DENALI) += denali.o +obj-$(CONFIG_NAND_DENALI_DT) += denali_dt.o +obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o +obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_nand.o +obj-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o +obj-$(CONFIG_NAND_FSMC) += fsmc_nand.o +obj-$(CONFIG_NAND_KB9202) += kb9202_nand.o +obj-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o +obj-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o +obj-$(CONFIG_NAND_LPC32XX_MLC) += lpc32xx_nand_mlc.o +obj-$(CONFIG_NAND_LPC32XX_SLC) += lpc32xx_nand_slc.o +obj-$(CONFIG_NAND_VF610_NFC) += vf610_nfc.o +obj-$(CONFIG_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_NAND_MXS) += mxs_nand.o +obj-$(CONFIG_NAND_MXS_DT) += mxs_nand_dt.o +obj-$(CONFIG_NAND_PXA3XX) += pxa3xx_nand.o +obj-$(CONFIG_NAND_SPEAR) += spr_nand.o +obj-$(CONFIG_TEGRA_NAND) += tegra_nand.o +obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o +obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o +obj-$(CONFIG_NAND_PLAT) += nand_plat.o +obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o + +else # minimal SPL drivers + +obj-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_spl.o +obj-$(CONFIG_NAND_FSL_IFC) += fsl_ifc_spl.o +obj-$(CONFIG_NAND_MXC) += mxc_nand_spl.o +obj-$(CONFIG_NAND_MXS) += mxs_nand_spl.o mxs_nand.o +obj-$(CONFIG_NAND_SUNXI) += sunxi_nand_spl.o + +endif # drivers diff --git a/drivers/mtd/nand/am335x_spl_bch.c b/drivers/mtd/nand/raw/am335x_spl_bch.c similarity index 100% rename from drivers/mtd/nand/am335x_spl_bch.c rename to drivers/mtd/nand/raw/am335x_spl_bch.c diff --git a/drivers/mtd/nand/arasan_nfc.c b/drivers/mtd/nand/raw/arasan_nfc.c similarity index 100% rename from drivers/mtd/nand/arasan_nfc.c rename to drivers/mtd/nand/raw/arasan_nfc.c diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/raw/atmel_nand.c similarity index 100% rename from drivers/mtd/nand/atmel_nand.c rename to drivers/mtd/nand/raw/atmel_nand.c diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/raw/atmel_nand_ecc.h similarity index 100% rename from drivers/mtd/nand/atmel_nand_ecc.h rename to drivers/mtd/nand/raw/atmel_nand_ecc.h diff --git a/drivers/mtd/nand/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c similarity index 99% rename from drivers/mtd/nand/davinci_nand.c rename to drivers/mtd/nand/raw/davinci_nand.c index 305e68a..e6a84a5 100644 --- a/drivers/mtd/nand/davinci_nand.c +++ b/drivers/mtd/nand/raw/davinci_nand.c @@ -9,7 +9,7 @@ /* * - * linux/drivers/mtd/nand/nand_davinci.c + * linux/drivers/mtd/nand/raw/nand_davinci.c * * NAND Flash Driver * diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/raw/denali.c similarity index 100% rename from drivers/mtd/nand/denali.c rename to drivers/mtd/nand/raw/denali.c diff --git a/drivers/mtd/nand/denali.h b/drivers/mtd/nand/raw/denali.h similarity index 100% rename from drivers/mtd/nand/denali.h rename to drivers/mtd/nand/raw/denali.h diff --git a/drivers/mtd/nand/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c similarity index 100% rename from drivers/mtd/nand/denali_dt.c rename to drivers/mtd/nand/raw/denali_dt.c diff --git a/drivers/mtd/nand/denali_spl.c b/drivers/mtd/nand/raw/denali_spl.c similarity index 100% rename from drivers/mtd/nand/denali_spl.c rename to drivers/mtd/nand/raw/denali_spl.c diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c similarity index 100% rename from drivers/mtd/nand/fsl_elbc_nand.c rename to drivers/mtd/nand/raw/fsl_elbc_nand.c diff --git a/drivers/mtd/nand/fsl_elbc_spl.c b/drivers/mtd/nand/raw/fsl_elbc_spl.c similarity index 100% rename from drivers/mtd/nand/fsl_elbc_spl.c rename to drivers/mtd/nand/raw/fsl_elbc_spl.c diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c similarity index 100% rename from drivers/mtd/nand/fsl_ifc_nand.c rename to drivers/mtd/nand/raw/fsl_ifc_nand.c diff --git a/drivers/mtd/nand/fsl_ifc_spl.c b/drivers/mtd/nand/raw/fsl_ifc_spl.c similarity index 100% rename from drivers/mtd/nand/fsl_ifc_spl.c rename to drivers/mtd/nand/raw/fsl_ifc_spl.c diff --git a/drivers/mtd/nand/fsl_upm.c b/drivers/mtd/nand/raw/fsl_upm.c similarity index 100% rename from drivers/mtd/nand/fsl_upm.c rename to drivers/mtd/nand/raw/fsl_upm.c diff --git a/drivers/mtd/nand/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c similarity index 100% rename from drivers/mtd/nand/fsmc_nand.c rename to drivers/mtd/nand/raw/fsmc_nand.c diff --git a/drivers/mtd/nand/kb9202_nand.c b/drivers/mtd/nand/raw/kb9202_nand.c similarity index 100% rename from drivers/mtd/nand/kb9202_nand.c rename to drivers/mtd/nand/raw/kb9202_nand.c diff --git a/drivers/mtd/nand/kirkwood_nand.c b/drivers/mtd/nand/raw/kirkwood_nand.c similarity index 100% rename from drivers/mtd/nand/kirkwood_nand.c rename to drivers/mtd/nand/raw/kirkwood_nand.c diff --git a/drivers/mtd/nand/kmeter1_nand.c b/drivers/mtd/nand/raw/kmeter1_nand.c similarity index 100% rename from drivers/mtd/nand/kmeter1_nand.c rename to drivers/mtd/nand/raw/kmeter1_nand.c diff --git a/drivers/mtd/nand/lpc32xx_nand_mlc.c b/drivers/mtd/nand/raw/lpc32xx_nand_mlc.c similarity index 100% rename from drivers/mtd/nand/lpc32xx_nand_mlc.c rename to drivers/mtd/nand/raw/lpc32xx_nand_mlc.c diff --git a/drivers/mtd/nand/lpc32xx_nand_slc.c b/drivers/mtd/nand/raw/lpc32xx_nand_slc.c similarity index 100% rename from drivers/mtd/nand/lpc32xx_nand_slc.c rename to drivers/mtd/nand/raw/lpc32xx_nand_slc.c diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c similarity index 100% rename from drivers/mtd/nand/mxc_nand.c rename to drivers/mtd/nand/raw/mxc_nand.c diff --git a/drivers/mtd/nand/mxc_nand.h b/drivers/mtd/nand/raw/mxc_nand.h similarity index 100% rename from drivers/mtd/nand/mxc_nand.h rename to drivers/mtd/nand/raw/mxc_nand.h diff --git a/drivers/mtd/nand/mxc_nand_spl.c b/drivers/mtd/nand/raw/mxc_nand_spl.c similarity index 100% rename from drivers/mtd/nand/mxc_nand_spl.c rename to drivers/mtd/nand/raw/mxc_nand_spl.c diff --git a/drivers/mtd/nand/mxs_nand.c b/drivers/mtd/nand/raw/mxs_nand.c similarity index 100% rename from drivers/mtd/nand/mxs_nand.c rename to drivers/mtd/nand/raw/mxs_nand.c diff --git a/drivers/mtd/nand/mxs_nand.h b/drivers/mtd/nand/raw/mxs_nand.h similarity index 100% rename from drivers/mtd/nand/mxs_nand.h rename to drivers/mtd/nand/raw/mxs_nand.h diff --git a/drivers/mtd/nand/mxs_nand_dt.c b/drivers/mtd/nand/raw/mxs_nand_dt.c similarity index 100% rename from drivers/mtd/nand/mxs_nand_dt.c rename to drivers/mtd/nand/raw/mxs_nand_dt.c diff --git a/drivers/mtd/nand/mxs_nand_spl.c b/drivers/mtd/nand/raw/mxs_nand_spl.c similarity index 100% rename from drivers/mtd/nand/mxs_nand_spl.c rename to drivers/mtd/nand/raw/mxs_nand_spl.c diff --git a/drivers/mtd/nand/nand.c b/drivers/mtd/nand/raw/nand.c similarity index 100% rename from drivers/mtd/nand/nand.c rename to drivers/mtd/nand/raw/nand.c diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/raw/nand_base.c similarity index 100% rename from drivers/mtd/nand/nand_base.c rename to drivers/mtd/nand/raw/nand_base.c diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/raw/nand_bbt.c similarity index 100% rename from drivers/mtd/nand/nand_bbt.c rename to drivers/mtd/nand/raw/nand_bbt.c diff --git a/drivers/mtd/nand/nand_bch.c b/drivers/mtd/nand/raw/nand_bch.c similarity index 100% rename from drivers/mtd/nand/nand_bch.c rename to drivers/mtd/nand/raw/nand_bch.c diff --git a/drivers/mtd/nand/nand_ecc.c b/drivers/mtd/nand/raw/nand_ecc.c similarity index 99% rename from drivers/mtd/nand/nand_ecc.c rename to drivers/mtd/nand/raw/nand_ecc.c index 05e55fc..2bc329b 100644 --- a/drivers/mtd/nand/nand_ecc.c +++ b/drivers/mtd/nand/raw/nand_ecc.c @@ -3,7 +3,7 @@ * This file contains an ECC algorithm from Toshiba that detects and * corrects 1 bit errors in a 256 byte block of data. * - * drivers/mtd/nand/nand_ecc.c + * drivers/mtd/nand/raw/nand_ecc.c * * Copyright (C) 2000-2004 Steven J. Hill (sjhill@realitydiluted.com) * Toshiba America Electronics Components, Inc. diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c similarity index 100% rename from drivers/mtd/nand/nand_ids.c rename to drivers/mtd/nand/raw/nand_ids.c diff --git a/drivers/mtd/nand/nand_plat.c b/drivers/mtd/nand/raw/nand_plat.c similarity index 100% rename from drivers/mtd/nand/nand_plat.c rename to drivers/mtd/nand/raw/nand_plat.c diff --git a/drivers/mtd/nand/nand_spl_load.c b/drivers/mtd/nand/raw/nand_spl_load.c similarity index 100% rename from drivers/mtd/nand/nand_spl_load.c rename to drivers/mtd/nand/raw/nand_spl_load.c diff --git a/drivers/mtd/nand/nand_spl_loaders.c b/drivers/mtd/nand/raw/nand_spl_loaders.c similarity index 100% rename from drivers/mtd/nand/nand_spl_loaders.c rename to drivers/mtd/nand/raw/nand_spl_loaders.c diff --git a/drivers/mtd/nand/nand_spl_simple.c b/drivers/mtd/nand/raw/nand_spl_simple.c similarity index 100% rename from drivers/mtd/nand/nand_spl_simple.c rename to drivers/mtd/nand/raw/nand_spl_simple.c diff --git a/drivers/mtd/nand/nand_timings.c b/drivers/mtd/nand/raw/nand_timings.c similarity index 100% rename from drivers/mtd/nand/nand_timings.c rename to drivers/mtd/nand/raw/nand_timings.c diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/raw/nand_util.c similarity index 99% rename from drivers/mtd/nand/nand_util.c rename to drivers/mtd/nand/raw/nand_util.c index 1ded7aa..fc2235c 100644 --- a/drivers/mtd/nand/nand_util.c +++ b/drivers/mtd/nand/raw/nand_util.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * drivers/mtd/nand/nand_util.c + * drivers/mtd/nand/raw/nand_util.c * * Copyright (C) 2006 by Weiss-Electronic GmbH. * All rights reserved. diff --git a/drivers/mtd/nand/omap_elm.c b/drivers/mtd/nand/raw/omap_elm.c similarity index 100% rename from drivers/mtd/nand/omap_elm.c rename to drivers/mtd/nand/raw/omap_elm.c diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/raw/omap_gpmc.c similarity index 100% rename from drivers/mtd/nand/omap_gpmc.c rename to drivers/mtd/nand/raw/omap_gpmc.c diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/raw/pxa3xx_nand.c similarity index 99% rename from drivers/mtd/nand/pxa3xx_nand.c rename to drivers/mtd/nand/raw/pxa3xx_nand.c index 2a02a9d..4c783f1 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/raw/pxa3xx_nand.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * drivers/mtd/nand/pxa3xx_nand.c + * drivers/mtd/nand/raw/pxa3xx_nand.c * * Copyright © 2005 Intel Corporation * Copyright © 2006 Marvell International Ltd. diff --git a/drivers/mtd/nand/pxa3xx_nand.h b/drivers/mtd/nand/raw/pxa3xx_nand.h similarity index 100% rename from drivers/mtd/nand/pxa3xx_nand.h rename to drivers/mtd/nand/raw/pxa3xx_nand.h diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c similarity index 100% rename from drivers/mtd/nand/sunxi_nand.c rename to drivers/mtd/nand/raw/sunxi_nand.c diff --git a/drivers/mtd/nand/sunxi_nand_spl.c b/drivers/mtd/nand/raw/sunxi_nand_spl.c similarity index 100% rename from drivers/mtd/nand/sunxi_nand_spl.c rename to drivers/mtd/nand/raw/sunxi_nand_spl.c diff --git a/drivers/mtd/nand/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c similarity index 100% rename from drivers/mtd/nand/tegra_nand.c rename to drivers/mtd/nand/raw/tegra_nand.c diff --git a/drivers/mtd/nand/tegra_nand.h b/drivers/mtd/nand/raw/tegra_nand.h similarity index 100% rename from drivers/mtd/nand/tegra_nand.h rename to drivers/mtd/nand/raw/tegra_nand.h diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c similarity index 100% rename from drivers/mtd/nand/vf610_nfc.c rename to drivers/mtd/nand/raw/vf610_nfc.c diff --git a/drivers/mtd/nand/zynq_nand.c b/drivers/mtd/nand/raw/zynq_nand.c similarity index 100% rename from drivers/mtd/nand/zynq_nand.c rename to drivers/mtd/nand/raw/zynq_nand.c diff --git a/include/configs/MPC8313ERDB.h b/include/configs/MPC8313ERDB.h index 884dd41..5b5c38f 100644 --- a/include/configs/MPC8313ERDB.h +++ b/include/configs/MPC8313ERDB.h @@ -240,7 +240,7 @@ /* LB refresh timer prescal, 266MHz/32 */ #define CONFIG_SYS_LBC_MRTPR 0x20000000 /*TODO */ -/* drivers/mtd/nand/nand.c */ +/* drivers/mtd/nand/raw/nand.c */ #if defined(CONFIG_NAND) && defined(CONFIG_SPL_BUILD) #define CONFIG_SYS_NAND_BASE 0xFFF00000 #else From 3657b2f4a309ed2e527c805333f9485757912397 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:08 +0200 Subject: [PATCH 09/37] mtd: rename nand into rawnand in Kconfig prompt Sync the Kconfig raw NAND entry title with the code architecture. Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 1e4ea7b..008f7b4 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -1,6 +1,6 @@ menuconfig NAND - bool "NAND Device Support" + bool "Raw NAND Device Support" if NAND config SYS_NAND_SELF_INIT From b95db8d33a1e920801816e47ffc5c6f18acce024 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:30:09 +0200 Subject: [PATCH 10/37] mtd: nand: Add core infrastructure to deal with NAND devices Add an intermediate layer to abstract NAND device interface so that some logic can be shared between SPI NANDs, parallel/raw NANDs, OneNANDs, ... Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/Kconfig | 3 + drivers/mtd/nand/Makefile | 2 + drivers/mtd/nand/bbt.c | 132 +++++++++ drivers/mtd/nand/core.c | 243 +++++++++++++++ include/linux/mtd/nand.h | 731 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1111 insertions(+) create mode 100644 drivers/mtd/nand/bbt.c create mode 100644 drivers/mtd/nand/core.c create mode 100644 include/linux/mtd/nand.h diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 6d53734..1c1a1f4 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -1 +1,4 @@ +config MTD_NAND_CORE + tristate + source "drivers/mtd/nand/raw/Kconfig" diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 69f40d1..cd492db 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -1,2 +1,4 @@ # SPDX-License-Identifier: GPL-2.0+ +nandcore-objs := core.o bbt.o +obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o diff --git a/drivers/mtd/nand/bbt.c b/drivers/mtd/nand/bbt.c new file mode 100644 index 0000000..7e0ad31 --- /dev/null +++ b/drivers/mtd/nand/bbt.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Free Electrons + * + * Authors: + * Boris Brezillon + * Peter Pan + */ + +#define pr_fmt(fmt) "nand-bbt: " fmt + +#include +#ifndef __UBOOT__ +#include +#endif + +/** + * nanddev_bbt_init() - Initialize the BBT (Bad Block Table) + * @nand: NAND device + * + * Initialize the in-memory BBT. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_bbt_init(struct nand_device *nand) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned int nblocks = nanddev_neraseblocks(nand); + unsigned int nwords = DIV_ROUND_UP(nblocks * bits_per_block, + BITS_PER_LONG); + + nand->bbt.cache = kzalloc(nwords, GFP_KERNEL); + if (!nand->bbt.cache) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_init); + +/** + * nanddev_bbt_cleanup() - Cleanup the BBT (Bad Block Table) + * @nand: NAND device + * + * Undoes what has been done in nanddev_bbt_init() + */ +void nanddev_bbt_cleanup(struct nand_device *nand) +{ + kfree(nand->bbt.cache); +} +EXPORT_SYMBOL_GPL(nanddev_bbt_cleanup); + +/** + * nanddev_bbt_update() - Update a BBT + * @nand: nand device + * + * Update the BBT. Currently a NOP function since on-flash bbt is not yet + * supported. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_bbt_update(struct nand_device *nand) +{ + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_update); + +/** + * nanddev_bbt_get_block_status() - Return the status of an eraseblock + * @nand: nand device + * @entry: the BBT entry + * + * Return: a positive number nand_bbt_block_status status or -%ERANGE if @entry + * is bigger than the BBT size. + */ +int nanddev_bbt_get_block_status(const struct nand_device *nand, + unsigned int entry) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned long *pos = nand->bbt.cache + + ((entry * bits_per_block) / BITS_PER_LONG); + unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG; + unsigned long status; + + if (entry >= nanddev_neraseblocks(nand)) + return -ERANGE; + + status = pos[0] >> offs; + if (bits_per_block + offs > BITS_PER_LONG) + status |= pos[1] << (BITS_PER_LONG - offs); + + return status & GENMASK(bits_per_block - 1, 0); +} +EXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status); + +/** + * nanddev_bbt_set_block_status() - Update the status of an eraseblock in the + * in-memory BBT + * @nand: nand device + * @entry: the BBT entry to update + * @status: the new status + * + * Update an entry of the in-memory BBT. If you want to push the updated BBT + * the NAND you should call nanddev_bbt_update(). + * + * Return: 0 in case of success or -%ERANGE if @entry is bigger than the BBT + * size. + */ +int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry, + enum nand_bbt_block_status status) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned long *pos = nand->bbt.cache + + ((entry * bits_per_block) / BITS_PER_LONG); + unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG; + unsigned long val = status & GENMASK(bits_per_block - 1, 0); + + if (entry >= nanddev_neraseblocks(nand)) + return -ERANGE; + + pos[0] &= ~GENMASK(offs + bits_per_block - 1, offs); + pos[0] |= val << offs; + + if (bits_per_block + offs > BITS_PER_LONG) { + unsigned int rbits = bits_per_block + offs - BITS_PER_LONG; + + pos[1] &= ~GENMASK(rbits - 1, 0); + pos[1] |= val >> rbits; + } + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status); diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c new file mode 100644 index 0000000..0b79369 --- /dev/null +++ b/drivers/mtd/nand/core.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Free Electrons + * + * Authors: + * Boris Brezillon + * Peter Pan + */ + +#define pr_fmt(fmt) "nand: " fmt + +#ifndef __UBOOT__ +#include +#endif +#include + +/** + * nanddev_isbad() - Check if a block is bad + * @nand: NAND device + * @pos: position pointing to the block we want to check + * + * Return: true if the block is bad, false otherwise. + */ +bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos) +{ + if (nanddev_bbt_is_initialized(nand)) { + unsigned int entry; + int status; + + entry = nanddev_bbt_pos_to_entry(nand, pos); + status = nanddev_bbt_get_block_status(nand, entry); + /* Lazy block status retrieval */ + if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) { + if (nand->ops->isbad(nand, pos)) + status = NAND_BBT_BLOCK_FACTORY_BAD; + else + status = NAND_BBT_BLOCK_GOOD; + + nanddev_bbt_set_block_status(nand, entry, status); + } + + if (status == NAND_BBT_BLOCK_WORN || + status == NAND_BBT_BLOCK_FACTORY_BAD) + return true; + + return false; + } + + return nand->ops->isbad(nand, pos); +} +EXPORT_SYMBOL_GPL(nanddev_isbad); + +/** + * nanddev_markbad() - Mark a block as bad + * @nand: NAND device + * @pos: position of the block to mark bad + * + * Mark a block bad. This function is updating the BBT if available and + * calls the low-level markbad hook (nand->ops->markbad()). + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct mtd_info *mtd = nanddev_to_mtd(nand); + unsigned int entry; + int ret = 0; + + if (nanddev_isbad(nand, pos)) + return 0; + + ret = nand->ops->markbad(nand, pos); + if (ret) + pr_warn("failed to write BBM to block @%llx (err = %d)\n", + nanddev_pos_to_offs(nand, pos), ret); + + if (!nanddev_bbt_is_initialized(nand)) + goto out; + + entry = nanddev_bbt_pos_to_entry(nand, pos); + ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN); + if (ret) + goto out; + + ret = nanddev_bbt_update(nand); + +out: + if (!ret) + mtd->ecc_stats.badblocks++; + + return ret; +} +EXPORT_SYMBOL_GPL(nanddev_markbad); + +/** + * nanddev_isreserved() - Check whether an eraseblock is reserved or not + * @nand: NAND device + * @pos: NAND position to test + * + * Checks whether the eraseblock pointed by @pos is reserved or not. + * + * Return: true if the eraseblock is reserved, false otherwise. + */ +bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos) +{ + unsigned int entry; + int status; + + if (!nanddev_bbt_is_initialized(nand)) + return false; + + /* Return info from the table */ + entry = nanddev_bbt_pos_to_entry(nand, pos); + status = nanddev_bbt_get_block_status(nand, entry); + return status == NAND_BBT_BLOCK_RESERVED; +} +EXPORT_SYMBOL_GPL(nanddev_isreserved); + +/** + * nanddev_erase() - Erase a NAND portion + * @nand: NAND device + * @pos: position of the block to erase + * + * Erases the block if it's not bad. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos) +{ + if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) { + pr_warn("attempt to erase a bad/reserved block @%llx\n", + nanddev_pos_to_offs(nand, pos)); + return -EIO; + } + + return nand->ops->erase(nand, pos); +} +EXPORT_SYMBOL_GPL(nanddev_erase); + +/** + * nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices + * @mtd: MTD device + * @einfo: erase request + * + * This is a simple mtd->_erase() implementation iterating over all blocks + * concerned by @einfo and calling nand->ops->erase() on each of them. + * + * Note that mtd->_erase should not be directly assigned to this helper, + * because there's no locking here. NAND specialized layers should instead + * implement there own wrapper around nanddev_mtd_erase() taking the + * appropriate lock before calling nanddev_mtd_erase(). + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_pos pos, last; + int ret; + + nanddev_offs_to_pos(nand, einfo->addr, &pos); + nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last); + while (nanddev_pos_cmp(&pos, &last) <= 0) { + ret = nanddev_erase(nand, &pos); + if (ret) { + einfo->fail_addr = nanddev_pos_to_offs(nand, &pos); + + return ret; + } + + nanddev_pos_next_eraseblock(nand, &pos); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_mtd_erase); + +/** + * nanddev_init() - Initialize a NAND device + * @nand: NAND device + * @ops: NAND device operations + * @owner: NAND device owner + * + * Initializes a NAND device object. Consistency checks are done on @ops and + * @nand->memorg. Also takes care of initializing the BBT. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_init(struct nand_device *nand, const struct nand_ops *ops, + struct module *owner) +{ + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct nand_memory_organization *memorg = nanddev_get_memorg(nand); + + if (!nand || !ops) + return -EINVAL; + + if (!ops->erase || !ops->markbad || !ops->isbad) + return -EINVAL; + + if (!memorg->bits_per_cell || !memorg->pagesize || + !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun || + !memorg->planes_per_lun || !memorg->luns_per_target || + !memorg->ntargets) + return -EINVAL; + + nand->rowconv.eraseblock_addr_shift = + fls(memorg->pages_per_eraseblock - 1); + nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) + + nand->rowconv.eraseblock_addr_shift; + + nand->ops = ops; + + mtd->type = memorg->bits_per_cell == 1 ? + MTD_NANDFLASH : MTD_MLCNANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock; + mtd->writesize = memorg->pagesize; + mtd->writebufsize = memorg->pagesize; + mtd->oobsize = memorg->oobsize; + mtd->size = nanddev_size(nand); + mtd->owner = owner; + + return nanddev_bbt_init(nand); +} +EXPORT_SYMBOL_GPL(nanddev_init); + +/** + * nanddev_cleanup() - Release resources allocated in nanddev_init() + * @nand: NAND device + * + * Basically undoes what has been done in nanddev_init(). + */ +void nanddev_cleanup(struct nand_device *nand) +{ + if (nanddev_bbt_is_initialized(nand)) + nanddev_bbt_cleanup(nand); +} +EXPORT_SYMBOL_GPL(nanddev_cleanup); + +MODULE_DESCRIPTION("Generic NAND framework"); +MODULE_AUTHOR("Boris Brezillon "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h new file mode 100644 index 0000000..ada7af4 --- /dev/null +++ b/include/linux/mtd/nand.h @@ -0,0 +1,731 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2017 - Free Electrons + * + * Authors: + * Boris Brezillon + * Peter Pan + */ + +#ifndef __LINUX_MTD_NAND_H +#define __LINUX_MTD_NAND_H + +#include + +/** + * struct nand_memory_organization - Memory organization structure + * @bits_per_cell: number of bits per NAND cell + * @pagesize: page size + * @oobsize: OOB area size + * @pages_per_eraseblock: number of pages per eraseblock + * @eraseblocks_per_lun: number of eraseblocks per LUN (Logical Unit Number) + * @planes_per_lun: number of planes per LUN + * @luns_per_target: number of LUN per target (target is a synonym for die) + * @ntargets: total number of targets exposed by the NAND device + */ +struct nand_memory_organization { + unsigned int bits_per_cell; + unsigned int pagesize; + unsigned int oobsize; + unsigned int pages_per_eraseblock; + unsigned int eraseblocks_per_lun; + unsigned int planes_per_lun; + unsigned int luns_per_target; + unsigned int ntargets; +}; + +#define NAND_MEMORG(bpc, ps, os, ppe, epl, ppl, lpt, nt) \ + { \ + .bits_per_cell = (bpc), \ + .pagesize = (ps), \ + .oobsize = (os), \ + .pages_per_eraseblock = (ppe), \ + .eraseblocks_per_lun = (epl), \ + .planes_per_lun = (ppl), \ + .luns_per_target = (lpt), \ + .ntargets = (nt), \ + } + +/** + * struct nand_row_converter - Information needed to convert an absolute offset + * into a row address + * @lun_addr_shift: position of the LUN identifier in the row address + * @eraseblock_addr_shift: position of the eraseblock identifier in the row + * address + */ +struct nand_row_converter { + unsigned int lun_addr_shift; + unsigned int eraseblock_addr_shift; +}; + +/** + * struct nand_pos - NAND position object + * @target: the NAND target/die + * @lun: the LUN identifier + * @plane: the plane within the LUN + * @eraseblock: the eraseblock within the LUN + * @page: the page within the LUN + * + * These information are usually used by specific sub-layers to select the + * appropriate target/die and generate a row address to pass to the device. + */ +struct nand_pos { + unsigned int target; + unsigned int lun; + unsigned int plane; + unsigned int eraseblock; + unsigned int page; +}; + +/** + * struct nand_page_io_req - NAND I/O request object + * @pos: the position this I/O request is targeting + * @dataoffs: the offset within the page + * @datalen: number of data bytes to read from/write to this page + * @databuf: buffer to store data in or get data from + * @ooboffs: the OOB offset within the page + * @ooblen: the number of OOB bytes to read from/write to this page + * @oobbuf: buffer to store OOB data in or get OOB data from + * + * This object is used to pass per-page I/O requests to NAND sub-layers. This + * way all useful information are already formatted in a useful way and + * specific NAND layers can focus on translating these information into + * specific commands/operations. + */ +struct nand_page_io_req { + struct nand_pos pos; + unsigned int dataoffs; + unsigned int datalen; + union { + const void *out; + void *in; + } databuf; + unsigned int ooboffs; + unsigned int ooblen; + union { + const void *out; + void *in; + } oobbuf; +}; + +/** + * struct nand_ecc_req - NAND ECC requirements + * @strength: ECC strength + * @step_size: ECC step/block size + */ +struct nand_ecc_req { + unsigned int strength; + unsigned int step_size; +}; + +#define NAND_ECCREQ(str, stp) { .strength = (str), .step_size = (stp) } + +/** + * struct nand_bbt - bad block table object + * @cache: in memory BBT cache + */ +struct nand_bbt { + unsigned long *cache; +}; + +struct nand_device; + +/** + * struct nand_ops - NAND operations + * @erase: erase a specific block. No need to check if the block is bad before + * erasing, this has been taken care of by the generic NAND layer + * @markbad: mark a specific block bad. No need to check if the block is + * already marked bad, this has been taken care of by the generic + * NAND layer. This method should just write the BBM (Bad Block + * Marker) so that future call to struct_nand_ops->isbad() return + * true + * @isbad: check whether a block is bad or not. This method should just read + * the BBM and return whether the block is bad or not based on what it + * reads + * + * These are all low level operations that should be implemented by specialized + * NAND layers (SPI NAND, raw NAND, ...). + */ +struct nand_ops { + int (*erase)(struct nand_device *nand, const struct nand_pos *pos); + int (*markbad)(struct nand_device *nand, const struct nand_pos *pos); + bool (*isbad)(struct nand_device *nand, const struct nand_pos *pos); +}; + +/** + * struct nand_device - NAND device + * @mtd: MTD instance attached to the NAND device + * @memorg: memory layout + * @eccreq: ECC requirements + * @rowconv: position to row address converter + * @bbt: bad block table info + * @ops: NAND operations attached to the NAND device + * + * Generic NAND object. Specialized NAND layers (raw NAND, SPI NAND, OneNAND) + * should declare their own NAND object embedding a nand_device struct (that's + * how inheritance is done). + * struct_nand_device->memorg and struct_nand_device->eccreq should be filled + * at device detection time to reflect the NAND device + * capabilities/requirements. Once this is done nanddev_init() can be called. + * It will take care of converting NAND information into MTD ones, which means + * the specialized NAND layers should never manually tweak + * struct_nand_device->mtd except for the ->_read/write() hooks. + */ +struct nand_device { + struct mtd_info *mtd; + struct nand_memory_organization memorg; + struct nand_ecc_req eccreq; + struct nand_row_converter rowconv; + struct nand_bbt bbt; + const struct nand_ops *ops; +}; + +/** + * struct nand_io_iter - NAND I/O iterator + * @req: current I/O request + * @oobbytes_per_page: maximum number of OOB bytes per page + * @dataleft: remaining number of data bytes to read/write + * @oobleft: remaining number of OOB bytes to read/write + * + * Can be used by specialized NAND layers to iterate over all pages covered + * by an MTD I/O request, which should greatly simplifies the boiler-plate + * code needed to read/write data from/to a NAND device. + */ +struct nand_io_iter { + struct nand_page_io_req req; + unsigned int oobbytes_per_page; + unsigned int dataleft; + unsigned int oobleft; +}; + +/** + * mtd_to_nanddev() - Get the NAND device attached to the MTD instance + * @mtd: MTD instance + * + * Return: the NAND device embedding @mtd. + */ +static inline struct nand_device *mtd_to_nanddev(struct mtd_info *mtd) +{ + return mtd->priv; +} + +/** + * nanddev_to_mtd() - Get the MTD device attached to a NAND device + * @nand: NAND device + * + * Return: the MTD device embedded in @nand. + */ +static inline struct mtd_info *nanddev_to_mtd(struct nand_device *nand) +{ + return nand->mtd; +} + +/* + * nanddev_bits_per_cell() - Get the number of bits per cell + * @nand: NAND device + * + * Return: the number of bits per cell. + */ +static inline unsigned int nanddev_bits_per_cell(const struct nand_device *nand) +{ + return nand->memorg.bits_per_cell; +} + +/** + * nanddev_page_size() - Get NAND page size + * @nand: NAND device + * + * Return: the page size. + */ +static inline size_t nanddev_page_size(const struct nand_device *nand) +{ + return nand->memorg.pagesize; +} + +/** + * nanddev_per_page_oobsize() - Get NAND OOB size + * @nand: NAND device + * + * Return: the OOB size. + */ +static inline unsigned int +nanddev_per_page_oobsize(const struct nand_device *nand) +{ + return nand->memorg.oobsize; +} + +/** + * nanddev_pages_per_eraseblock() - Get the number of pages per eraseblock + * @nand: NAND device + * + * Return: the number of pages per eraseblock. + */ +static inline unsigned int +nanddev_pages_per_eraseblock(const struct nand_device *nand) +{ + return nand->memorg.pages_per_eraseblock; +} + +/** + * nanddev_per_page_oobsize() - Get NAND erase block size + * @nand: NAND device + * + * Return: the eraseblock size. + */ +static inline size_t nanddev_eraseblock_size(const struct nand_device *nand) +{ + return nand->memorg.pagesize * nand->memorg.pages_per_eraseblock; +} + +/** + * nanddev_eraseblocks_per_lun() - Get the number of eraseblocks per LUN + * @nand: NAND device + * + * Return: the number of eraseblocks per LUN. + */ +static inline unsigned int +nanddev_eraseblocks_per_lun(const struct nand_device *nand) +{ + return nand->memorg.eraseblocks_per_lun; +} + +/** + * nanddev_target_size() - Get the total size provided by a single target/die + * @nand: NAND device + * + * Return: the total size exposed by a single target/die in bytes. + */ +static inline u64 nanddev_target_size(const struct nand_device *nand) +{ + return (u64)nand->memorg.luns_per_target * + nand->memorg.eraseblocks_per_lun * + nand->memorg.pages_per_eraseblock * + nand->memorg.pagesize; +} + +/** + * nanddev_ntarget() - Get the total of targets + * @nand: NAND device + * + * Return: the number of targets/dies exposed by @nand. + */ +static inline unsigned int nanddev_ntargets(const struct nand_device *nand) +{ + return nand->memorg.ntargets; +} + +/** + * nanddev_neraseblocks() - Get the total number of erasablocks + * @nand: NAND device + * + * Return: the total number of eraseblocks exposed by @nand. + */ +static inline unsigned int nanddev_neraseblocks(const struct nand_device *nand) +{ + return (u64)nand->memorg.luns_per_target * + nand->memorg.eraseblocks_per_lun * + nand->memorg.pages_per_eraseblock; +} + +/** + * nanddev_size() - Get NAND size + * @nand: NAND device + * + * Return: the total size (in bytes) exposed by @nand. + */ +static inline u64 nanddev_size(const struct nand_device *nand) +{ + return nanddev_target_size(nand) * nanddev_ntargets(nand); +} + +/** + * nanddev_get_memorg() - Extract memory organization info from a NAND device + * @nand: NAND device + * + * This can be used by the upper layer to fill the memorg info before calling + * nanddev_init(). + * + * Return: the memorg object embedded in the NAND device. + */ +static inline struct nand_memory_organization * +nanddev_get_memorg(struct nand_device *nand) +{ + return &nand->memorg; +} + +int nanddev_init(struct nand_device *nand, const struct nand_ops *ops, + struct module *owner); +void nanddev_cleanup(struct nand_device *nand); + +/** + * nanddev_register() - Register a NAND device + * @nand: NAND device + * + * Register a NAND device. + * This function is just a wrapper around mtd_device_register() + * registering the MTD device embedded in @nand. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +static inline int nanddev_register(struct nand_device *nand) +{ + return mtd_device_register(nand->mtd, NULL, 0); +} + +/** + * nanddev_unregister() - Unregister a NAND device + * @nand: NAND device + * + * Unregister a NAND device. + * This function is just a wrapper around mtd_device_unregister() + * unregistering the MTD device embedded in @nand. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +static inline int nanddev_unregister(struct nand_device *nand) +{ + return mtd_device_unregister(nand->mtd); +} + +/** + * nanddev_set_of_node() - Attach a DT node to a NAND device + * @nand: NAND device + * @np: DT node + * + * Attach a DT node to a NAND device. + */ +static inline void nanddev_set_of_node(struct nand_device *nand, + const struct device_node *np) +{ + mtd_set_of_node(nand->mtd, np); +} + +/** + * nanddev_get_of_node() - Retrieve the DT node attached to a NAND device + * @nand: NAND device + * + * Return: the DT node attached to @nand. + */ +static inline const struct device_node *nanddev_get_of_node(struct nand_device *nand) +{ + return mtd_get_of_node(nand->mtd); +} + +/** + * nanddev_offs_to_pos() - Convert an absolute NAND offset into a NAND position + * @nand: NAND device + * @offs: absolute NAND offset (usually passed by the MTD layer) + * @pos: a NAND position object to fill in + * + * Converts @offs into a nand_pos representation. + * + * Return: the offset within the NAND page pointed by @pos. + */ +static inline unsigned int nanddev_offs_to_pos(struct nand_device *nand, + loff_t offs, + struct nand_pos *pos) +{ + unsigned int pageoffs; + u64 tmp = offs; + + pageoffs = do_div(tmp, nand->memorg.pagesize); + pos->page = do_div(tmp, nand->memorg.pages_per_eraseblock); + pos->eraseblock = do_div(tmp, nand->memorg.eraseblocks_per_lun); + pos->plane = pos->eraseblock % nand->memorg.planes_per_lun; + pos->lun = do_div(tmp, nand->memorg.luns_per_target); + pos->target = tmp; + + return pageoffs; +} + +/** + * nanddev_pos_cmp() - Compare two NAND positions + * @a: First NAND position + * @b: Second NAND position + * + * Compares two NAND positions. + * + * Return: -1 if @a < @b, 0 if @a == @b and 1 if @a > @b. + */ +static inline int nanddev_pos_cmp(const struct nand_pos *a, + const struct nand_pos *b) +{ + if (a->target != b->target) + return a->target < b->target ? -1 : 1; + + if (a->lun != b->lun) + return a->lun < b->lun ? -1 : 1; + + if (a->eraseblock != b->eraseblock) + return a->eraseblock < b->eraseblock ? -1 : 1; + + if (a->page != b->page) + return a->page < b->page ? -1 : 1; + + return 0; +} + +/** + * nanddev_pos_to_offs() - Convert a NAND position into an absolute offset + * @nand: NAND device + * @pos: the NAND position to convert + * + * Converts @pos NAND position into an absolute offset. + * + * Return: the absolute offset. Note that @pos points to the beginning of a + * page, if one wants to point to a specific offset within this page + * the returned offset has to be adjusted manually. + */ +static inline loff_t nanddev_pos_to_offs(struct nand_device *nand, + const struct nand_pos *pos) +{ + unsigned int npages; + + npages = pos->page + + ((pos->eraseblock + + (pos->lun + + (pos->target * nand->memorg.luns_per_target)) * + nand->memorg.eraseblocks_per_lun) * + nand->memorg.pages_per_eraseblock); + + return (loff_t)npages * nand->memorg.pagesize; +} + +/** + * nanddev_pos_to_row() - Extract a row address from a NAND position + * @nand: NAND device + * @pos: the position to convert + * + * Converts a NAND position into a row address that can then be passed to the + * device. + * + * Return: the row address extracted from @pos. + */ +static inline unsigned int nanddev_pos_to_row(struct nand_device *nand, + const struct nand_pos *pos) +{ + return (pos->lun << nand->rowconv.lun_addr_shift) | + (pos->eraseblock << nand->rowconv.eraseblock_addr_shift) | + pos->page; +} + +/** + * nanddev_pos_next_target() - Move a position to the next target/die + * @nand: NAND device + * @pos: the position to update + * + * Updates @pos to point to the start of the next target/die. Useful when you + * want to iterate over all targets/dies of a NAND device. + */ +static inline void nanddev_pos_next_target(struct nand_device *nand, + struct nand_pos *pos) +{ + pos->page = 0; + pos->plane = 0; + pos->eraseblock = 0; + pos->lun = 0; + pos->target++; +} + +/** + * nanddev_pos_next_lun() - Move a position to the next LUN + * @nand: NAND device + * @pos: the position to update + * + * Updates @pos to point to the start of the next LUN. Useful when you want to + * iterate over all LUNs of a NAND device. + */ +static inline void nanddev_pos_next_lun(struct nand_device *nand, + struct nand_pos *pos) +{ + if (pos->lun >= nand->memorg.luns_per_target - 1) + return nanddev_pos_next_target(nand, pos); + + pos->lun++; + pos->page = 0; + pos->plane = 0; + pos->eraseblock = 0; +} + +/** + * nanddev_pos_next_eraseblock() - Move a position to the next eraseblock + * @nand: NAND device + * @pos: the position to update + * + * Updates @pos to point to the start of the next eraseblock. Useful when you + * want to iterate over all eraseblocks of a NAND device. + */ +static inline void nanddev_pos_next_eraseblock(struct nand_device *nand, + struct nand_pos *pos) +{ + if (pos->eraseblock >= nand->memorg.eraseblocks_per_lun - 1) + return nanddev_pos_next_lun(nand, pos); + + pos->eraseblock++; + pos->page = 0; + pos->plane = pos->eraseblock % nand->memorg.planes_per_lun; +} + +/** + * nanddev_pos_next_eraseblock() - Move a position to the next page + * @nand: NAND device + * @pos: the position to update + * + * Updates @pos to point to the start of the next page. Useful when you want to + * iterate over all pages of a NAND device. + */ +static inline void nanddev_pos_next_page(struct nand_device *nand, + struct nand_pos *pos) +{ + if (pos->page >= nand->memorg.pages_per_eraseblock - 1) + return nanddev_pos_next_eraseblock(nand, pos); + + pos->page++; +} + +/** + * nand_io_iter_init - Initialize a NAND I/O iterator + * @nand: NAND device + * @offs: absolute offset + * @req: MTD request + * @iter: NAND I/O iterator + * + * Initializes a NAND iterator based on the information passed by the MTD + * layer. + */ +static inline void nanddev_io_iter_init(struct nand_device *nand, + loff_t offs, struct mtd_oob_ops *req, + struct nand_io_iter *iter) +{ + struct mtd_info *mtd = nanddev_to_mtd(nand); + + iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos); + iter->req.ooboffs = req->ooboffs; + iter->oobbytes_per_page = mtd_oobavail(mtd, req); + iter->dataleft = req->len; + iter->oobleft = req->ooblen; + iter->req.databuf.in = req->datbuf; + iter->req.datalen = min_t(unsigned int, + nand->memorg.pagesize - iter->req.dataoffs, + iter->dataleft); + iter->req.oobbuf.in = req->oobbuf; + iter->req.ooblen = min_t(unsigned int, + iter->oobbytes_per_page - iter->req.ooboffs, + iter->oobleft); +} + +/** + * nand_io_iter_next_page - Move to the next page + * @nand: NAND device + * @iter: NAND I/O iterator + * + * Updates the @iter to point to the next page. + */ +static inline void nanddev_io_iter_next_page(struct nand_device *nand, + struct nand_io_iter *iter) +{ + nanddev_pos_next_page(nand, &iter->req.pos); + iter->dataleft -= iter->req.datalen; + iter->req.databuf.in += iter->req.datalen; + iter->oobleft -= iter->req.ooblen; + iter->req.oobbuf.in += iter->req.ooblen; + iter->req.dataoffs = 0; + iter->req.ooboffs = 0; + iter->req.datalen = min_t(unsigned int, nand->memorg.pagesize, + iter->dataleft); + iter->req.ooblen = min_t(unsigned int, iter->oobbytes_per_page, + iter->oobleft); +} + +/** + * nand_io_iter_end - Should end iteration or not + * @nand: NAND device + * @iter: NAND I/O iterator + * + * Check whether @iter has reached the end of the NAND portion it was asked to + * iterate on or not. + * + * Return: true if @iter has reached the end of the iteration request, false + * otherwise. + */ +static inline bool nanddev_io_iter_end(struct nand_device *nand, + const struct nand_io_iter *iter) +{ + if (iter->dataleft || iter->oobleft) + return false; + + return true; +} + +/** + * nand_io_for_each_page - Iterate over all NAND pages contained in an MTD I/O + * request + * @nand: NAND device + * @start: start address to read/write from + * @req: MTD I/O request + * @iter: NAND I/O iterator + * + * Should be used for iterate over pages that are contained in an MTD request. + */ +#define nanddev_io_for_each_page(nand, start, req, iter) \ + for (nanddev_io_iter_init(nand, start, req, iter); \ + !nanddev_io_iter_end(nand, iter); \ + nanddev_io_iter_next_page(nand, iter)) + +bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos); +bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos); +int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos); +int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos); + +/* BBT related functions */ +enum nand_bbt_block_status { + NAND_BBT_BLOCK_STATUS_UNKNOWN, + NAND_BBT_BLOCK_GOOD, + NAND_BBT_BLOCK_WORN, + NAND_BBT_BLOCK_RESERVED, + NAND_BBT_BLOCK_FACTORY_BAD, + NAND_BBT_BLOCK_NUM_STATUS, +}; + +int nanddev_bbt_init(struct nand_device *nand); +void nanddev_bbt_cleanup(struct nand_device *nand); +int nanddev_bbt_update(struct nand_device *nand); +int nanddev_bbt_get_block_status(const struct nand_device *nand, + unsigned int entry); +int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry, + enum nand_bbt_block_status status); +int nanddev_bbt_markbad(struct nand_device *nand, unsigned int block); + +/** + * nanddev_bbt_pos_to_entry() - Convert a NAND position into a BBT entry + * @nand: NAND device + * @pos: the NAND position we want to get BBT entry for + * + * Return the BBT entry used to store information about the eraseblock pointed + * by @pos. + * + * Return: the BBT entry storing information about eraseblock pointed by @pos. + */ +static inline unsigned int nanddev_bbt_pos_to_entry(struct nand_device *nand, + const struct nand_pos *pos) +{ + return pos->eraseblock + + ((pos->lun + (pos->target * nand->memorg.luns_per_target)) * + nand->memorg.eraseblocks_per_lun); +} + +/** + * nanddev_bbt_is_initialized() - Check if the BBT has been initialized + * @nand: NAND device + * + * Return: true if the BBT has been initialized, false otherwise. + */ +static inline bool nanddev_bbt_is_initialized(struct nand_device *nand) +{ + return !!nand->bbt.cache; +} + +/* MTD -> NAND helper functions. */ +int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo); + +#endif /* __LINUX_MTD_NAND_H */ From f86787280b37e381f8d82f48583434d62dd16e27 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:30:10 +0200 Subject: [PATCH 11/37] mtd: nand: Pass mode information to nand_page_io_req The NAND sub-layers are likely to need the MTD_OPS_XXX mode information in order to decide if they should enable/disable ECC or how they should place the OOB bytes in the provided OOB buffer. Add a field to nand_page_io_req to pass this information. Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal --- include/linux/mtd/nand.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index ada7af4..13e8dd1 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -86,6 +86,7 @@ struct nand_pos { * @ooboffs: the OOB offset within the page * @ooblen: the number of OOB bytes to read from/write to this page * @oobbuf: buffer to store OOB data in or get OOB data from + * @mode: one of the %MTD_OPS_XXX mode * * This object is used to pass per-page I/O requests to NAND sub-layers. This * way all useful information are already formatted in a useful way and @@ -106,6 +107,7 @@ struct nand_page_io_req { const void *out; void *in; } oobbuf; + int mode; }; /** @@ -599,6 +601,7 @@ static inline void nanddev_io_iter_init(struct nand_device *nand, { struct mtd_info *mtd = nanddev_to_mtd(nand); + iter->req.mode = req->mode; iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos); iter->req.ooboffs = req->ooboffs; iter->oobbytes_per_page = mtd_oobavail(mtd, req); From d13f5b254a43e292814a618f60a2696ba01267a7 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:30:11 +0200 Subject: [PATCH 12/37] spi: Extend the core to ease integration of SPI memory controllers Some controllers are exposing high-level interfaces to access various kind of SPI memories. Unfortunately they do not fit in the current spi_controller model and usually have drivers placed in drivers/mtd/spi-nor which are only supporting SPI NORs and not SPI memories in general. This is an attempt at defining a SPI memory interface which works for all kinds of SPI memories (NORs, NANDs, SRAMs). Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi-mem.c | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/spi-mem.h | 258 ++++++++++++++++++++++++++ include/spi.h | 11 ++ 5 files changed, 778 insertions(+) create mode 100644 drivers/spi/spi-mem.c create mode 100644 include/spi-mem.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index dcd719f..9fbd267 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -18,6 +18,13 @@ config DM_SPI if DM_SPI +config SPI_MEM + bool "SPI memory extension" + help + Enable this option if you want to enable the SPI memory extension. + This extension is meant to simplify interaction with SPI memories + by providing an high-level interface to send memory-like commands. + config ALTERA_SPI bool "Altera SPI driver" help diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 728e30c..bdb5b5a 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -8,6 +8,7 @@ ifdef CONFIG_DM_SPI obj-y += spi-uclass.o obj-$(CONFIG_SANDBOX) += spi-emul-uclass.o obj-$(CONFIG_SOFT_SPI) += soft_spi.o +obj-$(CONFIG_SPI_MEM) += spi-mem.o else obj-y += spi.o obj-$(CONFIG_SOFT_SPI) += soft_spi_legacy.o diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c new file mode 100644 index 0000000..af9aef0 --- /dev/null +++ b/drivers/spi/spi-mem.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Exceet Electronics GmbH + * Copyright (C) 2018 Bootlin + * + * Author: Boris Brezillon + */ + +#ifndef __UBOOT__ +#include +#include +#include "internals.h" +#else +#include +#include +#endif + +#ifndef __UBOOT__ +/** + * spi_controller_dma_map_mem_op_data() - DMA-map the buffer attached to a + * memory operation + * @ctlr: the SPI controller requesting this dma_map() + * @op: the memory operation containing the buffer to map + * @sgt: a pointer to a non-initialized sg_table that will be filled by this + * function + * + * Some controllers might want to do DMA on the data buffer embedded in @op. + * This helper prepares everything for you and provides a ready-to-use + * sg_table. This function is not intended to be called from spi drivers. + * Only SPI controller drivers should use it. + * Note that the caller must ensure the memory region pointed by + * op->data.buf.{in,out} is DMA-able before calling this function. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, + const struct spi_mem_op *op, + struct sg_table *sgt) +{ + struct device *dmadev; + + if (!op->data.nbytes) + return -EINVAL; + + if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) + dmadev = ctlr->dma_tx->device->dev; + else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) + dmadev = ctlr->dma_rx->device->dev; + else + dmadev = ctlr->dev.parent; + + if (!dmadev) + return -EINVAL; + + return spi_map_buf(ctlr, dmadev, sgt, op->data.buf.in, op->data.nbytes, + op->data.dir == SPI_MEM_DATA_IN ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); +} +EXPORT_SYMBOL_GPL(spi_controller_dma_map_mem_op_data); + +/** + * spi_controller_dma_unmap_mem_op_data() - DMA-unmap the buffer attached to a + * memory operation + * @ctlr: the SPI controller requesting this dma_unmap() + * @op: the memory operation containing the buffer to unmap + * @sgt: a pointer to an sg_table previously initialized by + * spi_controller_dma_map_mem_op_data() + * + * Some controllers might want to do DMA on the data buffer embedded in @op. + * This helper prepares things so that the CPU can access the + * op->data.buf.{in,out} buffer again. + * + * This function is not intended to be called from SPI drivers. Only SPI + * controller drivers should use it. + * + * This function should be called after the DMA operation has finished and is + * only valid if the previous spi_controller_dma_map_mem_op_data() call + * returned 0. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, + const struct spi_mem_op *op, + struct sg_table *sgt) +{ + struct device *dmadev; + + if (!op->data.nbytes) + return; + + if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) + dmadev = ctlr->dma_tx->device->dev; + else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) + dmadev = ctlr->dma_rx->device->dev; + else + dmadev = ctlr->dev.parent; + + spi_unmap_buf(ctlr, dmadev, sgt, + op->data.dir == SPI_MEM_DATA_IN ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); +} +EXPORT_SYMBOL_GPL(spi_controller_dma_unmap_mem_op_data); +#endif /* __UBOOT__ */ + +static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool tx) +{ + u32 mode = slave->mode; + + switch (buswidth) { + case 1: + return 0; + + case 2: + if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) || + (!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD)))) + return 0; + + break; + + case 4: + if ((tx && (mode & SPI_TX_QUAD)) || + (!tx && (mode & SPI_RX_QUAD))) + return 0; + + break; + + default: + break; + } + + return -ENOTSUPP; +} + +bool spi_mem_default_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + if (spi_check_buswidth_req(slave, op->cmd.buswidth, true)) + return false; + + if (op->addr.nbytes && + spi_check_buswidth_req(slave, op->addr.buswidth, true)) + return false; + + if (op->dummy.nbytes && + spi_check_buswidth_req(slave, op->dummy.buswidth, true)) + return false; + + if (op->data.nbytes && + spi_check_buswidth_req(slave, op->data.buswidth, + op->data.dir == SPI_MEM_DATA_OUT)) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(spi_mem_default_supports_op); + +/** + * spi_mem_supports_op() - Check if a memory device and the controller it is + * connected to support a specific memory operation + * @slave: the SPI device + * @op: the memory operation to check + * + * Some controllers are only supporting Single or Dual IOs, others might only + * support specific opcodes, or it can even be that the controller and device + * both support Quad IOs but the hardware prevents you from using it because + * only 2 IO lines are connected. + * + * This function checks whether a specific operation is supported. + * + * Return: true if @op is supported, false otherwise. + */ +bool spi_mem_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + struct udevice *bus = slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + + if (ops->mem_ops && ops->mem_ops->supports_op) + return ops->mem_ops->supports_op(slave, op); + + return spi_mem_default_supports_op(slave, op); +} +EXPORT_SYMBOL_GPL(spi_mem_supports_op); + +/** + * spi_mem_exec_op() - Execute a memory operation + * @slave: the SPI device + * @op: the memory operation to execute + * + * Executes a memory operation. + * + * This function first checks that @op is supported and then tries to execute + * it. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) +{ + struct udevice *bus = slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + unsigned int pos = 0; + const u8 *tx_buf = NULL; + u8 *rx_buf = NULL; + u8 *op_buf; + int op_len; + u32 flag; + int ret; + int i; + + if (!spi_mem_supports_op(slave, op)) + return -ENOTSUPP; + + if (ops->mem_ops) { +#ifndef __UBOOT__ + /* + * Flush the message queue before executing our SPI memory + * operation to prevent preemption of regular SPI transfers. + */ + spi_flush_queue(ctlr); + + if (ctlr->auto_runtime_pm) { + ret = pm_runtime_get_sync(ctlr->dev.parent); + if (ret < 0) { + dev_err(&ctlr->dev, + "Failed to power device: %d\n", + ret); + return ret; + } + } + + mutex_lock(&ctlr->bus_lock_mutex); + mutex_lock(&ctlr->io_mutex); +#endif + ret = ops->mem_ops->exec_op(slave, op); +#ifndef __UBOOT__ + mutex_unlock(&ctlr->io_mutex); + mutex_unlock(&ctlr->bus_lock_mutex); + + if (ctlr->auto_runtime_pm) + pm_runtime_put(ctlr->dev.parent); +#endif + + /* + * Some controllers only optimize specific paths (typically the + * read path) and expect the core to use the regular SPI + * interface in other cases. + */ + if (!ret || ret != -ENOTSUPP) + return ret; + } + +#ifndef __UBOOT__ + tmpbufsize = sizeof(op->cmd.opcode) + op->addr.nbytes + + op->dummy.nbytes; + + /* + * Allocate a buffer to transmit the CMD, ADDR cycles with kmalloc() so + * we're guaranteed that this buffer is DMA-able, as required by the + * SPI layer. + */ + tmpbuf = kzalloc(tmpbufsize, GFP_KERNEL | GFP_DMA); + if (!tmpbuf) + return -ENOMEM; + + spi_message_init(&msg); + + tmpbuf[0] = op->cmd.opcode; + xfers[xferpos].tx_buf = tmpbuf; + xfers[xferpos].len = sizeof(op->cmd.opcode); + xfers[xferpos].tx_nbits = op->cmd.buswidth; + spi_message_add_tail(&xfers[xferpos], &msg); + xferpos++; + totalxferlen++; + + if (op->addr.nbytes) { + int i; + + for (i = 0; i < op->addr.nbytes; i++) + tmpbuf[i + 1] = op->addr.val >> + (8 * (op->addr.nbytes - i - 1)); + + xfers[xferpos].tx_buf = tmpbuf + 1; + xfers[xferpos].len = op->addr.nbytes; + xfers[xferpos].tx_nbits = op->addr.buswidth; + spi_message_add_tail(&xfers[xferpos], &msg); + xferpos++; + totalxferlen += op->addr.nbytes; + } + + if (op->dummy.nbytes) { + memset(tmpbuf + op->addr.nbytes + 1, 0xff, op->dummy.nbytes); + xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1; + xfers[xferpos].len = op->dummy.nbytes; + xfers[xferpos].tx_nbits = op->dummy.buswidth; + spi_message_add_tail(&xfers[xferpos], &msg); + xferpos++; + totalxferlen += op->dummy.nbytes; + } + + if (op->data.nbytes) { + if (op->data.dir == SPI_MEM_DATA_IN) { + xfers[xferpos].rx_buf = op->data.buf.in; + xfers[xferpos].rx_nbits = op->data.buswidth; + } else { + xfers[xferpos].tx_buf = op->data.buf.out; + xfers[xferpos].tx_nbits = op->data.buswidth; + } + + xfers[xferpos].len = op->data.nbytes; + spi_message_add_tail(&xfers[xferpos], &msg); + xferpos++; + totalxferlen += op->data.nbytes; + } + + ret = spi_sync(slave, &msg); + + kfree(tmpbuf); + + if (ret) + return ret; + + if (msg.actual_length != totalxferlen) + return -EIO; +#else + + /* U-Boot does not support parallel SPI data lanes */ + if ((op->cmd.buswidth != 1) || + (op->addr.nbytes && op->addr.buswidth != 1) || + (op->dummy.nbytes && op->dummy.buswidth != 1) || + (op->data.nbytes && op->data.buswidth != 1)) { + printf("Dual/Quad raw SPI transfers not supported\n"); + return -ENOTSUPP; + } + + if (op->data.nbytes) { + if (op->data.dir == SPI_MEM_DATA_IN) + rx_buf = op->data.buf.in; + else + tx_buf = op->data.buf.out; + } + + op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; + op_buf = calloc(1, op_len); + + ret = spi_claim_bus(slave); + if (ret < 0) + return ret; + + op_buf[pos++] = op->cmd.opcode; + + if (op->addr.nbytes) { + for (i = 0; i < op->addr.nbytes; i++) + op_buf[pos + i] = op->addr.val >> + (8 * (op->addr.nbytes - i - 1)); + + pos += op->addr.nbytes; + } + + if (op->dummy.nbytes) + memset(op_buf + pos, 0xff, op->dummy.nbytes); + + /* 1st transfer: opcode + address + dummy cycles */ + flag = SPI_XFER_BEGIN; + /* Make sure to set END bit if no tx or rx data messages follow */ + if (!tx_buf && !rx_buf) + flag |= SPI_XFER_END; + + ret = spi_xfer(slave, op_len * 8, op_buf, NULL, flag); + if (ret) + return ret; + + /* 2nd transfer: rx or tx data path */ + if (tx_buf || rx_buf) { + ret = spi_xfer(slave, op->data.nbytes * 8, tx_buf, + rx_buf, SPI_XFER_END); + if (ret) + return ret; + } + + spi_release_bus(slave); + + for (i = 0; i < pos; i++) + debug("%02x ", op_buf[i]); + debug("| [%dB %s] ", + tx_buf || rx_buf ? op->data.nbytes : 0, + tx_buf || rx_buf ? (tx_buf ? "out" : "in") : "-"); + for (i = 0; i < op->data.nbytes; i++) + debug("%02x ", tx_buf ? tx_buf[i] : rx_buf[i]); + debug("[ret %d]\n", ret); + + free(op_buf); + + if (ret < 0) + return ret; +#endif /* __UBOOT__ */ + + return 0; +} +EXPORT_SYMBOL_GPL(spi_mem_exec_op); + +/** + * spi_mem_adjust_op_size() - Adjust the data size of a SPI mem operation to + * match controller limitations + * @slave: the SPI device + * @op: the operation to adjust + * + * Some controllers have FIFO limitations and must split a data transfer + * operation into multiple ones, others require a specific alignment for + * optimized accesses. This function allows SPI mem drivers to split a single + * operation into multiple sub-operations when required. + * + * Return: a negative error code if the controller can't properly adjust @op, + * 0 otherwise. Note that @op->data.nbytes will be updated if @op + * can't be handled in a single step. + */ +int spi_mem_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op) +{ + struct udevice *bus = slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + + if (ops->mem_ops && ops->mem_ops->adjust_op_size) + return ops->mem_ops->adjust_op_size(slave, op); + + return 0; +} +EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size); + +#ifndef __UBOOT__ +static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv) +{ + return container_of(drv, struct spi_mem_driver, spidrv.driver); +} + +static int spi_mem_probe(struct spi_device *spi) +{ + struct spi_mem_driver *memdrv = to_spi_mem_drv(spi->dev.driver); + struct spi_mem *mem; + + mem = devm_kzalloc(&spi->dev, sizeof(*mem), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + mem->spi = spi; + spi_set_drvdata(spi, mem); + + return memdrv->probe(mem); +} + +static int spi_mem_remove(struct spi_device *spi) +{ + struct spi_mem_driver *memdrv = to_spi_mem_drv(spi->dev.driver); + struct spi_mem *mem = spi_get_drvdata(spi); + + if (memdrv->remove) + return memdrv->remove(mem); + + return 0; +} + +static void spi_mem_shutdown(struct spi_device *spi) +{ + struct spi_mem_driver *memdrv = to_spi_mem_drv(spi->dev.driver); + struct spi_mem *mem = spi_get_drvdata(spi); + + if (memdrv->shutdown) + memdrv->shutdown(mem); +} + +/** + * spi_mem_driver_register_with_owner() - Register a SPI memory driver + * @memdrv: the SPI memory driver to register + * @owner: the owner of this driver + * + * Registers a SPI memory driver. + * + * Return: 0 in case of success, a negative error core otherwise. + */ + +int spi_mem_driver_register_with_owner(struct spi_mem_driver *memdrv, + struct module *owner) +{ + memdrv->spidrv.probe = spi_mem_probe; + memdrv->spidrv.remove = spi_mem_remove; + memdrv->spidrv.shutdown = spi_mem_shutdown; + + return __spi_register_driver(owner, &memdrv->spidrv); +} +EXPORT_SYMBOL_GPL(spi_mem_driver_register_with_owner); + +/** + * spi_mem_driver_unregister_with_owner() - Unregister a SPI memory driver + * @memdrv: the SPI memory driver to unregister + * + * Unregisters a SPI memory driver. + */ +void spi_mem_driver_unregister(struct spi_mem_driver *memdrv) +{ + spi_unregister_driver(&memdrv->spidrv); +} +EXPORT_SYMBOL_GPL(spi_mem_driver_unregister); +#endif /* __UBOOT__ */ diff --git a/include/spi-mem.h b/include/spi-mem.h new file mode 100644 index 0000000..36814ef --- /dev/null +++ b/include/spi-mem.h @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Exceet Electronics GmbH + * Copyright (C) 2018 Bootlin + * + * Author: + * Peter Pan + * Boris Brezillon + */ + +#ifndef __UBOOT_SPI_MEM_H +#define __UBOOT_SPI_MEM_H + +#include +#include +#include +#include + +#define SPI_MEM_OP_CMD(__opcode, __buswidth) \ + { \ + .buswidth = __buswidth, \ + .opcode = __opcode, \ + } + +#define SPI_MEM_OP_ADDR(__nbytes, __val, __buswidth) \ + { \ + .nbytes = __nbytes, \ + .val = __val, \ + .buswidth = __buswidth, \ + } + +#define SPI_MEM_OP_NO_ADDR { } + +#define SPI_MEM_OP_DUMMY(__nbytes, __buswidth) \ + { \ + .nbytes = __nbytes, \ + .buswidth = __buswidth, \ + } + +#define SPI_MEM_OP_NO_DUMMY { } + +#define SPI_MEM_OP_DATA_IN(__nbytes, __buf, __buswidth) \ + { \ + .dir = SPI_MEM_DATA_IN, \ + .nbytes = __nbytes, \ + .buf.in = __buf, \ + .buswidth = __buswidth, \ + } + +#define SPI_MEM_OP_DATA_OUT(__nbytes, __buf, __buswidth) \ + { \ + .dir = SPI_MEM_DATA_OUT, \ + .nbytes = __nbytes, \ + .buf.out = __buf, \ + .buswidth = __buswidth, \ + } + +#define SPI_MEM_OP_NO_DATA { } + +/** + * enum spi_mem_data_dir - describes the direction of a SPI memory data + * transfer from the controller perspective + * @SPI_MEM_DATA_IN: data coming from the SPI memory + * @SPI_MEM_DATA_OUT: data sent the SPI memory + */ +enum spi_mem_data_dir { + SPI_MEM_DATA_IN, + SPI_MEM_DATA_OUT, +}; + +/** + * struct spi_mem_op - describes a SPI memory operation + * @cmd.buswidth: number of IO lines used to transmit the command + * @cmd.opcode: operation opcode + * @addr.nbytes: number of address bytes to send. Can be zero if the operation + * does not need to send an address + * @addr.buswidth: number of IO lines used to transmit the address cycles + * @addr.val: address value. This value is always sent MSB first on the bus. + * Note that only @addr.nbytes are taken into account in this + * address value, so users should make sure the value fits in the + * assigned number of bytes. + * @dummy.nbytes: number of dummy bytes to send after an opcode or address. Can + * be zero if the operation does not require dummy bytes + * @dummy.buswidth: number of IO lanes used to transmit the dummy bytes + * @data.buswidth: number of IO lanes used to send/receive the data + * @data.dir: direction of the transfer + * @data.buf.in: input buffer + * @data.buf.out: output buffer + */ +struct spi_mem_op { + struct { + u8 buswidth; + u8 opcode; + } cmd; + + struct { + u8 nbytes; + u8 buswidth; + u64 val; + } addr; + + struct { + u8 nbytes; + u8 buswidth; + } dummy; + + struct { + u8 buswidth; + enum spi_mem_data_dir dir; + unsigned int nbytes; + /* buf.{in,out} must be DMA-able. */ + union { + void *in; + const void *out; + } buf; + } data; +}; + +#define SPI_MEM_OP(__cmd, __addr, __dummy, __data) \ + { \ + .cmd = __cmd, \ + .addr = __addr, \ + .dummy = __dummy, \ + .data = __data, \ + } + +#ifndef __UBOOT__ +/** + * struct spi_mem - describes a SPI memory device + * @spi: the underlying SPI device + * @drvpriv: spi_mem_driver private data + * + * Extra information that describe the SPI memory device and may be needed by + * the controller to properly handle this device should be placed here. + * + * One example would be the device size since some controller expose their SPI + * mem devices through a io-mapped region. + */ +struct spi_mem { + struct udevice *dev; + void *drvpriv; +}; + +/** + * struct spi_mem_set_drvdata() - attach driver private data to a SPI mem + * device + * @mem: memory device + * @data: data to attach to the memory device + */ +static inline void spi_mem_set_drvdata(struct spi_mem *mem, void *data) +{ + mem->drvpriv = data; +} + +/** + * struct spi_mem_get_drvdata() - get driver private data attached to a SPI mem + * device + * @mem: memory device + * + * Return: the data attached to the mem device. + */ +static inline void *spi_mem_get_drvdata(struct spi_mem *mem) +{ + return mem->drvpriv; +} +#endif /* __UBOOT__ */ + +/** + * struct spi_controller_mem_ops - SPI memory operations + * @adjust_op_size: shrink the data xfer of an operation to match controller's + * limitations (can be alignment of max RX/TX size + * limitations) + * @supports_op: check if an operation is supported by the controller + * @exec_op: execute a SPI memory operation + * + * This interface should be implemented by SPI controllers providing an + * high-level interface to execute SPI memory operation, which is usually the + * case for QSPI controllers. + */ +struct spi_controller_mem_ops { + int (*adjust_op_size)(struct spi_slave *slave, struct spi_mem_op *op); + bool (*supports_op)(struct spi_slave *slave, + const struct spi_mem_op *op); + int (*exec_op)(struct spi_slave *slave, + const struct spi_mem_op *op); +}; + +#ifndef __UBOOT__ +/** + * struct spi_mem_driver - SPI memory driver + * @spidrv: inherit from a SPI driver + * @probe: probe a SPI memory. Usually where detection/initialization takes + * place + * @remove: remove a SPI memory + * @shutdown: take appropriate action when the system is shutdown + * + * This is just a thin wrapper around a spi_driver. The core takes care of + * allocating the spi_mem object and forwarding the probe/remove/shutdown + * request to the spi_mem_driver. The reason we use this wrapper is because + * we might have to stuff more information into the spi_mem struct to let + * SPI controllers know more about the SPI memory they interact with, and + * having this intermediate layer allows us to do that without adding more + * useless fields to the spi_device object. + */ +struct spi_mem_driver { + struct spi_driver spidrv; + int (*probe)(struct spi_mem *mem); + int (*remove)(struct spi_mem *mem); + void (*shutdown)(struct spi_mem *mem); +}; + +#if IS_ENABLED(CONFIG_SPI_MEM) +int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, + const struct spi_mem_op *op, + struct sg_table *sg); + +void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, + const struct spi_mem_op *op, + struct sg_table *sg); +#else +static inline int +spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, + const struct spi_mem_op *op, + struct sg_table *sg) +{ + return -ENOTSUPP; +} + +static inline void +spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, + const struct spi_mem_op *op, + struct sg_table *sg) +{ +} +#endif /* CONFIG_SPI_MEM */ +#endif /* __UBOOT__ */ + +int spi_mem_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op); + +bool spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op); + +int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op); + +#ifndef __UBOOT__ +int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv, + struct module *owner); + +void spi_mem_driver_unregister(struct spi_mem_driver *drv); + +#define spi_mem_driver_register(__drv) \ + spi_mem_driver_register_with_owner(__drv, THIS_MODULE) + +#define module_spi_mem_driver(__drv) \ + module_driver(__drv, spi_mem_driver_register, \ + spi_mem_driver_unregister) +#endif + +#endif /* __LINUX_SPI_MEM_H */ diff --git a/include/spi.h b/include/spi.h index 9754c53..938627b 100644 --- a/include/spi.h +++ b/include/spi.h @@ -9,6 +9,8 @@ #ifndef _SPI_H_ #define _SPI_H_ +#include + /* SPI mode flags */ #define SPI_CPHA BIT(0) /* clock phase */ #define SPI_CPOL BIT(1) /* clock polarity */ @@ -403,6 +405,15 @@ struct dm_spi_ops { void *din, unsigned long flags); /** + * Optimized handlers for SPI memory-like operations. + * + * Optimized/dedicated operations for interactions with SPI memory. This + * field is optional and should only be implemented if the controller + * has native support for memory like operations. + */ + const struct spi_controller_mem_ops *mem_ops; + + /** * Set transfer speed. * This sets a new speed to be applied for next spi_xfer(). * @bus: The SPI bus From 0a6d6bae03864938f073cc114992c40f2338a155 Mon Sep 17 00:00:00 2001 From: Peter Pan Date: Thu, 16 Aug 2018 17:30:12 +0200 Subject: [PATCH 13/37] mtd: nand: Add core infrastructure to support SPI NANDs Add a SPI NAND framework based on the generic NAND framework and the spi-mem infrastructure. In its current state, this framework supports the following features: - single/dual/quad IO modes - on-die ECC Signed-off-by: Peter Pan Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/Kconfig | 2 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/spi/Kconfig | 7 + drivers/mtd/nand/spi/Makefile | 4 + drivers/mtd/nand/spi/core.c | 1235 +++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 427 ++++++++++++++ 6 files changed, 1676 insertions(+) create mode 100644 drivers/mtd/nand/spi/Kconfig create mode 100644 drivers/mtd/nand/spi/Makefile create mode 100644 drivers/mtd/nand/spi/core.c create mode 100644 include/linux/mtd/spinand.h diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 1c1a1f4..78ae04b 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -2,3 +2,5 @@ config MTD_NAND_CORE tristate source "drivers/mtd/nand/raw/Kconfig" + +source "drivers/mtd/nand/spi/Kconfig" diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index cd492db..a358bc6 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -2,3 +2,4 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o +obj-$(CONFIG_MTD_SPI_NAND) += spi/ diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig new file mode 100644 index 0000000..2197cb5 --- /dev/null +++ b/drivers/mtd/nand/spi/Kconfig @@ -0,0 +1,7 @@ +menuconfig MTD_SPI_NAND + bool "SPI NAND device Support" + depends on MTD && DM_SPI + select MTD_NAND_CORE + select SPI_MEM + help + This is the framework for the SPI NAND device drivers. diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile new file mode 100644 index 0000000..f0c6e69 --- /dev/null +++ b/drivers/mtd/nand/spi/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +spinand-objs := core.o +obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c new file mode 100644 index 0000000..08f853a --- /dev/null +++ b/drivers/mtd/nand/spi/core.c @@ -0,0 +1,1235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan + * Boris Brezillon + */ + +#define pr_fmt(fmt) "spi-nand: " fmt + +#ifndef __UBOOT__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +/* SPI NAND index visible in MTD names */ +static int spi_nand_idx; + +static void spinand_cache_op_adjust_colum(struct spinand_device *spinand, + const struct nand_page_io_req *req, + u16 *column) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int shift; + + if (nand->memorg.planes_per_lun < 2) + return; + + /* The plane number is passed in MSB just above the column address */ + shift = fls(nand->memorg.pagesize); + *column |= req->pos.plane << shift; +} + +static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) +{ + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg, + spinand->scratchbuf); + int ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + *val = *spinand->scratchbuf; + return 0; +} + +static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) +{ + struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg, + spinand->scratchbuf); + + *spinand->scratchbuf = val; + return spi_mem_exec_op(spinand->slave, &op); +} + +static int spinand_read_status(struct spinand_device *spinand, u8 *status) +{ + return spinand_read_reg_op(spinand, REG_STATUS, status); +} + +static int spinand_get_cfg(struct spinand_device *spinand, u8 *cfg) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + if (WARN_ON(spinand->cur_target < 0 || + spinand->cur_target >= nand->memorg.ntargets)) + return -EINVAL; + + *cfg = spinand->cfg_cache[spinand->cur_target]; + return 0; +} + +static int spinand_set_cfg(struct spinand_device *spinand, u8 cfg) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + if (WARN_ON(spinand->cur_target < 0 || + spinand->cur_target >= nand->memorg.ntargets)) + return -EINVAL; + + if (spinand->cfg_cache[spinand->cur_target] == cfg) + return 0; + + ret = spinand_write_reg_op(spinand, REG_CFG, cfg); + if (ret) + return ret; + + spinand->cfg_cache[spinand->cur_target] = cfg; + return 0; +} + +/** + * spinand_upd_cfg() - Update the configuration register + * @spinand: the spinand device + * @mask: the mask encoding the bits to update in the config reg + * @val: the new value to apply + * + * Update the configuration register. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val) +{ + int ret; + u8 cfg; + + ret = spinand_get_cfg(spinand, &cfg); + if (ret) + return ret; + + cfg &= ~mask; + cfg |= val; + + return spinand_set_cfg(spinand, cfg); +} + +/** + * spinand_select_target() - Select a specific NAND target/die + * @spinand: the spinand device + * @target: the target/die to select + * + * Select a new target/die. If chip only has one die, this function is a NOOP. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_select_target(struct spinand_device *spinand, unsigned int target) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + if (WARN_ON(target >= nand->memorg.ntargets)) + return -EINVAL; + + if (spinand->cur_target == target) + return 0; + + if (nand->memorg.ntargets == 1) { + spinand->cur_target = target; + return 0; + } + + ret = spinand->select_target(spinand, target); + if (ret) + return ret; + + spinand->cur_target = target; + return 0; +} + +static int spinand_init_cfg_cache(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct udevice *dev = spinand->slave->dev; + unsigned int target; + int ret; + + spinand->cfg_cache = devm_kzalloc(dev, + sizeof(*spinand->cfg_cache) * + nand->memorg.ntargets, + GFP_KERNEL); + if (!spinand->cfg_cache) + return -ENOMEM; + + for (target = 0; target < nand->memorg.ntargets; target++) { + ret = spinand_select_target(spinand, target); + if (ret) + return ret; + + /* + * We use spinand_read_reg_op() instead of spinand_get_cfg() + * here to bypass the config cache. + */ + ret = spinand_read_reg_op(spinand, REG_CFG, + &spinand->cfg_cache[target]); + if (ret) + return ret; + } + + return 0; +} + +static int spinand_init_quad_enable(struct spinand_device *spinand) +{ + bool enable = false; + + if (!(spinand->flags & SPINAND_HAS_QE_BIT)) + return 0; + + if (spinand->op_templates.read_cache->data.buswidth == 4 || + spinand->op_templates.write_cache->data.buswidth == 4 || + spinand->op_templates.update_cache->data.buswidth == 4) + enable = true; + + return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, + enable ? CFG_QUAD_ENABLE : 0); +} + +static int spinand_ecc_enable(struct spinand_device *spinand, + bool enable) +{ + return spinand_upd_cfg(spinand, CFG_ECC_ENABLE, + enable ? CFG_ECC_ENABLE : 0); +} + +static int spinand_write_enable_op(struct spinand_device *spinand) +{ + struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true); + + return spi_mem_exec_op(spinand->slave, &op); +} + +static int spinand_load_page_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, &req->pos); + struct spi_mem_op op = SPINAND_PAGE_READ_OP(row); + + return spi_mem_exec_op(spinand->slave, &op); +} + +static int spinand_read_from_cache_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct spi_mem_op op = *spinand->op_templates.read_cache; + struct nand_device *nand = spinand_to_nand(spinand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct nand_page_io_req adjreq = *req; + unsigned int nbytes = 0; + void *buf = NULL; + u16 column = 0; + int ret; + + if (req->datalen) { + adjreq.datalen = nanddev_page_size(nand); + adjreq.dataoffs = 0; + adjreq.databuf.in = spinand->databuf; + buf = spinand->databuf; + nbytes = adjreq.datalen; + } + + if (req->ooblen) { + adjreq.ooblen = nanddev_per_page_oobsize(nand); + adjreq.ooboffs = 0; + adjreq.oobbuf.in = spinand->oobbuf; + nbytes += nanddev_per_page_oobsize(nand); + if (!buf) { + buf = spinand->oobbuf; + column = nanddev_page_size(nand); + } + } + + spinand_cache_op_adjust_colum(spinand, &adjreq, &column); + op.addr.val = column; + + /* + * Some controllers are limited in term of max RX data size. In this + * case, just repeat the READ_CACHE operation after updating the + * column. + */ + while (nbytes) { + op.data.buf.in = buf; + op.data.nbytes = nbytes; + ret = spi_mem_adjust_op_size(spinand->slave, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + buf += op.data.nbytes; + nbytes -= op.data.nbytes; + op.addr.val += op.data.nbytes; + } + + if (req->datalen) + memcpy(req->databuf.in, spinand->databuf + req->dataoffs, + req->datalen); + + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_get_databytes(mtd, req->oobbuf.in, + spinand->oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs, + req->ooblen); + } + + return 0; +} + +static int spinand_write_to_cache_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct spi_mem_op op = *spinand->op_templates.write_cache; + struct nand_device *nand = spinand_to_nand(spinand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct nand_page_io_req adjreq = *req; + unsigned int nbytes = 0; + void *buf = NULL; + u16 column = 0; + int ret; + + memset(spinand->databuf, 0xff, + nanddev_page_size(nand) + + nanddev_per_page_oobsize(nand)); + + if (req->datalen) { + memcpy(spinand->databuf + req->dataoffs, req->databuf.out, + req->datalen); + adjreq.dataoffs = 0; + adjreq.datalen = nanddev_page_size(nand); + adjreq.databuf.out = spinand->databuf; + nbytes = adjreq.datalen; + buf = spinand->databuf; + } + + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_set_databytes(mtd, req->oobbuf.out, + spinand->oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(spinand->oobbuf + req->ooboffs, req->oobbuf.out, + req->ooblen); + + adjreq.ooblen = nanddev_per_page_oobsize(nand); + adjreq.ooboffs = 0; + nbytes += nanddev_per_page_oobsize(nand); + if (!buf) { + buf = spinand->oobbuf; + column = nanddev_page_size(nand); + } + } + + spinand_cache_op_adjust_colum(spinand, &adjreq, &column); + + op = *spinand->op_templates.write_cache; + op.addr.val = column; + + /* + * Some controllers are limited in term of max TX data size. In this + * case, split the operation into one LOAD CACHE and one or more + * LOAD RANDOM CACHE. + */ + while (nbytes) { + op.data.buf.out = buf; + op.data.nbytes = nbytes; + + ret = spi_mem_adjust_op_size(spinand->slave, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + buf += op.data.nbytes; + nbytes -= op.data.nbytes; + op.addr.val += op.data.nbytes; + + /* + * We need to use the RANDOM LOAD CACHE operation if there's + * more than one iteration, because the LOAD operation resets + * the cache to 0xff. + */ + if (nbytes) { + column = op.addr.val; + op = *spinand->op_templates.update_cache; + op.addr.val = column; + } + } + + return 0; +} + +static int spinand_program_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, &req->pos); + struct spi_mem_op op = SPINAND_PROG_EXEC_OP(row); + + return spi_mem_exec_op(spinand->slave, &op); +} + +static int spinand_erase_op(struct spinand_device *spinand, + const struct nand_pos *pos) +{ + struct nand_device *nand = &spinand->base; + unsigned int row = nanddev_pos_to_row(nand, pos); + struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row); + + return spi_mem_exec_op(spinand->slave, &op); +} + +static int spinand_wait(struct spinand_device *spinand, u8 *s) +{ + unsigned long start, stop; + u8 status; + int ret; + + start = get_timer(0); + stop = 400; + do { + ret = spinand_read_status(spinand, &status); + if (ret) + return ret; + + if (!(status & STATUS_BUSY)) + goto out; + } while (get_timer(start) < stop); + + /* + * Extra read, just in case the STATUS_READY bit has changed + * since our last check + */ + ret = spinand_read_status(spinand, &status); + if (ret) + return ret; + +out: + if (s) + *s = status; + + return status & STATUS_BUSY ? -ETIMEDOUT : 0; +} + +static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf) +{ + struct spi_mem_op op = SPINAND_READID_OP(0, spinand->scratchbuf, + SPINAND_MAX_ID_LEN); + int ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (!ret) + memcpy(buf, spinand->scratchbuf, SPINAND_MAX_ID_LEN); + + return ret; +} + +static int spinand_reset_op(struct spinand_device *spinand) +{ + struct spi_mem_op op = SPINAND_RESET_OP; + int ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + return spinand_wait(spinand, NULL); +} + +static int spinand_lock_block(struct spinand_device *spinand, u8 lock) +{ + return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock); +} + +static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + if (spinand->eccinfo.get_status) + return spinand->eccinfo.get_status(spinand, status); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * We have no way to know exactly how many bitflips have been + * fixed, so let's return the maximum possible value so that + * wear-leveling layers move the data immediately. + */ + return nand->eccreq.strength; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int spinand_read_page(struct spinand_device *spinand, + const struct nand_page_io_req *req, + bool ecc_enabled) +{ + u8 status; + int ret; + + ret = spinand_load_page_op(spinand, req); + if (ret) + return ret; + + ret = spinand_wait(spinand, &status); + if (ret < 0) + return ret; + + ret = spinand_read_from_cache_op(spinand, req); + if (ret) + return ret; + + if (!ecc_enabled) + return 0; + + return spinand_check_ecc_status(spinand, status); +} + +static int spinand_write_page(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + u8 status; + int ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_write_to_cache_op(spinand, req); + if (ret) + return ret; + + ret = spinand_program_op(spinand, req); + if (ret) + return ret; + + ret = spinand_wait(spinand, &status); + if (!ret && (status & STATUS_PROG_FAILED)) + ret = -EIO; + + return ret; +} + +static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int max_bitflips = 0; + struct nand_io_iter iter; + bool enable_ecc = false; + bool ecc_failed = false; + int ret = 0; + + if (ops->mode != MTD_OPS_RAW && spinand->eccinfo.ooblayout) + enable_ecc = true; + +#ifndef __UBOOT__ + mutex_lock(&spinand->lock); +#endif + + nanddev_io_for_each_page(nand, from, ops, &iter) { + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + break; + + ret = spinand_ecc_enable(spinand, enable_ecc); + if (ret) + break; + + ret = spinand_read_page(spinand, &iter.req, enable_ecc); + if (ret < 0 && ret != -EBADMSG) + break; + + if (ret == -EBADMSG) { + ecc_failed = true; + mtd->ecc_stats.failed++; + ret = 0; + } else { + mtd->ecc_stats.corrected += ret; + max_bitflips = max_t(unsigned int, max_bitflips, ret); + } + + ops->retlen += iter.req.datalen; + ops->oobretlen += iter.req.ooblen; + } + +#ifndef __UBOOT__ + mutex_unlock(&spinand->lock); +#endif + if (ecc_failed && !ret) + ret = -EBADMSG; + + return ret ? ret : max_bitflips; +} + +static int spinand_mtd_write(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_io_iter iter; + bool enable_ecc = false; + int ret = 0; + + if (ops->mode != MTD_OPS_RAW && mtd->ooblayout) + enable_ecc = true; + +#ifndef __UBOOT__ + mutex_lock(&spinand->lock); +#endif + + nanddev_io_for_each_page(nand, to, ops, &iter) { + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + break; + + ret = spinand_ecc_enable(spinand, enable_ecc); + if (ret) + break; + + ret = spinand_write_page(spinand, &iter.req); + if (ret) + break; + + ops->retlen += iter.req.datalen; + ops->oobretlen += iter.req.ooblen; + } + +#ifndef __UBOOT__ + mutex_unlock(&spinand->lock); +#endif + + return ret; +} + +static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_page_io_req req = { + .pos = *pos, + .ooblen = 2, + .ooboffs = 0, + .oobbuf.in = spinand->oobbuf, + .mode = MTD_OPS_RAW, + }; + int ret; + + memset(spinand->oobbuf, 0, 2); + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_read_page(spinand, &req, false); + if (ret) + return ret; + + if (spinand->oobbuf[0] != 0xff || spinand->oobbuf[1] != 0xff) + return true; + + return false; +} + +static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); +#ifndef __UBOOT__ + struct spinand_device *spinand = nand_to_spinand(nand); +#endif + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); +#ifndef __UBOOT__ + mutex_lock(&spinand->lock); +#endif + ret = nanddev_isbad(nand, &pos); +#ifndef __UBOOT__ + mutex_unlock(&spinand->lock); +#endif + return ret; +} + +static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_page_io_req req = { + .pos = *pos, + .ooboffs = 0, + .ooblen = 2, + .oobbuf.out = spinand->oobbuf, + }; + int ret; + + /* Erase block before marking it bad. */ + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_erase_op(spinand, pos); + if (ret) + return ret; + + memset(spinand->oobbuf, 0, 2); + return spinand_write_page(spinand, &req); +} + +static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); +#ifndef __UBOOT__ + struct spinand_device *spinand = nand_to_spinand(nand); +#endif + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); +#ifndef __UBOOT__ + mutex_lock(&spinand->lock); +#endif + ret = nanddev_markbad(nand, &pos); +#ifndef __UBOOT__ + mutex_unlock(&spinand->lock); +#endif + return ret; +} + +static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 status; + int ret; + + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_erase_op(spinand, pos); + if (ret) + return ret; + + ret = spinand_wait(spinand, &status); + if (!ret && (status & STATUS_ERASE_FAILED)) + ret = -EIO; + + return ret; +} + +static int spinand_mtd_erase(struct mtd_info *mtd, + struct erase_info *einfo) +{ +#ifndef __UBOOT__ + struct spinand_device *spinand = mtd_to_spinand(mtd); +#endif + int ret; + +#ifndef __UBOOT__ + mutex_lock(&spinand->lock); +#endif + ret = nanddev_mtd_erase(mtd, einfo); +#ifndef __UBOOT__ + mutex_unlock(&spinand->lock); +#endif + + return ret; +} + +static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs) +{ +#ifndef __UBOOT__ + struct spinand_device *spinand = mtd_to_spinand(mtd); +#endif + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); +#ifndef __UBOOT__ + mutex_lock(&spinand->lock); +#endif + ret = nanddev_isreserved(nand, &pos); +#ifndef __UBOOT__ + mutex_unlock(&spinand->lock); +#endif + + return ret; +} + +const struct spi_mem_op * +spinand_find_supported_op(struct spinand_device *spinand, + const struct spi_mem_op *ops, + unsigned int nops) +{ + unsigned int i; + + for (i = 0; i < nops; i++) { + if (spi_mem_supports_op(spinand->slave, &ops[i])) + return &ops[i]; + } + + return NULL; +} + +static const struct nand_ops spinand_ops = { + .erase = spinand_erase, + .markbad = spinand_markbad, + .isbad = spinand_isbad, +}; + +static int spinand_manufacturer_detect(struct spinand_device *spinand) +{ + return -ENOTSUPP; +} + +static int spinand_manufacturer_init(struct spinand_device *spinand) +{ + if (spinand->manufacturer->ops->init) + return spinand->manufacturer->ops->init(spinand); + + return 0; +} + +static void spinand_manufacturer_cleanup(struct spinand_device *spinand) +{ + /* Release manufacturer private data */ + if (spinand->manufacturer->ops->cleanup) + return spinand->manufacturer->ops->cleanup(spinand); +} + +static const struct spi_mem_op * +spinand_select_op_variant(struct spinand_device *spinand, + const struct spinand_op_variants *variants) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + for (i = 0; i < variants->nops; i++) { + struct spi_mem_op op = variants->ops[i]; + unsigned int nbytes; + int ret; + + nbytes = nanddev_per_page_oobsize(nand) + + nanddev_page_size(nand); + + while (nbytes) { + op.data.nbytes = nbytes; + ret = spi_mem_adjust_op_size(spinand->slave, &op); + if (ret) + break; + + if (!spi_mem_supports_op(spinand->slave, &op)) + break; + + nbytes -= op.data.nbytes; + } + + if (!nbytes) + return &variants->ops[i]; + } + + return NULL; +} + +/** + * spinand_match_and_init() - Try to find a match between a device ID and an + * entry in a spinand_info table + * @spinand: SPI NAND object + * @table: SPI NAND device description table + * @table_size: size of the device description table + * + * Should be used by SPI NAND manufacturer drivers when they want to find a + * match between a device ID retrieved through the READ_ID command and an + * entry in the SPI NAND description table. If a match is found, the spinand + * object will be initialized with information provided by the matching + * spinand_info entry. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_match_and_init(struct spinand_device *spinand, + const struct spinand_info *table, + unsigned int table_size, u8 devid) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + for (i = 0; i < table_size; i++) { + const struct spinand_info *info = &table[i]; + const struct spi_mem_op *op; + + if (devid != info->devid) + continue; + + nand->memorg = table[i].memorg; + nand->eccreq = table[i].eccreq; + spinand->eccinfo = table[i].eccinfo; + spinand->flags = table[i].flags; + spinand->select_target = table[i].select_target; + + op = spinand_select_op_variant(spinand, + info->op_variants.read_cache); + if (!op) + return -ENOTSUPP; + + spinand->op_templates.read_cache = op; + + op = spinand_select_op_variant(spinand, + info->op_variants.write_cache); + if (!op) + return -ENOTSUPP; + + spinand->op_templates.write_cache = op; + + op = spinand_select_op_variant(spinand, + info->op_variants.update_cache); + spinand->op_templates.update_cache = op; + + return 0; + } + + return -ENOTSUPP; +} + +static int spinand_detect(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + ret = spinand_reset_op(spinand); + if (ret) + return ret; + + ret = spinand_read_id_op(spinand, spinand->id.data); + if (ret) + return ret; + + spinand->id.len = SPINAND_MAX_ID_LEN; + + ret = spinand_manufacturer_detect(spinand); + if (ret) { + dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN, + spinand->id.data); + return ret; + } + + if (nand->memorg.ntargets > 1 && !spinand->select_target) { + dev_err(dev, + "SPI NANDs with more than one die must implement ->select_target()\n"); + return -EINVAL; + } + + dev_info(spinand->slave->dev, + "%s SPI NAND was found.\n", spinand->manufacturer->name); + dev_info(spinand->slave->dev, + "%llu MiB, block size: %zu KiB, page size: %zu, OOB size: %u\n", + nanddev_size(nand) >> 20, nanddev_eraseblock_size(nand) >> 10, + nanddev_page_size(nand), nanddev_per_page_oobsize(nand)); + + return 0; +} + +static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = 62; + + return 0; +} + +static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = { + .ecc = spinand_noecc_ooblayout_ecc, + .free = spinand_noecc_ooblayout_free, +}; + +static int spinand_init(struct spinand_device *spinand) +{ + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct nand_device *nand = mtd_to_nanddev(mtd); + int ret, i; + + /* + * We need a scratch buffer because the spi_mem interface requires that + * buf passed in spi_mem_op->data.buf be DMA-able. + */ + spinand->scratchbuf = kzalloc(SPINAND_MAX_ID_LEN, GFP_KERNEL); + if (!spinand->scratchbuf) + return -ENOMEM; + + ret = spinand_detect(spinand); + if (ret) + goto err_free_bufs; + + /* + * Use kzalloc() instead of devm_kzalloc() here, because some drivers + * may use this buffer for DMA access. + * Memory allocated by devm_ does not guarantee DMA-safe alignment. + */ + spinand->databuf = kzalloc(nanddev_page_size(nand) + + nanddev_per_page_oobsize(nand), + GFP_KERNEL); + if (!spinand->databuf) { + ret = -ENOMEM; + goto err_free_bufs; + } + + spinand->oobbuf = spinand->databuf + nanddev_page_size(nand); + + ret = spinand_init_cfg_cache(spinand); + if (ret) + goto err_free_bufs; + + ret = spinand_init_quad_enable(spinand); + if (ret) + goto err_free_bufs; + + ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0); + if (ret) + goto err_free_bufs; + + ret = spinand_manufacturer_init(spinand); + if (ret) { + dev_err(dev, + "Failed to initialize the SPI NAND chip (err = %d)\n", + ret); + goto err_free_bufs; + } + + /* After power up, all blocks are locked, so unlock them here. */ + for (i = 0; i < nand->memorg.ntargets; i++) { + ret = spinand_select_target(spinand, i); + if (ret) + goto err_free_bufs; + + ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED); + if (ret) + goto err_free_bufs; + } + + ret = nanddev_init(nand, &spinand_ops, THIS_MODULE); + if (ret) + goto err_manuf_cleanup; + + /* + * Right now, we don't support ECC, so let the whole oob + * area is available for user. + */ + mtd->_read_oob = spinand_mtd_read; + mtd->_write_oob = spinand_mtd_write; + mtd->_block_isbad = spinand_mtd_block_isbad; + mtd->_block_markbad = spinand_mtd_block_markbad; + mtd->_block_isreserved = spinand_mtd_block_isreserved; + mtd->_erase = spinand_mtd_erase; + + if (spinand->eccinfo.ooblayout) + mtd_set_ooblayout(mtd, spinand->eccinfo.ooblayout); + else + mtd_set_ooblayout(mtd, &spinand_noecc_ooblayout); + + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + goto err_cleanup_nanddev; + + mtd->oobavail = ret; + + return 0; + +err_cleanup_nanddev: + nanddev_cleanup(nand); + +err_manuf_cleanup: + spinand_manufacturer_cleanup(spinand); + +err_free_bufs: + kfree(spinand->databuf); + kfree(spinand->scratchbuf); + return ret; +} + +static void spinand_cleanup(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + nanddev_cleanup(nand); + spinand_manufacturer_cleanup(spinand); + kfree(spinand->databuf); + kfree(spinand->scratchbuf); +} + +static int spinand_probe(struct udevice *dev) +{ + struct spinand_device *spinand = dev_get_priv(dev); + struct spi_slave *slave = dev_get_parent_priv(dev); + struct mtd_info *mtd = dev_get_uclass_priv(dev); + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + +#ifndef __UBOOT__ + spinand = devm_kzalloc(&mem->spi->dev, sizeof(*spinand), + GFP_KERNEL); + if (!spinand) + return -ENOMEM; + + spinand->spimem = mem; + spi_mem_set_drvdata(mem, spinand); + spinand_set_of_node(spinand, mem->spi->dev.of_node); + mutex_init(&spinand->lock); + + mtd = spinand_to_mtd(spinand); + mtd->dev.parent = &mem->spi->dev; +#else + nand->mtd = mtd; + mtd->priv = nand; + mtd->dev = dev; + mtd->name = malloc(20); + if (!mtd->name) + return -ENOMEM; + sprintf(mtd->name, "spi-nand%d", spi_nand_idx++); + spinand->slave = slave; + spinand_set_of_node(spinand, dev->node.np); +#endif + + ret = spinand_init(spinand); + if (ret) + return ret; + +#ifndef __UBOOT__ + ret = mtd_device_register(mtd, NULL, 0); +#else + ret = add_mtd_device(mtd); +#endif + if (ret) + goto err_spinand_cleanup; + + return 0; + +err_spinand_cleanup: + spinand_cleanup(spinand); + + return ret; +} + +#ifndef __UBOOT__ +static int spinand_remove(struct udevice *slave) +{ + struct spinand_device *spinand; + struct mtd_info *mtd; + int ret; + + spinand = spi_mem_get_drvdata(slave); + mtd = spinand_to_mtd(spinand); + free(mtd->name); + + ret = mtd_device_unregister(mtd); + if (ret) + return ret; + + spinand_cleanup(spinand); + + return 0; +} + +static const struct spi_device_id spinand_ids[] = { + { .name = "spi-nand" }, + { /* sentinel */ }, +}; + +#ifdef CONFIG_OF +static const struct of_device_id spinand_of_ids[] = { + { .compatible = "spi-nand" }, + { /* sentinel */ }, +}; +#endif + +static struct spi_mem_driver spinand_drv = { + .spidrv = { + .id_table = spinand_ids, + .driver = { + .name = "spi-nand", + .of_match_table = of_match_ptr(spinand_of_ids), + }, + }, + .probe = spinand_probe, + .remove = spinand_remove, +}; +module_spi_mem_driver(spinand_drv); + +MODULE_DESCRIPTION("SPI NAND framework"); +MODULE_AUTHOR("Peter Pan"); +MODULE_LICENSE("GPL v2"); +#endif /* __UBOOT__ */ + +static const struct udevice_id spinand_ids[] = { + { .compatible = "spi-nand" }, + { /* sentinel */ }, +}; + +U_BOOT_DRIVER(spinand) = { + .name = "spi_nand", + .id = UCLASS_MTD, + .of_match = spinand_ids, + .priv_auto_alloc_size = sizeof(struct spinand_device), + .probe = spinand_probe, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h new file mode 100644 index 0000000..ad59fc0 --- /dev/null +++ b/include/linux/mtd/spinand.h @@ -0,0 +1,427 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan + */ +#ifndef __LINUX_MTD_SPINAND_H +#define __LINUX_MTD_SPINAND_H + +#ifndef __UBOOT__ +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +/** + * Standard SPI NAND flash operations + */ + +#define SPINAND_RESET_OP \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xff, 1), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPINAND_WR_EN_DIS_OP(enable) \ + SPI_MEM_OP(SPI_MEM_OP_CMD((enable) ? 0x06 : 0x04, 1), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPINAND_READID_OP(ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x9f, 1), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_DUMMY(ndummy, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 1)) + +#define SPINAND_SET_FEATURE_OP(reg, valptr) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x1f, 1), \ + SPI_MEM_OP_ADDR(1, reg, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, valptr, 1)) + +#define SPINAND_GET_FEATURE_OP(reg, valptr) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x0f, 1), \ + SPI_MEM_OP_ADDR(1, reg, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_IN(1, valptr, 1)) + +#define SPINAND_BLK_ERASE_OP(addr) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xd8, 1), \ + SPI_MEM_OP_ADDR(3, addr, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPINAND_PAGE_READ_OP(addr) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x13, 1), \ + SPI_MEM_OP_ADDR(3, addr, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPINAND_PAGE_READ_FROM_CACHE_OP(fast, addr, ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(fast ? 0x0b : 0x03, 1), \ + SPI_MEM_OP_ADDR(2, addr, 1), \ + SPI_MEM_OP_DUMMY(ndummy, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 1)) + +#define SPINAND_PAGE_READ_FROM_CACHE_X2_OP(addr, ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \ + SPI_MEM_OP_ADDR(2, addr, 1), \ + SPI_MEM_OP_DUMMY(ndummy, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 2)) + +#define SPINAND_PAGE_READ_FROM_CACHE_X4_OP(addr, ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \ + SPI_MEM_OP_ADDR(2, addr, 1), \ + SPI_MEM_OP_DUMMY(ndummy, 1), \ + SPI_MEM_OP_DATA_IN(len, buf, 4)) + +#define SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(addr, ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \ + SPI_MEM_OP_ADDR(2, addr, 2), \ + SPI_MEM_OP_DUMMY(ndummy, 2), \ + SPI_MEM_OP_DATA_IN(len, buf, 2)) + +#define SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(addr, ndummy, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \ + SPI_MEM_OP_ADDR(2, addr, 4), \ + SPI_MEM_OP_DUMMY(ndummy, 4), \ + SPI_MEM_OP_DATA_IN(len, buf, 4)) + +#define SPINAND_PROG_EXEC_OP(addr) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(0x10, 1), \ + SPI_MEM_OP_ADDR(3, addr, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + +#define SPINAND_PROG_LOAD(reset, addr, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x02 : 0x84, 1), \ + SPI_MEM_OP_ADDR(2, addr, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(len, buf, 1)) + +#define SPINAND_PROG_LOAD_X4(reset, addr, buf, len) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(reset ? 0x32 : 0x34, 1), \ + SPI_MEM_OP_ADDR(2, addr, 1), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(len, buf, 4)) + +/** + * Standard SPI NAND flash commands + */ +#define SPINAND_CMD_PROG_LOAD_X4 0x32 +#define SPINAND_CMD_PROG_LOAD_RDM_DATA_X4 0x34 + +/* feature register */ +#define REG_BLOCK_LOCK 0xa0 +#define BL_ALL_UNLOCKED 0x00 + +/* configuration register */ +#define REG_CFG 0xb0 +#define CFG_OTP_ENABLE BIT(6) +#define CFG_ECC_ENABLE BIT(4) +#define CFG_QUAD_ENABLE BIT(0) + +/* status register */ +#define REG_STATUS 0xc0 +#define STATUS_BUSY BIT(0) +#define STATUS_ERASE_FAILED BIT(2) +#define STATUS_PROG_FAILED BIT(3) +#define STATUS_ECC_MASK GENMASK(5, 4) +#define STATUS_ECC_NO_BITFLIPS (0 << 4) +#define STATUS_ECC_HAS_BITFLIPS (1 << 4) +#define STATUS_ECC_UNCOR_ERROR (2 << 4) + +struct spinand_op; +struct spinand_device; + +#define SPINAND_MAX_ID_LEN 4 + +/** + * struct spinand_id - SPI NAND id structure + * @data: buffer containing the id bytes. Currently 4 bytes large, but can + * be extended if required + * @len: ID length + * + * struct_spinand_id->data contains all bytes returned after a READ_ID command, + * including dummy bytes if the chip does not emit ID bytes right after the + * READ_ID command. The responsibility to extract real ID bytes is left to + * struct_manufacurer_ops->detect(). + */ +struct spinand_id { + u8 data[SPINAND_MAX_ID_LEN]; + int len; +}; + +/** + * struct manufacurer_ops - SPI NAND manufacturer specific operations + * @detect: detect a SPI NAND device. Every time a SPI NAND device is probed + * the core calls the struct_manufacurer_ops->detect() hook of each + * registered manufacturer until one of them return 1. Note that + * the first thing to check in this hook is that the manufacturer ID + * in struct_spinand_device->id matches the manufacturer whose + * ->detect() hook has been called. Should return 1 if there's a + * match, 0 if the manufacturer ID does not match and a negative + * error code otherwise. When true is returned, the core assumes + * that properties of the NAND chip (spinand->base.memorg and + * spinand->base.eccreq) have been filled + * @init: initialize a SPI NAND device + * @cleanup: cleanup a SPI NAND device + * + * Each SPI NAND manufacturer driver should implement this interface so that + * NAND chips coming from this vendor can be detected and initialized properly. + */ +struct spinand_manufacturer_ops { + int (*detect)(struct spinand_device *spinand); + int (*init)(struct spinand_device *spinand); + void (*cleanup)(struct spinand_device *spinand); +}; + +/** + * struct spinand_manufacturer - SPI NAND manufacturer instance + * @id: manufacturer ID + * @name: manufacturer name + * @ops: manufacturer operations + */ +struct spinand_manufacturer { + u8 id; + char *name; + const struct spinand_manufacturer_ops *ops; +}; + +/** + * struct spinand_op_variants - SPI NAND operation variants + * @ops: the list of variants for a given operation + * @nops: the number of variants + * + * Some operations like read-from-cache/write-to-cache have several variants + * depending on the number of IO lines you use to transfer data or address + * cycles. This structure is a way to describe the different variants supported + * by a chip and let the core pick the best one based on the SPI mem controller + * capabilities. + */ +struct spinand_op_variants { + const struct spi_mem_op *ops; + unsigned int nops; +}; + +#define SPINAND_OP_VARIANTS(name, ...) \ + const struct spinand_op_variants name = { \ + .ops = (struct spi_mem_op[]) { __VA_ARGS__ }, \ + .nops = sizeof((struct spi_mem_op[]){ __VA_ARGS__ }) / \ + sizeof(struct spi_mem_op), \ + } + +/** + * spinand_ecc_info - description of the on-die ECC implemented by a SPI NAND + * chip + * @get_status: get the ECC status. Should return a positive number encoding + * the number of corrected bitflips if correction was possible or + * -EBADMSG if there are uncorrectable errors. I can also return + * other negative error codes if the error is not caused by + * uncorrectable bitflips + * @ooblayout: the OOB layout used by the on-die ECC implementation + */ +struct spinand_ecc_info { + int (*get_status)(struct spinand_device *spinand, u8 status); + const struct mtd_ooblayout_ops *ooblayout; +}; + +#define SPINAND_HAS_QE_BIT BIT(0) + +/** + * struct spinand_info - Structure used to describe SPI NAND chips + * @model: model name + * @devid: device ID + * @flags: OR-ing of the SPINAND_XXX flags + * @memorg: memory organization + * @eccreq: ECC requirements + * @eccinfo: on-die ECC info + * @op_variants: operations variants + * @op_variants.read_cache: variants of the read-cache operation + * @op_variants.write_cache: variants of the write-cache operation + * @op_variants.update_cache: variants of the update-cache operation + * @select_target: function used to select a target/die. Required only for + * multi-die chips + * + * Each SPI NAND manufacturer driver should have a spinand_info table + * describing all the chips supported by the driver. + */ +struct spinand_info { + const char *model; + u8 devid; + u32 flags; + struct nand_memory_organization memorg; + struct nand_ecc_req eccreq; + struct spinand_ecc_info eccinfo; + struct { + const struct spinand_op_variants *read_cache; + const struct spinand_op_variants *write_cache; + const struct spinand_op_variants *update_cache; + } op_variants; + int (*select_target)(struct spinand_device *spinand, + unsigned int target); +}; + +#define SPINAND_INFO_OP_VARIANTS(__read, __write, __update) \ + { \ + .read_cache = __read, \ + .write_cache = __write, \ + .update_cache = __update, \ + } + +#define SPINAND_ECCINFO(__ooblayout, __get_status) \ + .eccinfo = { \ + .ooblayout = __ooblayout, \ + .get_status = __get_status, \ + } + +#define SPINAND_SELECT_TARGET(__func) \ + .select_target = __func, + +#define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \ + __flags, ...) \ + { \ + .model = __model, \ + .devid = __id, \ + .memorg = __memorg, \ + .eccreq = __eccreq, \ + .op_variants = __op_variants, \ + .flags = __flags, \ + __VA_ARGS__ \ + } + +/** + * struct spinand_device - SPI NAND device instance + * @base: NAND device instance + * @slave: pointer to the SPI slave object + * @lock: lock used to serialize accesses to the NAND + * @id: NAND ID as returned by READ_ID + * @flags: NAND flags + * @op_templates: various SPI mem op templates + * @op_templates.read_cache: read cache op template + * @op_templates.write_cache: write cache op template + * @op_templates.update_cache: update cache op template + * @select_target: select a specific target/die. Usually called before sending + * a command addressing a page or an eraseblock embedded in + * this die. Only required if your chip exposes several dies + * @cur_target: currently selected target/die + * @eccinfo: on-die ECC information + * @cfg_cache: config register cache. One entry per die + * @databuf: bounce buffer for data + * @oobbuf: bounce buffer for OOB data + * @scratchbuf: buffer used for everything but page accesses. This is needed + * because the spi-mem interface explicitly requests that buffers + * passed in spi_mem_op be DMA-able, so we can't based the bufs on + * the stack + * @manufacturer: SPI NAND manufacturer information + * @priv: manufacturer private data + */ +struct spinand_device { + struct nand_device base; +#ifndef __UBOOT__ + struct spi_mem *spimem; + struct mutex lock; +#else + struct spi_slave *slave; +#endif + struct spinand_id id; + u32 flags; + + struct { + const struct spi_mem_op *read_cache; + const struct spi_mem_op *write_cache; + const struct spi_mem_op *update_cache; + } op_templates; + + int (*select_target)(struct spinand_device *spinand, + unsigned int target); + unsigned int cur_target; + + struct spinand_ecc_info eccinfo; + + u8 *cfg_cache; + u8 *databuf; + u8 *oobbuf; + u8 *scratchbuf; + const struct spinand_manufacturer *manufacturer; + void *priv; +}; + +/** + * mtd_to_spinand() - Get the SPI NAND device attached to an MTD instance + * @mtd: MTD instance + * + * Return: the SPI NAND device attached to @mtd. + */ +static inline struct spinand_device *mtd_to_spinand(struct mtd_info *mtd) +{ + return container_of(mtd_to_nanddev(mtd), struct spinand_device, base); +} + +/** + * spinand_to_mtd() - Get the MTD device embedded in a SPI NAND device + * @spinand: SPI NAND device + * + * Return: the MTD device embedded in @spinand. + */ +static inline struct mtd_info *spinand_to_mtd(struct spinand_device *spinand) +{ + return nanddev_to_mtd(&spinand->base); +} + +/** + * nand_to_spinand() - Get the SPI NAND device embedding an NAND object + * @nand: NAND object + * + * Return: the SPI NAND device embedding @nand. + */ +static inline struct spinand_device *nand_to_spinand(struct nand_device *nand) +{ + return container_of(nand, struct spinand_device, base); +} + +/** + * spinand_to_nand() - Get the NAND device embedded in a SPI NAND object + * @spinand: SPI NAND device + * + * Return: the NAND device embedded in @spinand. + */ +static inline struct nand_device * +spinand_to_nand(struct spinand_device *spinand) +{ + return &spinand->base; +} + +/** + * spinand_set_of_node - Attach a DT node to a SPI NAND device + * @spinand: SPI NAND device + * @np: DT node + * + * Attach a DT node to a SPI NAND device. + */ +static inline void spinand_set_of_node(struct spinand_device *spinand, + const struct device_node *np) +{ + nanddev_set_of_node(&spinand->base, np); +} + +int spinand_match_and_init(struct spinand_device *dev, + const struct spinand_info *table, + unsigned int table_size, u8 devid); + +int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val); +int spinand_select_target(struct spinand_device *spinand, unsigned int target); + +#endif /* __LINUX_MTD_SPINAND_H */ From 883d8778ae177172c0a53c018faa39e61f30dea3 Mon Sep 17 00:00:00 2001 From: Peter Pan Date: Thu, 16 Aug 2018 17:30:13 +0200 Subject: [PATCH 14/37] mtd: spinand: Add initial support for Micron MT29F2G01ABAGD Add a basic driver for Micron SPI NANDs. Only one device is supported right now, but the driver will be extended to support more devices afterwards. Signed-off-by: Peter Pan Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/spi/Makefile | 2 +- drivers/mtd/nand/spi/core.c | 17 ++++++ drivers/mtd/nand/spi/micron.c | 135 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 3 + 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/micron.c diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index f0c6e69..4eb745a 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -spinand-objs := core.o +spinand-objs := core.o micron.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 08f853a..36b8b52 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -829,8 +829,25 @@ static const struct nand_ops spinand_ops = { .isbad = spinand_isbad, }; +static const struct spinand_manufacturer *spinand_manufacturers[] = { + µn_spinand_manufacturer, +}; + static int spinand_manufacturer_detect(struct spinand_device *spinand) { + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) { + ret = spinand_manufacturers[i]->ops->detect(spinand); + if (ret > 0) { + spinand->manufacturer = spinand_manufacturers[i]; + return 0; + } else if (ret < 0) { + return ret; + } + } + return -ENOTSUPP; } diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c new file mode 100644 index 0000000..83951c5 --- /dev/null +++ b/drivers/mtd/nand/spi/micron.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan + */ + +#ifndef __UBOOT__ +#include +#include +#endif +#include + +#define SPINAND_MFR_MICRON 0x2c + +#define MICRON_STATUS_ECC_MASK GENMASK(7, 4) +#define MICRON_STATUS_ECC_NO_BITFLIPS (0 << 4) +#define MICRON_STATUS_ECC_1TO3_BITFLIPS (1 << 4) +#define MICRON_STATUS_ECC_4TO6_BITFLIPS (3 << 4) +#define MICRON_STATUS_ECC_7TO8_BITFLIPS (5 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, 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 mt29f2g01abagd_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 64; + region->length = 64; + + return 0; +} + +static int mt29f2g01abagd_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = 62; + + return 0; +} + +static const struct mtd_ooblayout_ops mt29f2g01abagd_ooblayout = { + .ecc = mt29f2g01abagd_ooblayout_ecc, + .free = mt29f2g01abagd_ooblayout_free, +}; + +static int mt29f2g01abagd_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & MICRON_STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case MICRON_STATUS_ECC_1TO3_BITFLIPS: + return 3; + + case MICRON_STATUS_ECC_4TO6_BITFLIPS: + return 6; + + case MICRON_STATUS_ECC_7TO8_BITFLIPS: + return 8; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info micron_spinand_table[] = { + SPINAND_INFO("MT29F2G01ABAGD", 0x24, + NAND_MEMORG(1, 2048, 128, 64, 2048, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&mt29f2g01abagd_ooblayout, + mt29f2g01abagd_ecc_get_status)), +}; + +static int micron_spinand_detect(struct spinand_device *spinand) +{ + u8 *id = spinand->id.data; + int ret; + + /* + * Micron SPI NAND read ID need a dummy byte, + * so the first byte in raw_id is dummy. + */ + if (id[1] != SPINAND_MFR_MICRON) + return 0; + + ret = spinand_match_and_init(spinand, micron_spinand_table, + ARRAY_SIZE(micron_spinand_table), id[2]); + if (ret) + return ret; + + return 1; +} + +static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = { + .detect = micron_spinand_detect, +}; + +const struct spinand_manufacturer micron_spinand_manufacturer = { + .id = SPINAND_MFR_MICRON, + .name = "Micron", + .ops = µn_spinand_manuf_ops, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index ad59fc0..5302d38 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -200,6 +200,9 @@ struct spinand_manufacturer { const struct spinand_manufacturer_ops *ops; }; +/* SPI NAND manufacturers */ +extern const struct spinand_manufacturer micron_spinand_manufacturer; + /** * struct spinand_op_variants - SPI NAND operation variants * @ops: the list of variants for a given operation From 3181c0a622d35bd8e6d4407458e7204d4df5a8c1 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 16 Aug 2018 17:30:14 +0200 Subject: [PATCH 15/37] mtd: spinand: Add initial support for Winbond W25M02GV Add support for the W25M02GV chip. Signed-off-by: Frieder Schrempf Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/spi/Makefile | 2 +- drivers/mtd/nand/spi/core.c | 1 + drivers/mtd/nand/spi/winbond.c | 143 +++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 1 + 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/winbond.c diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index 4eb745a..11ba5de 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -spinand-objs := core.o micron.o +spinand-objs := core.o micron.o winbond.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 36b8b52..ef3e644 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -831,6 +831,7 @@ static const struct nand_ops spinand_ops = { static const struct spinand_manufacturer *spinand_manufacturers[] = { µn_spinand_manufacturer, + &winbond_spinand_manufacturer, }; static int spinand_manufacturer_detect(struct spinand_device *spinand) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c new file mode 100644 index 0000000..eac811d --- /dev/null +++ b/drivers/mtd/nand/spi/winbond.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 exceet electronics GmbH + * + * Authors: + * Frieder Schrempf + * Boris Brezillon + */ + +#ifndef __UBOOT__ +#include +#include +#endif +#include + +#define SPINAND_MFR_WINBOND 0xEF + +#define WINBOND_CFG_BUF_READ BIT(3) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, 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 w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + + return 0; +} + +static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 2; + region->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops w25m02gv_ooblayout = { + .ecc = w25m02gv_ooblayout_ecc, + .free = w25m02gv_ooblayout_free, +}; + +static int w25m02gv_select_target(struct spinand_device *spinand, + unsigned int target) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, + spinand->scratchbuf, + 1)); + + *spinand->scratchbuf = target; + return spi_mem_exec_op(spinand->slave, &op); +} + +static const struct spinand_info winbond_spinand_table[] = { + SPINAND_INFO("W25M02GV", 0xAB, + NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 2), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), + SPINAND_SELECT_TARGET(w25m02gv_select_target)), +}; + +/** + * winbond_spinand_detect - initialize device related part in spinand_device + * struct if it is a Winbond device. + * @spinand: SPI NAND device structure + */ +static int winbond_spinand_detect(struct spinand_device *spinand) +{ + u8 *id = spinand->id.data; + int ret; + + /* + * Winbond SPI NAND read ID need a dummy byte, + * so the first byte in raw_id is dummy. + */ + if (id[1] != SPINAND_MFR_WINBOND) + return 0; + + ret = spinand_match_and_init(spinand, winbond_spinand_table, + ARRAY_SIZE(winbond_spinand_table), id[2]); + if (ret) + return ret; + + return 1; +} + +static int winbond_spinand_init(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + /* + * Make sure all dies are in buffer read mode and not continuous read + * mode. + */ + for (i = 0; i < nand->memorg.ntargets; i++) { + spinand_select_target(spinand, i); + spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, + WINBOND_CFG_BUF_READ); + } + + return 0; +} + +static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { + .detect = winbond_spinand_detect, + .init = winbond_spinand_init, +}; + +const struct spinand_manufacturer winbond_spinand_manufacturer = { + .id = SPINAND_MFR_WINBOND, + .name = "Winbond", + .ops = &winbond_spinand_manuf_ops, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 5302d38..d40a7c8 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -202,6 +202,7 @@ struct spinand_manufacturer { /* SPI NAND manufacturers */ extern const struct spinand_manufacturer micron_spinand_manufacturer; +extern const struct spinand_manufacturer winbond_spinand_manufacturer; /** * struct spinand_op_variants - SPI NAND operation variants From 6f041ccabb03bea16c2f21f3254dc9c1cb38425c Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:30:15 +0200 Subject: [PATCH 16/37] mtd: spinand: Add initial support for the MX35LF1GE4AB chip Add minimal support for the MX35LF1GE4AB SPI NAND chip. Signed-off-by: Boris Brezillon Acked-by: Jagan Teki --- drivers/mtd/nand/spi/Makefile | 2 +- drivers/mtd/nand/spi/core.c | 1 + drivers/mtd/nand/spi/macronix.c | 138 ++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 1 + 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/nand/spi/macronix.c diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index 11ba5de..a66edd9 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -spinand-objs := core.o micron.o winbond.o +spinand-objs := core.o macronix.o micron.o winbond.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 ef3e644..362d104 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -830,6 +830,7 @@ static const struct nand_ops spinand_ops = { }; static const struct spinand_manufacturer *spinand_manufacturers[] = { + ¯onix_spinand_manufacturer, µn_spinand_manufacturer, &winbond_spinand_manufacturer, }; diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c new file mode 100644 index 0000000..dd351dc --- /dev/null +++ b/drivers/mtd/nand/spi/macronix.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Macronix + * + * Author: Boris Brezillon + */ + +#ifndef __UBOOT__ +#include +#include +#endif +#include + +#define SPINAND_MFR_MACRONIX 0xC2 + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_X4_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 mx35lf1ge4ab_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int mx35lf1ge4ab_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 2; + region->length = mtd->oobsize - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops mx35lf1ge4ab_ooblayout = { + .ecc = mx35lf1ge4ab_ooblayout_ecc, + .free = mx35lf1ge4ab_ooblayout_free, +}; + +static int mx35lf1ge4ab_get_eccsr(struct spinand_device *spinand, u8 *eccsr) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_DUMMY(1, 1), + SPI_MEM_OP_DATA_IN(1, eccsr, 1)); + + return spi_mem_exec_op(spinand->slave, &op); +} + +static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 eccsr; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (mx35lf1ge4ab_get_eccsr(spinand, &eccsr)) + return nand->eccreq.strength; + + if (WARN_ON(eccsr > nand->eccreq.strength || !eccsr)) + return nand->eccreq.strength; + + return eccsr; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info macronix_spinand_table[] = { + SPINAND_INFO("MX35LF1GE4AB", 0x12, + NAND_MEMORG(1, 2048, 64, 64, 1024, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lf1ge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), +}; + +static int macronix_spinand_detect(struct spinand_device *spinand) +{ + u8 *id = spinand->id.data; + int ret; + + /* + * Macronix SPI NAND read ID needs a dummy byte, so the first byte in + * raw_id is garbage. + */ + if (id[1] != SPINAND_MFR_MACRONIX) + return 0; + + ret = spinand_match_and_init(spinand, macronix_spinand_table, + ARRAY_SIZE(macronix_spinand_table), + id[2]); + if (ret) + return ret; + + return 1; +} + +static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = { + .detect = macronix_spinand_detect, +}; + +const struct spinand_manufacturer macronix_spinand_manufacturer = { + .id = SPINAND_MFR_MACRONIX, + .name = "Macronix", + .ops = ¯onix_spinand_manuf_ops, +}; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index d40a7c8..8c9c756 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -201,6 +201,7 @@ struct spinand_manufacturer { }; /* SPI NAND manufacturers */ +extern const struct spinand_manufacturer macronix_spinand_manufacturer; extern const struct spinand_manufacturer micron_spinand_manufacturer; extern const struct spinand_manufacturer winbond_spinand_manufacturer; From 515d0212615b8b4bbe1e39ccf7946e042dc1bf58 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:16 +0200 Subject: [PATCH 17/37] mtd: spinand: Add initial support for the MX35LF2GE4AB chip Add support for the MX35LF2GE4AB chip, which is similar to its cousin MX35LF1GE4AB, with two planes instead of one. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- drivers/mtd/nand/spi/macronix.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index dd351dc..662c561 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -27,13 +27,13 @@ static SPINAND_OP_VARIANTS(update_cache_variants, SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), SPINAND_PROG_LOAD(false, 0, NULL, 0)); -static int mx35lf1ge4ab_ooblayout_ecc(struct mtd_info *mtd, int section, +static int mx35lfxge4ab_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { return -ERANGE; } -static int mx35lf1ge4ab_ooblayout_free(struct mtd_info *mtd, int section, +static int mx35lfxge4ab_ooblayout_free(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { if (section) @@ -45,9 +45,9 @@ static int mx35lf1ge4ab_ooblayout_free(struct mtd_info *mtd, int section, return 0; } -static const struct mtd_ooblayout_ops mx35lf1ge4ab_ooblayout = { - .ecc = mx35lf1ge4ab_ooblayout_ecc, - .free = mx35lf1ge4ab_ooblayout_free, +static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = { + .ecc = mx35lfxge4ab_ooblayout_ecc, + .free = mx35lfxge4ab_ooblayout_free, }; static int mx35lf1ge4ab_get_eccsr(struct spinand_device *spinand, u8 *eccsr) @@ -102,8 +102,16 @@ static const struct spinand_info macronix_spinand_table[] = { &write_cache_variants, &update_cache_variants), SPINAND_HAS_QE_BIT, - SPINAND_ECCINFO(&mx35lf1ge4ab_ooblayout, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35LF2GE4AB", 0x22, + NAND_MEMORG(1, 2048, 64, 64, 2048, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), }; static int macronix_spinand_detect(struct spinand_device *spinand) From 0fa1fc43c87d01a2eb30553f9aea4b52ed43c1f5 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 16 Aug 2018 17:30:17 +0200 Subject: [PATCH 18/37] dt-bindings: Add bindings for SPI NAND devices Add bindings for SPI NAND chips. Signed-off-by: Boris Brezillon Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- doc/device-tree-bindings/mtd/spi-nand.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/device-tree-bindings/mtd/spi-nand.txt diff --git a/doc/device-tree-bindings/mtd/spi-nand.txt b/doc/device-tree-bindings/mtd/spi-nand.txt new file mode 100644 index 0000000..8b51f3b --- /dev/null +++ b/doc/device-tree-bindings/mtd/spi-nand.txt @@ -0,0 +1,5 @@ +SPI NAND flash + +Required properties: +- compatible: should be "spi-nand" +- reg: should encode the chip-select line used to access the NAND chip From 4048a5c519a86eab2805f4f07fe9f5c6c8a1fe91 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:18 +0200 Subject: [PATCH 19/37] mtd: declare MTD_PARTITIONS symbol in Kconfig UBI selects MTD_PARTITIONS which is the symbol to compile drivers/mtd/mtdpart.c. Unfortunately, the symbol was not defined in Kconfig and this worked only with board files defining it. Fix this by adding a boolean in Kconfig so boards defined by defconfig files only will work as expected. Signed-off-by: Miquel Raynal Reviewed-by: Boris Brezillon --- drivers/mtd/Kconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 9341d51..d98457e 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -1,5 +1,8 @@ menu "MTD Support" +config MTD_PARTITIONS + bool + config MTD bool "Enable Driver Model for MTD drivers" depends on DM From b0036f70044d3d59d5c90f0c4f1c8c1a90b7cd55 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:19 +0200 Subject: [PATCH 20/37] mtd: mtdpart: balance debug messages Balance debug message in the partition allocation/removal process in order to keep track of them more easily. Signed-off-by: Miquel Raynal --- drivers/mtd/mtdpart.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index ccbb175..9ccb1b3 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -362,6 +362,8 @@ int del_mtd_partitions(struct mtd_info *master) struct mtd_part *slave, *next; int ret, err = 0; + debug("Deleting MTD partitions on \"%s\":\n", master->name); + mutex_lock(&mtd_partitions_mutex); list_for_each_entry_safe(slave, next, &mtd_partitions, list) if (slave->master == master) { From b01c146d182da12e0b60022b34d15fd3a0c0f0fc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:20 +0200 Subject: [PATCH 21/37] cmd: ubi: delete useless and misleading definitions These definitions are simply not used and are misleading because similar definitions exist in jffs2/load_kernel.h and are used widely to define MTD device types (which is, by the way, totally redundant with what the MTD core does). Remove these definitions. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki Reviewed-by: Boris Brezillon --- cmd/ubi.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/ubi.c b/cmd/ubi.c index 913f0f7..0a3405a 100644 --- a/cmd/ubi.c +++ b/cmd/ubi.c @@ -27,11 +27,6 @@ #undef ubi_msg #define ubi_msg(fmt, ...) printf("UBI: " fmt "\n", ##__VA_ARGS__) -#define DEV_TYPE_NONE 0 -#define DEV_TYPE_NAND 1 -#define DEV_TYPE_ONENAND 2 -#define DEV_TYPE_NOR 3 - /* Private own data */ static struct ubi_device *ubi; static char buffer[80]; From 2b9ace5527d33539844fa831e12ad8357a25246e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 16 Aug 2018 17:30:21 +0200 Subject: [PATCH 22/37] cmd: mtdparts: add fallthrough in switch statement Switch blocks for deriving size naturally use fallthrough between 'case' statements. Make it explicit. Signed-off-by: Miquel Raynal --- cmd/mtdparts.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/mtdparts.c b/cmd/mtdparts.c index 0da3afd..756fc60 100644 --- a/cmd/mtdparts.c +++ b/cmd/mtdparts.c @@ -177,13 +177,16 @@ static u64 memsize_parse (const char *const ptr, const char **retptr) case 'G': case 'g': ret <<= 10; + /* Fallthrough */ case 'M': case 'm': ret <<= 10; + /* Fallthrough */ case 'K': case 'k': ret <<= 10; (*retptr)++; + /* Fallthrough */ default: break; } From a353e6aa8ec8f0aa101cc7e9543fe7843ddc6d98 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 6 Sep 2018 09:08:43 +0200 Subject: [PATCH 23/37] lib: strto: parse all lowercase metric prefixes in ustrtoul[l] Both ustrtoul and ustrtoull interpret 1k but not 1m or 1g. Even if the SI symbols for Mega and Giga are 'M' and 'G', certain entries of eg. mtdparts also use (wrongly) the metric prefix 'm' and 'g'. I do not see how parsing lowercase prefixes could break anything, so parse them like their uppercase counterpart. Also, even though kiB is not equal to kB in general, lets not change U-Boot behavior and always use kiB and kB (same applies for MiB vs. MB and GiB vs. GB) as a representation for 1024 instead of 1000. Signed-off-by: Miquel Raynal Reviewed-by: Stefan Roese --- lib/strto.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/strto.c b/lib/strto.c index 7f60769..b7fc31d 100644 --- a/lib/strto.c +++ b/lib/strto.c @@ -85,14 +85,13 @@ long simple_strtol(const char *cp, char **endp, unsigned int base) unsigned long ustrtoul(const char *cp, char **endp, unsigned int base) { unsigned long result = simple_strtoul(cp, endp, base); - switch (**endp) { - case 'G': + switch (tolower(**endp)) { + case 'g': result *= 1024; /* fall through */ - case 'M': + case 'm': result *= 1024; /* fall through */ - case 'K': case 'k': result *= 1024; if ((*endp)[1] == 'i') { @@ -108,14 +107,13 @@ unsigned long ustrtoul(const char *cp, char **endp, unsigned int base) unsigned long long ustrtoull(const char *cp, char **endp, unsigned int base) { unsigned long long result = simple_strtoull(cp, endp, base); - switch (**endp) { - case 'G': + switch (tolower(**endp)) { + case 'g': result *= 1024; /* fall through */ - case 'M': + case 'm': result *= 1024; /* fall through */ - case 'K': case 'k': result *= 1024; if ((*endp)[1] == 'i') { From b87b0d8d79e05f85e4f18200eac4283d513c3f3c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 6 Sep 2018 09:08:44 +0200 Subject: [PATCH 24/37] lib: strto: fix metric suffix parsing in strtoul[l] While 1kB or 1kiB will be parsed correctly, 1k will return the right amount, but the metric suffix will not be escaped once the char pointer updated. Fix this situation by simplifying the move of the endp pointer. Signed-off-by: Miquel Raynal Reviewed-by: Stefan Roese --- lib/strto.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/strto.c b/lib/strto.c index b7fc31d..55ff9f7 100644 --- a/lib/strto.c +++ b/lib/strto.c @@ -94,12 +94,11 @@ unsigned long ustrtoul(const char *cp, char **endp, unsigned int base) /* fall through */ case 'k': result *= 1024; - if ((*endp)[1] == 'i') { - if ((*endp)[2] == 'B') - (*endp) += 3; - else - (*endp) += 2; - } + (*endp)++; + if (**endp == 'i') + (*endp)++; + if (**endp == 'B') + (*endp)++; } return result; } @@ -116,12 +115,11 @@ unsigned long long ustrtoull(const char *cp, char **endp, unsigned int base) /* fall through */ case 'k': result *= 1024; - if ((*endp)[1] == 'i') { - if ((*endp)[2] == 'B') - (*endp) += 3; - else - (*endp) += 2; - } + (*endp)++; + if (**endp == 'i') + (*endp)++; + if (**endp == 'B') + (*endp)++; } return result; } From 00ac922db4085982011438e9471e0fcacca2e8fc Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 6 Sep 2018 09:08:45 +0200 Subject: [PATCH 25/37] cmd: mtdparts: accept spi-nand devices Let spi-nand devices be recognized by mtdparts. This is superfluous but a full mtdparts rework would be very time-consuming. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki Reviewed-by: Boris Brezillon Reviewed-by: Stefan Roese --- cmd/mtdparts.c | 13 ++++++++----- include/jffs2/load_kernel.h | 7 +++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/mtdparts.c b/cmd/mtdparts.c index 756fc60..2e54789 100644 --- a/cmd/mtdparts.c +++ b/cmd/mtdparts.c @@ -37,7 +37,7 @@ * mtdids=[,,...] * * := = - * := 'nand'|'nor'|'onenand' + * := 'nand'|'nor'|'onenand'|'spi-nand' * := mtd device number, 0... * := unique device tag used by linux kernel to find mtd device (mtd->name) * @@ -339,7 +339,7 @@ static int part_validate_eraseblock(struct mtdids *id, struct part_info *part) if (!mtd->numeraseregions) { /* - * Only one eraseregion (NAND, OneNAND or uniform NOR), + * Only one eraseregion (NAND, SPI-NAND, OneNAND or uniform NOR), * checking for alignment is easy here */ offset = part->offset; @@ -1030,7 +1030,7 @@ static struct mtdids* id_find_by_mtd_id(const char *mtd_id, unsigned int mtd_id_ } /** - * Parse device id string := 'nand'|'nor'|'onenand', + * Parse device id string := 'nand'|'nor'|'onenand'|'spi-nand', * return device type and number. * * @param id string describing device id @@ -1054,6 +1054,9 @@ int mtd_id_parse(const char *id, const char **ret_id, u8 *dev_type, } else if (strncmp(p, "onenand", 7) == 0) { *dev_type = MTD_DEV_TYPE_ONENAND; p += 7; + } else if (strncmp(p, "spi-nand", 8) == 0) { + *dev_type = MTD_DEV_TYPE_SPINAND; + p += 8; } else { printf("incorrect device type in %s\n", id); return 1; @@ -1636,7 +1639,7 @@ static int parse_mtdids(const char *const ids) while(p && (*p != '\0')) { ret = 1; - /* parse 'nor'|'nand'|'onenand' */ + /* parse 'nor'|'nand'|'onenand'|'spi-nand' */ if (mtd_id_parse(p, &p, &type, &num) != 0) break; @@ -2112,7 +2115,7 @@ static char mtdparts_help_text[] = "'mtdids' - linux kernel mtd device id <-> u-boot device id mapping\n\n" "mtdids=[,,...]\n\n" " := =\n" - " := 'nand'|'nor'|'onenand'\n" + " := 'nand'|'nor'|'onenand'|'spi-nand'\n" " := mtd device number, 0...\n" " := unique device tag used by linux kernel to find mtd device (mtd->name)\n\n" "'mtdparts' - partition list\n\n" diff --git a/include/jffs2/load_kernel.h b/include/jffs2/load_kernel.h index 1ddff06..9346d7e 100644 --- a/include/jffs2/load_kernel.h +++ b/include/jffs2/load_kernel.h @@ -15,9 +15,12 @@ #define MTD_DEV_TYPE_NOR 0x0001 #define MTD_DEV_TYPE_NAND 0x0002 #define MTD_DEV_TYPE_ONENAND 0x0004 +#define MTD_DEV_TYPE_SPINAND 0x0008 -#define MTD_DEV_TYPE(type) ((type == MTD_DEV_TYPE_NAND) ? "nand" : \ - (type == MTD_DEV_TYPE_ONENAND) ? "onenand" : "nor") +#define MTD_DEV_TYPE(type) (type == MTD_DEV_TYPE_NAND ? "nand" : \ + (type == MTD_DEV_TYPE_NOR ? "nor" : \ + (type == MTD_DEV_TYPE_ONENAND ? "onenand" : \ + "spi-nand"))) \ struct mtd_device { struct list_head link; From d60aea94e92a8783e95591b7eeaf733903eeaec7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 6 Sep 2018 09:08:46 +0200 Subject: [PATCH 26/37] cmd: mtdparts: remove mandatory 'mtdparts=' prefix All U-Boot users must define the mtdparts environment variable with: setenv mtdparts mtdparts=... While this may ease the partition declaration job to be passed to Linux, this is a pure software limitation and forcing this prefix is a complete non-sense. Let the user to declare manually the mtdparts variable without the prefix. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki Reviewed-by: Stefan Roese --- cmd/mtdparts.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/cmd/mtdparts.c b/cmd/mtdparts.c index 2e54789..f7ed1a0 100644 --- a/cmd/mtdparts.c +++ b/cmd/mtdparts.c @@ -44,7 +44,7 @@ * * 'mtdparts' - partition list * - * mtdparts=mtdparts=[;...] + * mtdparts=[mtdparts=][;...] * * := :[,...] * := unique device tag used by linux kernel to find mtd device (mtd->name) @@ -62,11 +62,11 @@ * * 1 NOR Flash, with 1 single writable partition: * mtdids=nor0=edb7312-nor - * mtdparts=mtdparts=edb7312-nor:- + * mtdparts=[mtdparts=]edb7312-nor:- * * 1 NOR Flash with 2 partitions, 1 NAND with one * mtdids=nor0=edb7312-nor,nand0=edb7312-nand - * mtdparts=mtdparts=edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home) + * mtdparts=[mtdparts=]edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home) * */ @@ -1099,9 +1099,6 @@ static int generate_mtdparts(char *buf, u32 buflen) return 0; } - strcpy(p, "mtdparts="); - p += 9; - list_for_each(dentry, &devices) { dev = list_entry(dentry, struct mtd_device, link); @@ -1572,11 +1569,9 @@ static int parse_mtdparts(const char *const mtdparts) if (!p) p = mtdparts; - if (strncmp(p, "mtdparts=", 9) != 0) { - printf("mtdparts variable doesn't start with 'mtdparts='\n"); - return err; - } - p += 9; + /* Skip the useless prefix, if any */ + if (strncmp(p, "mtdparts=", 9) == 0) + p += 9; while (*p != '\0') { err = 1; From 739def7253c4b782bcb9cc816edf3b2f2aeafb4e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:23 +0200 Subject: [PATCH 27/37] dm: drop unused helper in MTD header include/mtd.h might be included by files even if CONFIG_DM is not enabled. In this case, the call to dev_get_uclass_priv() would trigger a build error. Because this helper has no user, let's drop it off. Signed-off-by: Miquel Raynal Reviewed-by: Jagan Teki --- include/mtd.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/include/mtd.h b/include/mtd.h index 548e7f1..9e5c807 100644 --- a/include/mtd.h +++ b/include/mtd.h @@ -8,15 +8,4 @@ #include -/* - * Get mtd_info structure of the dev, which is stored as uclass private. - * - * @dev: The MTD device - * @return: pointer to mtd_info, NULL on error - */ -static inline struct mtd_info *mtd_get_info(struct udevice *dev) -{ - return dev_get_uclass_priv(dev); -} - #endif /* _MTD_H_ */ From e9f62db64b55fa3451731781cc3b1d3bdd271d54 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:24 +0200 Subject: [PATCH 28/37] mtd: uclass: add probe function The user might want to trigger the probe of any MTD device, export these functions so they can be called from a command source file. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- drivers/mtd/mtd-uclass.c | 16 ++++++++++++++++ include/mtd.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/drivers/mtd/mtd-uclass.c b/drivers/mtd/mtd-uclass.c index 9ca049c..5418217 100644 --- a/drivers/mtd/mtd-uclass.c +++ b/drivers/mtd/mtd-uclass.c @@ -5,9 +5,25 @@ #include #include +#include #include #include +/** + * mtd_probe - Probe the device @dev if not already done + * + * @dev: U-Boot device to probe + * + * @return 0 on success, an error otherwise. + */ +int mtd_probe(struct udevice *dev) +{ + if (device_active(dev)) + return 0; + + return device_probe(dev); +} + /* * Implement a MTD uclass which should include most flash drivers. * The uclass private is pointed to mtd_info. diff --git a/include/mtd.h b/include/mtd.h index 9e5c807..75aca26 100644 --- a/include/mtd.h +++ b/include/mtd.h @@ -8,4 +8,6 @@ #include +int mtd_probe(struct udevice *dev); + #endif /* _MTD_H_ */ From 21cc1fb5af06e468c74ae601bac719d306523f9c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:25 +0200 Subject: [PATCH 29/37] mtd: mtdpart: add a generic mtdparts-like parser The current parser is very specific to U-Boot mtdparts implementation. It does not use MTD structures like mtd_info and mtd_partition. Copy and adapt the current parser in drivers/mtd/mtd-uclass.c (to not break the current use of mtdparts.c itself) and write some kind of a wrapper around the current implementation to allow other commands to benefit from this parsing in a user-friendly way. This new function will allocate an mtd_partition array for each successful call. This array must be freed after use by the caller. The given 'mtdparts' buffer pointer will be moved forward to the next MTD device (if any, it will point towards a '\0' character otherwise). Signed-off-by: Miquel Raynal Acked-by: Jagan Teki Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- drivers/mtd/mtdpart.c | 210 +++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/partitions.h | 21 +++++ 2 files changed, 231 insertions(+) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 9ccb1b3..49353b0 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "mtdcore.h" @@ -76,6 +77,215 @@ char *kstrdup(const char *s, gfp_t gfp) } #endif +#define MTD_SIZE_REMAINING (~0LLU) +#define MTD_OFFSET_NOT_SPECIFIED (~0LLU) + +/** + * mtd_parse_partition - Parse @mtdparts partition definition, fill @partition + * with it and update the @mtdparts string pointer. + * + * The partition name is allocated and must be freed by the caller. + * + * This function is widely inspired from part_parse (mtdparts.c). + * + * @mtdparts: String describing the partition with mtdparts command syntax + * @partition: MTD partition structure to fill + * + * @return 0 on success, an error otherwise. + */ +static int mtd_parse_partition(const char **_mtdparts, + struct mtd_partition *partition) +{ + const char *mtdparts = *_mtdparts; + const char *name = NULL; + int name_len; + char *buf; + + /* Ensure the partition structure is empty */ + memset(partition, 0, sizeof(struct mtd_partition)); + + /* Fetch the partition size */ + if (*mtdparts == '-') { + /* Assign all remaining space to this partition */ + partition->size = MTD_SIZE_REMAINING; + mtdparts++; + } else { + partition->size = ustrtoull(mtdparts, (char **)&mtdparts, 0); + if (partition->size < SZ_4K) { + printf("Minimum partition size 4kiB, %lldB requested\n", + partition->size); + return -EINVAL; + } + } + + /* Check for the offset */ + partition->offset = MTD_OFFSET_NOT_SPECIFIED; + if (*mtdparts == '@') { + mtdparts++; + partition->offset = ustrtoull(mtdparts, (char **)&mtdparts, 0); + } + + /* Now look for the name */ + if (*mtdparts == '(') { + name = ++mtdparts; + mtdparts = strchr(name, ')'); + if (!mtdparts) { + printf("No closing ')' found in partition name\n"); + return -EINVAL; + } + name_len = mtdparts - name + 1; + if ((name_len - 1) == 0) { + printf("Empty partition name\n"); + return -EINVAL; + } + mtdparts++; + } else { + /* Name will be of the form size@offset */ + name_len = 22; + } + + /* Check if the partition is read-only */ + if (strncmp(mtdparts, "ro", 2) == 0) { + partition->mask_flags |= MTD_WRITEABLE; + mtdparts += 2; + } + + /* Check for a potential next partition definition */ + if (*mtdparts == ',') { + if (partition->size == MTD_SIZE_REMAINING) { + printf("No partitions allowed after a fill-up\n"); + return -EINVAL; + } + ++mtdparts; + } else if ((*mtdparts == ';') || (*mtdparts == '\0')) { + /* NOP */ + } else { + printf("Unexpected character '%c' in mtdparts\n", *mtdparts); + return -EINVAL; + } + + /* + * Allocate a buffer for the name and either copy the provided name or + * auto-generate it with the form 'size@offset'. + */ + buf = malloc(name_len); + if (!buf) + return -ENOMEM; + + if (name) + strncpy(buf, name, name_len - 1); + else + snprintf(buf, name_len, "0x%08llx@0x%08llx", + partition->size, partition->offset); + + buf[name_len - 1] = '\0'; + partition->name = buf; + + *_mtdparts = mtdparts; + + return 0; +} + +/** + * mtd_parse_partitions - Create a partition array from an mtdparts definition + * + * Stateless function that takes a @parent MTD device, a string @_mtdparts + * describing the partitions (with the "mtdparts" command syntax) and creates + * the corresponding MTD partition structure array @_parts. Both the name and + * the structure partition itself must be freed freed, the caller may use + * @mtd_free_parsed_partitions() for this purpose. + * + * @parent: MTD device which contains the partitions + * @_mtdparts: Pointer to a string describing the partitions with "mtdparts" + * command syntax. + * @_parts: Allocated array containing the partitions, must be freed by the + * caller. + * @_nparts: Size of @_parts array. + * + * @return 0 on success, an error otherwise. + */ +int mtd_parse_partitions(struct mtd_info *parent, const char **_mtdparts, + struct mtd_partition **_parts, int *_nparts) +{ + struct mtd_partition partition = {}, *parts; + const char *mtdparts = *_mtdparts; + int cur_off = 0, cur_sz = 0; + int nparts = 0; + int ret, idx; + u64 sz; + + /* First, iterate over the partitions until we know their number */ + while (mtdparts[0] != '\0' && mtdparts[0] != ';') { + ret = mtd_parse_partition(&mtdparts, &partition); + if (ret) + return ret; + + free((char *)partition.name); + nparts++; + } + + /* Allocate an array of partitions to give back to the caller */ + parts = malloc(sizeof(*parts) * nparts); + if (!parts) { + printf("Not enough space to save partitions meta-data\n"); + return -ENOMEM; + } + + /* Iterate again over each partition to save the data in our array */ + for (idx = 0; idx < nparts; idx++) { + ret = mtd_parse_partition(_mtdparts, &parts[idx]); + if (ret) + return ret; + + if (parts[idx].size == MTD_SIZE_REMAINING) + parts[idx].size = parent->size - cur_sz; + cur_sz += parts[idx].size; + + sz = parts[idx].size; + if (sz < parent->writesize || do_div(sz, parent->writesize)) { + printf("Partition size must be a multiple of %d\n", + parent->writesize); + return -EINVAL; + } + + if (parts[idx].offset == MTD_OFFSET_NOT_SPECIFIED) + parts[idx].offset = cur_off; + cur_off += parts[idx].size; + + parts[idx].ecclayout = parent->ecclayout; + } + + /* Offset by one mtdparts to point to the next device if any */ + if (*_mtdparts[0] == ';') + (*_mtdparts)++; + + *_parts = parts; + *_nparts = nparts; + + return 0; +} + +/** + * mtd_free_parsed_partitions - Free dynamically allocated partitions + * + * Each successful call to @mtd_parse_partitions must be followed by a call to + * @mtd_free_parsed_partitions to free any allocated array during the parsing + * process. + * + * @parts: Array containing the partitions that will be freed. + * @nparts: Size of @parts array. + */ +void mtd_free_parsed_partitions(struct mtd_partition *parts, + unsigned int nparts) +{ + int i; + + for (i = 0; i < nparts; i++) + free((char *)parts[i].name); + + free(parts); +} + /* * MTD methods which simply translate the effective address and pass through * to the _real_ device. diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h index ce0e8db..6eea0a5 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -87,4 +87,25 @@ int mtd_add_partition(struct mtd_info *master, const char *name, int mtd_del_partition(struct mtd_info *master, int partno); uint64_t mtd_get_device_size(const struct mtd_info *mtd); +#if defined(CONFIG_MTD_PARTITIONS) +int mtd_parse_partitions(struct mtd_info *parent, const char **_mtdparts, + struct mtd_partition **_parts, int *_nparts); +void mtd_free_parsed_partitions(struct mtd_partition *parts, + unsigned int nparts); +#else +static inline int +mtd_parse_partitions(struct mtd_info *parent, const char **_mtdparts, + struct mtd_partition **_parts, int *_nparts) +{ + *_nparts = 0; + + return 0; +} +static inline void +mtd_free_parsed_partitions(struct mtd_partition *parts, unsigned int nparts) +{ + return; +} +#endif /* defined(MTD_PARTITIONS) */ + #endif From ff4afa8a981e22eef670c7c857cb87983346cc2c Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:26 +0200 Subject: [PATCH 30/37] mtd: uboot: search for an equivalent MTD name with the mtdids Using an MTD device (resp. partition) name in mtdparts is simple and straightforward. However, for a long time already, another name was given in mtdparts to indicate a device (resp. partition) so the "mtdids" environment variable was created to do the match. Let's create a function that, from an MTD device (resp. partition) name, search for the equivalent name in the "mtdparts" environment variable thanks to the "mtdids" string. Signed-off-by: Miquel Raynal Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- drivers/mtd/mtd_uboot.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/mtd/mtd.h | 5 ++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index 2b3b2ee..8d7e789 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -5,7 +5,70 @@ */ #include #include -#include +#include /* Legacy */ + +/** + * mtd_search_alternate_name - Search an alternate name for @mtdname thanks to + * the mtdids legacy environment variable. + * + * The mtdids string is a list of comma-separated 'dev_id=mtd_id' tupples. + * Check if one of the mtd_id matches mtdname, in this case save dev_id in + * altname. + * + * @mtdname: Current MTD device name + * @altname: Alternate name to return + * @max_len: Length of the alternate name buffer + * + * @return 0 on success, an error otherwise. + */ +int mtd_search_alternate_name(const char *mtdname, char *altname, + unsigned int max_len) +{ + const char *mtdids, *equal, *comma, *dev_id, *mtd_id; + int dev_id_len, mtd_id_len; + + mtdids = env_get("mtdids"); + if (!mtdids) + return -EINVAL; + + do { + /* Find the '=' sign */ + dev_id = mtdids; + equal = strchr(dev_id, '='); + if (!equal) + break; + dev_id_len = equal - mtdids; + mtd_id = equal + 1; + + /* Find the end of the tupple */ + comma = strchr(mtdids, ','); + if (comma) + mtd_id_len = comma - mtd_id; + else + mtd_id_len = &mtdids[strlen(mtdids)] - mtd_id + 1; + + if (!dev_id_len || !mtd_id_len) + return -EINVAL; + + if (dev_id_len + 1 > max_len) + continue; + + /* Compare the name we search with the current mtd_id */ + if (!strncmp(mtdname, mtd_id, mtd_id_len)) { + strncpy(altname, dev_id, dev_id_len); + altname[dev_id_len] = 0; + + return 0; + } + + /* Go to the next tupple */ + mtdids = comma + 1; + } while (comma); + + return -EINVAL; +} + +/* Legacy */ static int get_part(const char *partname, int *idx, loff_t *off, loff_t *size, loff_t *maxsize, int devtype) diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index b8c2c3f..af6f4a6 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -549,5 +549,10 @@ int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off, void mtd_get_len_incl_bad(struct mtd_info *mtd, uint64_t offset, const uint64_t length, uint64_t *len_incl_bad, int *truncated); + +/* drivers/mtd/mtd_uboot.c */ +int mtd_search_alternate_name(const char *mtdname, char *altname, + unsigned int max_len); + #endif #endif /* __MTD_MTD_H__ */ From 2a74930da57f6fbe3c24509f1d481f435acd2356 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:27 +0200 Subject: [PATCH 31/37] mtd: mtdpart: implement proper partition handling Instead of collecting partitions in a flat list, create a hierarchy within the mtd_info structure: use a partitions list to keep track of the partitions of an MTD device (which might be itself a partition of another MTD device), a pointer to the parent device (NULL when the MTD device is the root one, not a partition). By also saving directly in mtd_info the offset of the partition, we can get rid of the mtd_part structure. Signed-off-by: Miquel Raynal Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- drivers/mtd/mtdcore.c | 2 + drivers/mtd/mtdpart.c | 413 +++++++++++++++++------------------------ include/linux/mtd/mtd.h | 32 ++++ include/linux/mtd/partitions.h | 1 - 4 files changed, 209 insertions(+), 239 deletions(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index fb1d68d..fb6c779 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -426,6 +426,8 @@ int add_mtd_device(struct mtd_info *mtd) mtd->index = i; mtd->usecount = 0; + INIT_LIST_HEAD(&mtd->partitions); + /* default value if not set by driver */ if (mtd->bitflip_threshold == 0) mtd->bitflip_threshold = mtd->ecc_strength; diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 49353b0..4d2ac81 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -30,29 +30,12 @@ #include "mtdcore.h" -/* Our partition linked list */ -static LIST_HEAD(mtd_partitions); #ifndef __UBOOT__ static DEFINE_MUTEX(mtd_partitions_mutex); #else DEFINE_MUTEX(mtd_partitions_mutex); #endif -/* Our partition node structure */ -struct mtd_part { - struct mtd_info mtd; - struct mtd_info *master; - uint64_t offset; - struct list_head list; -}; - -/* - * Given a pointer to the MTD object in the mtd_part structure, we can retrieve - * the pointer to that structure with this macro. - */ -#define PART(x) ((struct mtd_part *)(x)) - - #ifdef __UBOOT__ /* from mm/util.c */ @@ -294,19 +277,18 @@ void mtd_free_parsed_partitions(struct mtd_partition *parts, static int part_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct mtd_part *part = PART(mtd); struct mtd_ecc_stats stats; int res; - stats = part->master->ecc_stats; - res = part->master->_read(part->master, from + part->offset, len, - retlen, buf); + stats = mtd->parent->ecc_stats; + res = mtd->parent->_read(mtd->parent, from + mtd->offset, len, + retlen, buf); if (unlikely(mtd_is_eccerr(res))) mtd->ecc_stats.failed += - part->master->ecc_stats.failed - stats.failed; + mtd->parent->ecc_stats.failed - stats.failed; else mtd->ecc_stats.corrected += - part->master->ecc_stats.corrected - stats.corrected; + mtd->parent->ecc_stats.corrected - stats.corrected; return res; } @@ -314,17 +296,13 @@ static int part_read(struct mtd_info *mtd, loff_t from, size_t len, static int part_point(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, void **virt, resource_size_t *phys) { - struct mtd_part *part = PART(mtd); - - return part->master->_point(part->master, from + part->offset, len, - retlen, virt, phys); + return mtd->parent->_point(mtd->parent, from + mtd->offset, len, + retlen, virt, phys); } static int part_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { - struct mtd_part *part = PART(mtd); - - return part->master->_unpoint(part->master, from + part->offset, len); + return mtd->parent->_unpoint(mtd->parent, from + mtd->offset, len); } #endif @@ -333,17 +311,13 @@ static unsigned long part_get_unmapped_area(struct mtd_info *mtd, unsigned long offset, unsigned long flags) { - struct mtd_part *part = PART(mtd); - - offset += part->offset; - return part->master->_get_unmapped_area(part->master, len, offset, - flags); + offset += mtd->offset; + return mtd->parent->_get_unmapped_area(mtd->parent, len, offset, flags); } static int part_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { - struct mtd_part *part = PART(mtd); int res; if (from >= mtd->size) @@ -368,7 +342,7 @@ static int part_read_oob(struct mtd_info *mtd, loff_t from, return -EINVAL; } - res = part->master->_read_oob(part->master, from + part->offset, ops); + res = mtd->parent->_read_oob(mtd->parent, from + mtd->offset, ops); if (unlikely(res)) { if (mtd_is_bitflip(res)) mtd->ecc_stats.corrected++; @@ -381,99 +355,87 @@ static int part_read_oob(struct mtd_info *mtd, loff_t from, static int part_read_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_read_user_prot_reg(part->master, from, len, - retlen, buf); + return mtd->parent->_read_user_prot_reg(mtd->parent, from, len, + retlen, buf); } static int part_get_user_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, struct otp_info *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_get_user_prot_info(part->master, len, retlen, - buf); + return mtd->parent->_get_user_prot_info(mtd->parent, len, retlen, + buf); } static int part_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_read_fact_prot_reg(part->master, from, len, - retlen, buf); + return mtd->parent->_read_fact_prot_reg(mtd->parent, from, len, + retlen, buf); } static int part_get_fact_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, struct otp_info *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_get_fact_prot_info(part->master, len, retlen, - buf); + return mtd->parent->_get_fact_prot_info(mtd->parent, len, retlen, + buf); } static int part_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_write(part->master, to + part->offset, len, - retlen, buf); + return mtd->parent->_write(mtd->parent, to + mtd->offset, len, + retlen, buf); } static int part_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_panic_write(part->master, to + part->offset, len, - retlen, buf); + return mtd->parent->_panic_write(mtd->parent, to + mtd->offset, len, + retlen, buf); } static int part_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { - struct mtd_part *part = PART(mtd); - if (to >= mtd->size) return -EINVAL; if (ops->datbuf && to + ops->len > mtd->size) return -EINVAL; - return part->master->_write_oob(part->master, to + part->offset, ops); + return mtd->parent->_write_oob(mtd->parent, to + mtd->offset, ops); } static int part_write_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct mtd_part *part = PART(mtd); - return part->master->_write_user_prot_reg(part->master, from, len, - retlen, buf); + return mtd->parent->_write_user_prot_reg(mtd->parent, from, len, + retlen, buf); } static int part_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len) { - struct mtd_part *part = PART(mtd); - return part->master->_lock_user_prot_reg(part->master, from, len); + return mtd->parent->_lock_user_prot_reg(mtd->parent, from, len); } #ifndef __UBOOT__ static int part_writev(struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen) { - struct mtd_part *part = PART(mtd); - return part->master->_writev(part->master, vecs, count, - to + part->offset, retlen); + return mtd->parent->_writev(mtd->parent, vecs, count, + to + mtd->offset, retlen); } #endif static int part_erase(struct mtd_info *mtd, struct erase_info *instr) { - struct mtd_part *part = PART(mtd); int ret; - instr->addr += part->offset; - ret = part->master->_erase(part->master, instr); + instr->addr += mtd->offset; + ret = mtd->parent->_erase(mtd->parent, instr); if (ret) { if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN) - instr->fail_addr -= part->offset; - instr->addr -= part->offset; + instr->fail_addr -= mtd->offset; + instr->addr -= mtd->offset; } return ret; } @@ -481,11 +443,9 @@ static int part_erase(struct mtd_info *mtd, struct erase_info *instr) void mtd_erase_callback(struct erase_info *instr) { if (instr->mtd->_erase == part_erase) { - struct mtd_part *part = PART(instr->mtd); - if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN) - instr->fail_addr -= part->offset; - instr->addr -= part->offset; + instr->fail_addr -= instr->mtd->offset; + instr->addr -= instr->mtd->offset; } if (instr->callback) instr->callback(instr); @@ -494,107 +454,112 @@ EXPORT_SYMBOL_GPL(mtd_erase_callback); static int part_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { - struct mtd_part *part = PART(mtd); - return part->master->_lock(part->master, ofs + part->offset, len); + return mtd->parent->_lock(mtd->parent, ofs + mtd->offset, len); } static int part_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { - struct mtd_part *part = PART(mtd); - return part->master->_unlock(part->master, ofs + part->offset, len); + return mtd->parent->_unlock(mtd->parent, ofs + mtd->offset, len); } static int part_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len) { - struct mtd_part *part = PART(mtd); - return part->master->_is_locked(part->master, ofs + part->offset, len); + return mtd->parent->_is_locked(mtd->parent, ofs + mtd->offset, len); } static void part_sync(struct mtd_info *mtd) { - struct mtd_part *part = PART(mtd); - part->master->_sync(part->master); + mtd->parent->_sync(mtd->parent); } #ifndef __UBOOT__ static int part_suspend(struct mtd_info *mtd) { - struct mtd_part *part = PART(mtd); - return part->master->_suspend(part->master); + return mtd->parent->_suspend(mtd->parent); } static void part_resume(struct mtd_info *mtd) { - struct mtd_part *part = PART(mtd); - part->master->_resume(part->master); + mtd->parent->_resume(mtd->parent); } #endif static int part_block_isreserved(struct mtd_info *mtd, loff_t ofs) { - struct mtd_part *part = PART(mtd); - ofs += part->offset; - return part->master->_block_isreserved(part->master, ofs); + ofs += mtd->offset; + return mtd->parent->_block_isreserved(mtd->parent, ofs); } static int part_block_isbad(struct mtd_info *mtd, loff_t ofs) { - struct mtd_part *part = PART(mtd); - ofs += part->offset; - return part->master->_block_isbad(part->master, ofs); + ofs += mtd->offset; + return mtd->parent->_block_isbad(mtd->parent, ofs); } static int part_block_markbad(struct mtd_info *mtd, loff_t ofs) { - struct mtd_part *part = PART(mtd); int res; - ofs += part->offset; - res = part->master->_block_markbad(part->master, ofs); + ofs += mtd->offset; + res = mtd->parent->_block_markbad(mtd->parent, ofs); if (!res) mtd->ecc_stats.badblocks++; return res; } -static inline void free_partition(struct mtd_part *p) +static inline void free_partition(struct mtd_info *p) { - kfree(p->mtd.name); + kfree(p->name); kfree(p); } /* * This function unregisters and destroy all slave MTD objects which are - * attached to the given master MTD object. + * attached to the given master MTD object, recursively. */ +static int do_del_mtd_partitions(struct mtd_info *master) +{ + struct mtd_info *slave, *next; + int ret, err = 0; + + list_for_each_entry_safe(slave, next, &master->partitions, node) { + if (mtd_has_partitions(slave)) + del_mtd_partitions(slave); + + debug("Deleting %s MTD partition\n", slave->name); + ret = del_mtd_device(slave); + if (ret < 0) { + printf("Error when deleting partition \"%s\" (%d)\n", + slave->name, ret); + err = ret; + continue; + } + + list_del(&slave->node); + free_partition(slave); + } + + return err; +} int del_mtd_partitions(struct mtd_info *master) { - struct mtd_part *slave, *next; - int ret, err = 0; + int ret; debug("Deleting MTD partitions on \"%s\":\n", master->name); mutex_lock(&mtd_partitions_mutex); - list_for_each_entry_safe(slave, next, &mtd_partitions, list) - if (slave->master == master) { - ret = del_mtd_device(&slave->mtd); - if (ret < 0) { - err = ret; - continue; - } - list_del(&slave->list); - free_partition(slave); - } + ret = do_del_mtd_partitions(master); mutex_unlock(&mtd_partitions_mutex); - return err; + return ret; } -static struct mtd_part *allocate_partition(struct mtd_info *master, - const struct mtd_partition *part, int partno, - uint64_t cur_offset) +static struct mtd_info *allocate_partition(struct mtd_info *master, + const struct mtd_partition *part, + int partno, uint64_t cur_offset) { - struct mtd_part *slave; + struct mtd_info *slave; char *name; /* allocate the partition structure */ @@ -609,85 +574,87 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, } /* set up the MTD object for this partition */ - slave->mtd.type = master->type; - slave->mtd.flags = master->flags & ~part->mask_flags; - slave->mtd.size = part->size; - slave->mtd.writesize = master->writesize; - slave->mtd.writebufsize = master->writebufsize; - slave->mtd.oobsize = master->oobsize; - slave->mtd.oobavail = master->oobavail; - slave->mtd.subpage_sft = master->subpage_sft; - - slave->mtd.name = name; - slave->mtd.owner = master->owner; + slave->type = master->type; + slave->flags = master->flags & ~part->mask_flags; + slave->size = part->size; + slave->writesize = master->writesize; + slave->writebufsize = master->writebufsize; + slave->oobsize = master->oobsize; + slave->oobavail = master->oobavail; + slave->subpage_sft = master->subpage_sft; + + slave->name = name; + slave->owner = master->owner; #ifndef __UBOOT__ - slave->mtd.backing_dev_info = master->backing_dev_info; + slave->backing_dev_info = master->backing_dev_info; /* NOTE: we don't arrange MTDs as a tree; it'd be error-prone * to have the same data be in two different partitions. */ - slave->mtd.dev.parent = master->dev.parent; + slave->dev.parent = master->dev.parent; #endif if (master->_read) - slave->mtd._read = part_read; + slave->_read = part_read; if (master->_write) - slave->mtd._write = part_write; + slave->_write = part_write; if (master->_panic_write) - slave->mtd._panic_write = part_panic_write; + slave->_panic_write = part_panic_write; #ifndef __UBOOT__ if (master->_point && master->_unpoint) { - slave->mtd._point = part_point; - slave->mtd._unpoint = part_unpoint; + slave->_point = part_point; + slave->_unpoint = part_unpoint; } #endif if (master->_get_unmapped_area) - slave->mtd._get_unmapped_area = part_get_unmapped_area; + slave->_get_unmapped_area = part_get_unmapped_area; if (master->_read_oob) - slave->mtd._read_oob = part_read_oob; + slave->_read_oob = part_read_oob; if (master->_write_oob) - slave->mtd._write_oob = part_write_oob; + slave->_write_oob = part_write_oob; if (master->_read_user_prot_reg) - slave->mtd._read_user_prot_reg = part_read_user_prot_reg; + slave->_read_user_prot_reg = part_read_user_prot_reg; if (master->_read_fact_prot_reg) - slave->mtd._read_fact_prot_reg = part_read_fact_prot_reg; + slave->_read_fact_prot_reg = part_read_fact_prot_reg; if (master->_write_user_prot_reg) - slave->mtd._write_user_prot_reg = part_write_user_prot_reg; + slave->_write_user_prot_reg = part_write_user_prot_reg; if (master->_lock_user_prot_reg) - slave->mtd._lock_user_prot_reg = part_lock_user_prot_reg; + slave->_lock_user_prot_reg = part_lock_user_prot_reg; if (master->_get_user_prot_info) - slave->mtd._get_user_prot_info = part_get_user_prot_info; + slave->_get_user_prot_info = part_get_user_prot_info; if (master->_get_fact_prot_info) - slave->mtd._get_fact_prot_info = part_get_fact_prot_info; + slave->_get_fact_prot_info = part_get_fact_prot_info; if (master->_sync) - slave->mtd._sync = part_sync; + slave->_sync = part_sync; #ifndef __UBOOT__ if (!partno && !master->dev.class && master->_suspend && master->_resume) { - slave->mtd._suspend = part_suspend; - slave->mtd._resume = part_resume; + slave->_suspend = part_suspend; + slave->_resume = part_resume; } if (master->_writev) - slave->mtd._writev = part_writev; + slave->_writev = part_writev; #endif if (master->_lock) - slave->mtd._lock = part_lock; + slave->_lock = part_lock; if (master->_unlock) - slave->mtd._unlock = part_unlock; + slave->_unlock = part_unlock; if (master->_is_locked) - slave->mtd._is_locked = part_is_locked; + slave->_is_locked = part_is_locked; if (master->_block_isreserved) - slave->mtd._block_isreserved = part_block_isreserved; + slave->_block_isreserved = part_block_isreserved; if (master->_block_isbad) - slave->mtd._block_isbad = part_block_isbad; + slave->_block_isbad = part_block_isbad; if (master->_block_markbad) - slave->mtd._block_markbad = part_block_markbad; - slave->mtd._erase = part_erase; - slave->master = master; + slave->_block_markbad = part_block_markbad; + slave->_erase = part_erase; + slave->parent = master; slave->offset = part->offset; + INIT_LIST_HEAD(&slave->partitions); + INIT_LIST_HEAD(&slave->node); if (slave->offset == MTDPART_OFS_APPEND) slave->offset = cur_offset; @@ -703,41 +670,41 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, } if (slave->offset == MTDPART_OFS_RETAIN) { slave->offset = cur_offset; - if (master->size - slave->offset >= slave->mtd.size) { - slave->mtd.size = master->size - slave->offset - - slave->mtd.size; + if (master->size - slave->offset >= slave->size) { + slave->size = master->size - slave->offset + - slave->size; } else { debug("mtd partition \"%s\" doesn't have enough space: %#llx < %#llx, disabled\n", part->name, master->size - slave->offset, - slave->mtd.size); + slave->size); /* register to preserve ordering */ goto out_register; } } - if (slave->mtd.size == MTDPART_SIZ_FULL) - slave->mtd.size = master->size - slave->offset; + if (slave->size == MTDPART_SIZ_FULL) + slave->size = master->size - slave->offset; debug("0x%012llx-0x%012llx : \"%s\"\n", (unsigned long long)slave->offset, - (unsigned long long)(slave->offset + slave->mtd.size), slave->mtd.name); + (unsigned long long)(slave->offset + slave->size), slave->name); /* let's do some sanity checks */ if (slave->offset >= master->size) { /* let's register it anyway to preserve ordering */ slave->offset = 0; - slave->mtd.size = 0; + slave->size = 0; printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n", part->name); goto out_register; } - if (slave->offset + slave->mtd.size > master->size) { - slave->mtd.size = master->size - slave->offset; + if (slave->offset + slave->size > master->size) { + slave->size = master->size - slave->offset; printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n", - part->name, master->name, (unsigned long long)slave->mtd.size); + part->name, master->name, slave->size); } if (master->numeraseregions > 1) { /* Deal with variable erase size stuff */ int i, max = master->numeraseregions; - u64 end = slave->offset + slave->mtd.size; + u64 end = slave->offset + slave->size; struct mtd_erase_region_info *regions = master->eraseregions; /* Find the first erase regions which is part of this @@ -750,44 +717,43 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, /* Pick biggest erasesize */ for (; i < max && regions[i].offset < end; i++) { - if (slave->mtd.erasesize < regions[i].erasesize) { - slave->mtd.erasesize = regions[i].erasesize; - } + if (slave->erasesize < regions[i].erasesize) + slave->erasesize = regions[i].erasesize; } - BUG_ON(slave->mtd.erasesize == 0); + WARN_ON(slave->erasesize == 0); } else { /* Single erase size */ - slave->mtd.erasesize = master->erasesize; + slave->erasesize = master->erasesize; } - if ((slave->mtd.flags & MTD_WRITEABLE) && - mtd_mod_by_eb(slave->offset, &slave->mtd)) { + if ((slave->flags & MTD_WRITEABLE) && + mtd_mod_by_eb(slave->offset, slave)) { /* Doesn't start on a boundary of major erase size */ /* FIXME: Let it be writable if it is on a boundary of * _minor_ erase size though */ - slave->mtd.flags &= ~MTD_WRITEABLE; + slave->flags &= ~MTD_WRITEABLE; printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n", part->name); } - if ((slave->mtd.flags & MTD_WRITEABLE) && - mtd_mod_by_eb(slave->mtd.size, &slave->mtd)) { - slave->mtd.flags &= ~MTD_WRITEABLE; + if ((slave->flags & MTD_WRITEABLE) && + mtd_mod_by_eb(slave->size, slave)) { + slave->flags &= ~MTD_WRITEABLE; printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n", part->name); } - slave->mtd.ecclayout = master->ecclayout; - slave->mtd.ecc_step_size = master->ecc_step_size; - slave->mtd.ecc_strength = master->ecc_strength; - slave->mtd.bitflip_threshold = master->bitflip_threshold; + slave->ecclayout = master->ecclayout; + slave->ecc_step_size = master->ecc_step_size; + slave->ecc_strength = master->ecc_strength; + slave->bitflip_threshold = master->bitflip_threshold; if (master->_block_isbad) { uint64_t offs = 0; - while (offs < slave->mtd.size) { + while (offs < slave->size) { if (mtd_block_isbad(master, offs + slave->offset)) - slave->mtd.ecc_stats.badblocks++; - offs += slave->mtd.erasesize; + slave->ecc_stats.badblocks++; + offs += slave->erasesize; } } @@ -800,7 +766,7 @@ int mtd_add_partition(struct mtd_info *master, const char *name, long long offset, long long length) { struct mtd_partition part; - struct mtd_part *p, *new; + struct mtd_info *p, *new; uint64_t start, end; int ret = 0; @@ -829,21 +795,20 @@ int mtd_add_partition(struct mtd_info *master, const char *name, end = offset + length; mutex_lock(&mtd_partitions_mutex); - list_for_each_entry(p, &mtd_partitions, list) - if (p->master == master) { - if ((start >= p->offset) && - (start < (p->offset + p->mtd.size))) - goto err_inv; - - if ((end >= p->offset) && - (end < (p->offset + p->mtd.size))) - goto err_inv; - } + list_for_each_entry(p, &master->partitions, node) { + if (start >= p->offset && + (start < (p->offset + p->size))) + goto err_inv; + + if (end >= p->offset && + (end < (p->offset + p->size))) + goto err_inv; + } - list_add(&new->list, &mtd_partitions); + list_add_tail(&new->node, &master->partitions); mutex_unlock(&mtd_partitions_mutex); - add_mtd_device(&new->mtd); + add_mtd_device(new); return ret; err_inv: @@ -855,18 +820,17 @@ EXPORT_SYMBOL_GPL(mtd_add_partition); int mtd_del_partition(struct mtd_info *master, int partno) { - struct mtd_part *slave, *next; + struct mtd_info *slave, *next; int ret = -EINVAL; mutex_lock(&mtd_partitions_mutex); - list_for_each_entry_safe(slave, next, &mtd_partitions, list) - if ((slave->master == master) && - (slave->mtd.index == partno)) { - ret = del_mtd_device(&slave->mtd); + list_for_each_entry_safe(slave, next, &master->partitions, node) + if (slave->index == partno) { + ret = del_mtd_device(slave); if (ret < 0) break; - list_del(&slave->list); + list_del(&slave->node); free_partition(slave); break; } @@ -890,20 +854,10 @@ int add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts, int nbparts) { - struct mtd_part *slave; + struct mtd_info *slave; uint64_t cur_offset = 0; int i; -#ifdef __UBOOT__ - /* - * Need to init the list here, since LIST_INIT() does not - * work on platforms where relocation has problems (like MIPS - * & PPC). - */ - if (mtd_partitions.next == NULL) - INIT_LIST_HEAD(&mtd_partitions); -#endif - debug("Creating %d MTD partitions on \"%s\":\n", nbparts, master->name); for (i = 0; i < nbparts; i++) { @@ -912,12 +866,12 @@ int add_mtd_partitions(struct mtd_info *master, return PTR_ERR(slave); mutex_lock(&mtd_partitions_mutex); - list_add(&slave->list, &mtd_partitions); + list_add_tail(&slave->node, &master->partitions); mutex_unlock(&mtd_partitions_mutex); - add_mtd_device(&slave->mtd); + add_mtd_device(slave); - cur_offset = slave->offset + slave->mtd.size; + cur_offset = slave->offset + slave->size; } return 0; @@ -1020,29 +974,12 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types, } #endif -int mtd_is_partition(const struct mtd_info *mtd) -{ - struct mtd_part *part; - int ispart = 0; - - mutex_lock(&mtd_partitions_mutex); - list_for_each_entry(part, &mtd_partitions, list) - if (&part->mtd == mtd) { - ispart = 1; - break; - } - mutex_unlock(&mtd_partitions_mutex); - - return ispart; -} -EXPORT_SYMBOL_GPL(mtd_is_partition); - /* Returns the size of the entire flash chip */ uint64_t mtd_get_device_size(const struct mtd_info *mtd) { - if (!mtd_is_partition(mtd)) - return mtd->size; + if (mtd_is_partition(mtd)) + return mtd->parent->size; - return PART(mtd)->master->size; + return mtd->size; } EXPORT_SYMBOL_GPL(mtd_get_device_size); diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index af6f4a6..68e5915 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #if IS_ENABLED(CONFIG_DM) #include @@ -307,6 +308,27 @@ struct mtd_info { struct udevice *dev; #endif int usecount; + + /* MTD devices do not have any parent. MTD partitions do. */ + struct mtd_info *parent; + + /* + * Offset of the partition relatively to the parent offset. + * Is 0 for real MTD devices (ie. not partitions). + */ + u64 offset; + + /* + * List node used to add an MTD partition to the parent + * partition list. + */ + struct list_head node; + + /* + * List of partitions attached to this MTD device (the parent + * MTD device can itself be a partition). + */ + struct list_head partitions; }; #if IS_ENABLED(CONFIG_DM) @@ -334,6 +356,16 @@ static inline const struct device_node *mtd_get_of_node(struct mtd_info *mtd) } #endif +static inline bool mtd_is_partition(const struct mtd_info *mtd) +{ + return mtd->parent; +} + +static inline bool mtd_has_partitions(const struct mtd_info *mtd) +{ + return !list_empty(&mtd->partitions); +} + int mtd_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *oobecc); int mtd_ooblayout_find_eccregion(struct mtd_info *mtd, int eccbyte, diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h index 6eea0a5..3822237 100644 --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -81,7 +81,6 @@ extern void register_mtd_parser(struct mtd_part_parser *parser); extern void deregister_mtd_parser(struct mtd_part_parser *parser); #endif -int mtd_is_partition(const struct mtd_info *mtd); int mtd_add_partition(struct mtd_info *master, const char *name, long long offset, long long length); int mtd_del_partition(struct mtd_info *master, int partno); From 5db66b3aee6f2c057706d8715f7e5c472e82f047 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:28 +0200 Subject: [PATCH 32/37] cmd: mtd: add 'mtd' command There should not be a 'nand' command, a 'sf' command and certainly not a new 'spi-nand' command. Write a 'mtd' command instead to manage all MTD devices/partitions at once. This should be the preferred way to access any MTD device. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- cmd/Kconfig | 10 +- cmd/Makefile | 1 + cmd/mtd.c | 473 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mtd/Makefile | 2 +- drivers/mtd/mtd_uboot.c | 161 +++++++++++++++- include/mtd.h | 1 + 6 files changed, 644 insertions(+), 4 deletions(-) create mode 100644 cmd/mtd.c diff --git a/cmd/Kconfig b/cmd/Kconfig index 13d4c99..c9be859 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -864,6 +864,12 @@ config CMD_MMC_SWRITE Enable support for the "mmc swrite" command to write Android sparse images to eMMC. +config CMD_MTD + bool "mtd" + select MTD_PARTITIONS + help + MTD commands support. + config CMD_NAND bool "nand" default y if NAND_SUNXI @@ -1697,14 +1703,14 @@ config CMD_MTDPARTS config MTDIDS_DEFAULT string "Default MTD IDs" - depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH + depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH help Defines a default MTD IDs list for use with MTD partitions in the Linux MTD command line partitions format. config MTDPARTS_DEFAULT string "Default MTD partition scheme" - depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH + depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH help Defines a default MTD partitioning scheme in the Linux MTD command line partitions format diff --git a/cmd/Makefile b/cmd/Makefile index a61fab6..3ba65d4 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_CMD_MISC) += misc.o obj-$(CONFIG_CMD_MMC) += mmc.o obj-$(CONFIG_CMD_MMC_SPI) += mmc_spi.o obj-$(CONFIG_MP) += mp.o +obj-$(CONFIG_CMD_MTD) += mtd.o obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o obj-$(CONFIG_CMD_NAND) += nand.o obj-$(CONFIG_CMD_NET) += net.o diff --git a/cmd/mtd.c b/cmd/mtd.c new file mode 100644 index 0000000..6142223 --- /dev/null +++ b/cmd/mtd.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mtd.c + * + * Generic command to handle basic operations on any memory device. + * + * Copyright: Bootlin, 2018 + * Author: Miquèl Raynal + */ + +#include +#include +#include +#include +#include +#include + +static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) +{ + do_div(len, mtd->writesize); + + return len; +} + +static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) +{ + return !do_div(size, mtd->writesize); +} + +static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) +{ + return !do_div(size, mtd->erasesize); +} + +static void mtd_dump_buf(const u8 *buf, uint len, uint offset) +{ + int i, j; + + for (i = 0; i < len; ) { + printf("0x%08x:\t", offset + i); + for (j = 0; j < 8; j++) + printf("%02x ", buf[i + j]); + printf(" "); + i += 8; + for (j = 0; j < 8; j++) + printf("%02x ", buf[i + j]); + printf("\n"); + i += 8; + } +} + +static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off, + const u8 *buf, u64 len, bool woob) +{ + bool has_pages = mtd->type == MTD_NANDFLASH || + mtd->type == MTD_MLCNANDFLASH; + int npages = mtd_len_to_pages(mtd, len); + uint page; + + if (has_pages) { + for (page = 0; page < npages; page++) { + u64 data_off = page * mtd->writesize; + + printf("\nDump %d data bytes from 0x%08llx:\n", + mtd->writesize, start_off + data_off); + mtd_dump_buf(&buf[data_off], + mtd->writesize, start_off + data_off); + + if (woob) { + u64 oob_off = page * mtd->oobsize; + + printf("Dump %d OOB bytes from page at 0x%08llx:\n", + mtd->oobsize, start_off + data_off); + mtd_dump_buf(&buf[len + oob_off], + mtd->oobsize, 0); + } + } + } else { + printf("\nDump %lld data bytes from 0x%llx:\n", + len, start_off); + mtd_dump_buf(buf, len, start_off); + } +} + +static void mtd_show_parts(struct mtd_info *mtd, int level) +{ + struct mtd_info *part; + int i; + + list_for_each_entry(part, &mtd->partitions, node) { + for (i = 0; i < level; i++) + printf("\t"); + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", + part->offset, part->offset + part->size, part->name); + + mtd_show_parts(part, level + 1); + } +} + +static void mtd_show_device(struct mtd_info *mtd) +{ + /* Device */ + printf("* %s\n", mtd->name); +#if defined(CONFIG_DM) + if (mtd->dev) { + printf(" - device: %s\n", mtd->dev->name); + printf(" - parent: %s\n", mtd->dev->parent->name); + printf(" - driver: %s\n", mtd->dev->driver->name); + } +#endif + + /* MTD device information */ + printf(" - type: "); + switch (mtd->type) { + case MTD_RAM: + printf("RAM\n"); + break; + case MTD_ROM: + printf("ROM\n"); + break; + case MTD_NORFLASH: + printf("NOR flash\n"); + break; + case MTD_NANDFLASH: + printf("NAND flash\n"); + break; + case MTD_DATAFLASH: + printf("Data flash\n"); + break; + case MTD_UBIVOLUME: + printf("UBI volume\n"); + break; + case MTD_MLCNANDFLASH: + printf("MLC NAND flash\n"); + break; + case MTD_ABSENT: + default: + printf("Unknown\n"); + break; + } + + printf(" - block size: 0x%x bytes\n", mtd->erasesize); + printf(" - min I/O: 0x%x bytes\n", mtd->writesize); + + if (mtd->oobsize) { + printf(" - OOB size: %u bytes\n", mtd->oobsize); + printf(" - OOB available: %u bytes\n", mtd->oobavail); + } + + if (mtd->ecc_strength) { + printf(" - ECC strength: %u bits\n", mtd->ecc_strength); + printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); + printf(" - bitflip threshold: %u bits\n", + mtd->bitflip_threshold); + } + + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", + mtd->offset, mtd->offset + mtd->size, mtd->name); + + /* MTD partitions, if any */ + mtd_show_parts(mtd, 1); +} + +/* Logic taken from fs/ubifs/recovery.c:is_empty() */ +static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) +{ + int i; + + for (i = 0; i < op->len; i++) + if (op->datbuf[i] != 0xff) + return false; + + for (i = 0; i < op->ooblen; i++) + if (op->oobbuf[i] != 0xff) + return false; + + return true; +} + +static int do_mtd_list(void) +{ + struct mtd_info *mtd; + int dev_nb = 0; + + /* Ensure all devices (and their partitions) are probed */ + mtd_probe_devices(); + + printf("List of MTD devices:\n"); + mtd_for_each_device(mtd) { + if (!mtd_is_partition(mtd)) + mtd_show_device(mtd); + + dev_nb++; + } + + if (!dev_nb) { + printf("No MTD device found\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int mtd_special_write_oob(struct mtd_info *mtd, u64 off, + struct mtd_oob_ops *io_op, + bool write_empty_pages, bool woob) +{ + int ret = 0; + + /* + * By default, do not write an empty page. + * Skip it by simulating a successful write. + */ + if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { + io_op->retlen = mtd->writesize; + io_op->oobretlen = woob ? mtd->oobsize : 0; + } else { + ret = mtd_write_oob(mtd, off, io_op); + } + + return ret; +} + +static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + struct mtd_info *mtd; + const char *cmd; + char *mtd_name; + + /* All MTD commands need at least two arguments */ + if (argc < 2) + return CMD_RET_USAGE; + + /* Parse the command name and its optional suffixes */ + cmd = argv[1]; + + /* List the MTD devices if that is what the user wants */ + if (strcmp(cmd, "list") == 0) + return do_mtd_list(); + + /* + * The remaining commands require also at least a device ID. + * Check the selected device is valid. Ensure it is probed. + */ + if (argc < 3) + return CMD_RET_USAGE; + + mtd_name = argv[2]; + mtd_probe_devices(); + mtd = get_mtd_device_nm(mtd_name); + if (IS_ERR_OR_NULL(mtd)) { + printf("MTD device %s not found, ret %ld\n", + mtd_name, PTR_ERR(mtd)); + return CMD_RET_FAILURE; + } + put_mtd_device(mtd); + + argc -= 3; + argv += 3; + + /* Do the parsing */ + if (!strncmp(cmd, "read", 4) || !strncmp(cmd, "dump", 4) || + !strncmp(cmd, "write", 5)) { + bool has_pages = mtd->type == MTD_NANDFLASH || + mtd->type == MTD_MLCNANDFLASH; + bool dump, read, raw, woob, write_empty_pages; + struct mtd_oob_ops io_op = {}; + uint user_addr = 0, npages; + u64 start_off, off, len, remaining, default_len; + u32 oob_len; + u8 *buf; + int ret; + + dump = !strncmp(cmd, "dump", 4); + read = dump || !strncmp(cmd, "read", 4); + raw = strstr(cmd, ".raw"); + woob = strstr(cmd, ".oob"); + write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); + + if (!dump) { + if (!argc) + return CMD_RET_USAGE; + + user_addr = simple_strtoul(argv[0], NULL, 16); + argc--; + argv++; + } + + start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; + if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { + printf("Offset not aligned with a page (0x%x)\n", + mtd->writesize); + return CMD_RET_FAILURE; + } + + default_len = dump ? mtd->writesize : mtd->size; + len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : + default_len; + if (!mtd_is_aligned_with_min_io_size(mtd, len)) { + len = round_up(len, mtd->writesize); + printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", + mtd->writesize, len); + } + + remaining = len; + npages = mtd_len_to_pages(mtd, len); + oob_len = woob ? npages * mtd->oobsize : 0; + + if (dump) + buf = kmalloc(len + oob_len, GFP_KERNEL); + else + buf = map_sysmem(user_addr, 0); + + if (!buf) { + printf("Could not map/allocate the user buffer\n"); + return CMD_RET_FAILURE; + } + + if (has_pages) + printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", + read ? "Reading" : "Writing", len, npages, start_off, + raw ? " [raw]" : "", woob ? " [oob]" : "", + !read && write_empty_pages ? " [dontskipff]" : ""); + else + printf("%s %lld byte(s) at offset 0x%08llx\n", + read ? "Reading" : "Writing", len, start_off); + + io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; + io_op.len = has_pages ? mtd->writesize : len; + io_op.ooblen = woob ? mtd->oobsize : 0; + io_op.datbuf = buf; + io_op.oobbuf = woob ? &buf[len] : NULL; + + /* Search for the first good block after the given offset */ + off = start_off; + while (mtd_block_isbad(mtd, off)) + off += mtd->erasesize; + + /* Loop over the pages to do the actual read/write */ + while (remaining) { + /* Skip the block if it is bad */ + if (mtd_is_aligned_with_block_size(mtd, off) && + mtd_block_isbad(mtd, off)) { + off += mtd->erasesize; + continue; + } + + if (read) + ret = mtd_read_oob(mtd, off, &io_op); + else + ret = mtd_special_write_oob(mtd, off, &io_op, + write_empty_pages, + woob); + + if (ret) { + printf("Failure while %s at offset 0x%llx\n", + read ? "reading" : "writing", off); + return CMD_RET_FAILURE; + } + + off += io_op.retlen; + remaining -= io_op.retlen; + io_op.datbuf += io_op.retlen; + io_op.oobbuf += io_op.oobretlen; + } + + if (!ret && dump) + mtd_dump_device_buf(mtd, start_off, buf, len, woob); + + if (dump) + kfree(buf); + else + unmap_sysmem(buf); + + if (ret) { + printf("%s on %s failed with error %d\n", + read ? "Read" : "Write", mtd->name, ret); + return CMD_RET_FAILURE; + } + + } else if (!strcmp(cmd, "erase")) { + bool scrub = strstr(cmd, ".dontskipbad"); + struct erase_info erase_op = {}; + u64 off, len; + int ret; + + off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; + len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size; + + if (!mtd_is_aligned_with_block_size(mtd, off)) { + printf("Offset not aligned with a block (0x%x)\n", + mtd->erasesize); + return CMD_RET_FAILURE; + } + + if (!mtd_is_aligned_with_block_size(mtd, len)) { + printf("Size not a multiple of a block (0x%x)\n", + mtd->erasesize); + return CMD_RET_FAILURE; + } + + printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", + off, off + len - 1, mtd_div_by_eb(len, mtd)); + + erase_op.mtd = mtd; + erase_op.addr = off; + erase_op.len = len; + erase_op.scrub = scrub; + + while (erase_op.len) { + ret = mtd_erase(mtd, &erase_op); + + /* Abort if its not a bad block error */ + if (ret != -EIO) + break; + + printf("Skipping bad block at 0x%08llx\n", + erase_op.fail_addr); + + /* Skip bad block and continue behind it */ + erase_op.len -= erase_op.fail_addr - erase_op.addr; + erase_op.len -= mtd->erasesize; + erase_op.addr = erase_op.fail_addr + mtd->erasesize; + } + + if (ret && ret != -EIO) + return CMD_RET_FAILURE; + } else if (!strcmp(cmd, "bad")) { + loff_t off; + + if (!mtd_can_have_bb(mtd)) { + printf("Only NAND-based devices can have bad blocks\n"); + return CMD_RET_SUCCESS; + } + + printf("MTD device %s bad blocks list:\n", mtd->name); + for (off = 0; off < mtd->size; off += mtd->erasesize) + if (mtd_block_isbad(mtd, off)) + printf("\t0x%08llx\n", off); + } else { + return CMD_RET_USAGE; + } + + return CMD_RET_SUCCESS; +} + +static char mtd_help_text[] = +#ifdef CONFIG_SYS_LONGHELP + "- generic operations on memory technology devices\n\n" + "mtd list\n" + "mtd read[.raw][.oob] [ []]\n" + "mtd dump[.raw][.oob] [ []]\n" + "mtd write[.raw][.oob][.dontskipff] [ []]\n" + "mtd erase[.dontskipbad] [ []]\n" + "\n" + "Specific functions:\n" + "mtd bad \n" + "\n" + "With:\n" + "\t: NAND partition/chip name\n" + "\t: user address from/to which data will be retrieved/stored\n" + "\t: offset in in bytes (default: start of the part)\n" + "\t\t* must be block-aligned for erase\n" + "\t\t* must be page-aligned otherwise\n" + "\t: length of the operation in bytes (default: the entire device)\n" + "\t\t* must be a multiple of a block for erase\n" + "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" + "\n" + "The .dontskipff option forces writing empty pages, don't use it if unsure.\n" +#endif + ""; + +U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text); diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index cdf5152..22ceda9 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -3,7 +3,7 @@ # (C) Copyright 2000-2007 # Wolfgang Denk, DENX Software Engineering, wd@denx.de. -ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF))) +ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF)$(CONFIG_CMD_MTD))) obj-y += mtdcore.o mtd_uboot.o endif obj-$(CONFIG_MTD) += mtd-uclass.o diff --git a/drivers/mtd/mtd_uboot.c b/drivers/mtd/mtd_uboot.c index 8d7e789..6a211d5 100644 --- a/drivers/mtd/mtd_uboot.c +++ b/drivers/mtd/mtd_uboot.c @@ -4,8 +4,15 @@ * Heiko Schocher, DENX Software Engineering, hs@denx.de. */ #include +#include +#include +#include /* LEGACY */ #include -#include /* Legacy */ +#include +#include + +#define MTD_NAME_MAX_LEN 20 + /** * mtd_search_alternate_name - Search an alternate name for @mtdname thanks to @@ -68,6 +75,158 @@ int mtd_search_alternate_name(const char *mtdname, char *altname, return -EINVAL; } +#if IS_ENABLED(CONFIG_MTD) +static void mtd_probe_uclass_mtd_devs(void) +{ + struct udevice *dev; + int idx = 0; + + /* Probe devices with DM compliant drivers */ + while (!uclass_find_device(UCLASS_MTD, idx, &dev) && dev) { + mtd_probe(dev); + idx++; + } +} +#else +static void mtd_probe_uclass_mtd_devs(void) { } +#endif + +#if defined(CONFIG_MTD_PARTITIONS) +int mtd_probe_devices(void) +{ + static char *old_mtdparts; + static char *old_mtdids; + const char *mtdparts = env_get("mtdparts"); + const char *mtdids = env_get("mtdids"); + bool remaining_partitions = true; + struct mtd_info *mtd; + + mtd_probe_uclass_mtd_devs(); + + /* Check if mtdparts/mtdids changed since last call, otherwise: exit */ + if (!strcmp(mtdparts, old_mtdparts) && !strcmp(mtdids, old_mtdids)) + return 0; + + /* Update the local copy of mtdparts */ + free(old_mtdparts); + free(old_mtdids); + old_mtdparts = strdup(mtdparts); + old_mtdids = strdup(mtdids); + + /* If at least one partition is still in use, do not delete anything */ + mtd_for_each_device(mtd) { + if (mtd->usecount) { + printf("Partition \"%s\" already in use, aborting\n", + mtd->name); + return -EACCES; + } + } + + /* + * Everything looks clear, remove all partitions. It is not safe to + * remove entries from the mtd_for_each_device loop as it uses idr + * indexes and the partitions removal is done in bulk (all partitions of + * one device at the same time), so break and iterate from start each + * time a new partition is found and deleted. + */ + while (remaining_partitions) { + remaining_partitions = false; + mtd_for_each_device(mtd) { + if (!mtd_is_partition(mtd) && mtd_has_partitions(mtd)) { + del_mtd_partitions(mtd); + remaining_partitions = true; + break; + } + } + } + + /* Start the parsing by ignoring the extra 'mtdparts=' prefix, if any */ + if (strstr(mtdparts, "mtdparts=")) + mtdparts += 9; + + /* For each MTD device in mtdparts */ + while (mtdparts[0] != '\0') { + char mtd_name[MTD_NAME_MAX_LEN], *colon; + struct mtd_partition *parts; + int mtd_name_len, nparts; + int ret; + + colon = strchr(mtdparts, ':'); + if (!colon) { + printf("Wrong mtdparts: %s\n", mtdparts); + return -EINVAL; + } + + mtd_name_len = colon - mtdparts; + strncpy(mtd_name, mtdparts, mtd_name_len); + mtd_name[mtd_name_len] = '\0'; + /* Move the pointer forward (including the ':') */ + mtdparts += mtd_name_len + 1; + mtd = get_mtd_device_nm(mtd_name); + if (IS_ERR_OR_NULL(mtd)) { + char linux_name[MTD_NAME_MAX_LEN]; + + /* + * The MTD device named "mtd_name" does not exist. Try + * to find a correspondance with an MTD device having + * the same type and number as defined in the mtdids. + */ + debug("No device named %s\n", mtd_name); + ret = mtd_search_alternate_name(mtd_name, linux_name, + MTD_NAME_MAX_LEN); + if (!ret) + mtd = get_mtd_device_nm(linux_name); + + /* + * If no device could be found, move the mtdparts + * pointer forward until the next set of partitions. + */ + if (ret || IS_ERR_OR_NULL(mtd)) { + printf("Could not find a valid device for %s\n", + mtd_name); + mtdparts = strchr(mtdparts, ';'); + if (mtdparts) + mtdparts++; + + continue; + } + } + + /* + * Parse the MTD device partitions. It will update the mtdparts + * pointer, create an array of parts (that must be freed), and + * return the number of partition structures in the array. + */ + ret = mtd_parse_partitions(mtd, &mtdparts, &parts, &nparts); + if (ret) { + printf("Could not parse device %s\n", mtd->name); + put_mtd_device(mtd); + return -EINVAL; + } + + if (!nparts) + continue; + + /* Create the new MTD partitions */ + add_mtd_partitions(mtd, parts, nparts); + + /* Free the structures allocated during the parsing */ + mtd_free_parsed_partitions(parts, nparts); + + put_mtd_device(mtd); + } + + return 0; +} +#else +int mtd_probe_devices(void) +{ + mtd_probe_uclass_mtd_devs(); + + return 0; +} +#endif /* defined(CONFIG_MTD_PARTITIONS) */ + /* Legacy */ static int get_part(const char *partname, int *idx, loff_t *off, loff_t *size, diff --git a/include/mtd.h b/include/mtd.h index 75aca26..65fcd3c 100644 --- a/include/mtd.h +++ b/include/mtd.h @@ -9,5 +9,6 @@ #include int mtd_probe(struct udevice *dev); +int mtd_probe_devices(void); #endif /* _MTD_H_ */ From c58fb2cdb3e4abb8c2714b351b3856661573c747 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:29 +0200 Subject: [PATCH 33/37] cmd: ubi: clean the partition handling UBI should not mess with MTD partitions, now that the partitions are handled in a clean way, clean the ubi command and avoid using this uneeded extra-glue to reference the devices. Signed-off-by: Miquel Raynal Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- cmd/Kconfig | 2 ++ cmd/ubi.c | 96 ++++++++++++++++--------------------------------------------- 2 files changed, 27 insertions(+), 71 deletions(-) diff --git a/cmd/Kconfig b/cmd/Kconfig index c9be859..b571e84 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1840,6 +1840,8 @@ config CMD_UBI capabilities. Please, consult the MTD web site for more details (www.linux-mtd.infradead.org). Activate this option if you want to use U-Boot UBI commands. + It is also strongly encouraged to also enable CONFIG_MTD to get full + partition support. config CMD_UBIFS tristate "Enable UBIFS - Unsorted block images filesystem commands" diff --git a/cmd/ubi.c b/cmd/ubi.c index 0a3405a..bb8f97f 100644 --- a/cmd/ubi.c +++ b/cmd/ubi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -29,17 +30,6 @@ /* Private own data */ static struct ubi_device *ubi; -static char buffer[80]; -static int ubi_initialized; - -struct selected_dev { - char part_name[80]; - int selected; - int nr; - struct mtd_info *mtd_info; -}; - -static struct selected_dev ubi_dev; #ifdef CONFIG_CMD_UBIFS int ubifs_is_mounted(void); @@ -404,43 +394,24 @@ int ubi_volume_read(char *volume, char *buf, size_t size) return err; } -static int ubi_dev_scan(struct mtd_info *info, char *ubidev, - const char *vid_header_offset) +static int ubi_dev_scan(struct mtd_info *info, const char *vid_header_offset) { - struct mtd_device *dev; - struct part_info *part; - struct mtd_partition mtd_part; char ubi_mtd_param_buffer[80]; - u8 pnum; int err; - if (find_dev_and_part(ubidev, &dev, &pnum, &part) != 0) - return 1; + if (!vid_header_offset) + sprintf(ubi_mtd_param_buffer, "%s", info->name); + else + sprintf(ubi_mtd_param_buffer, "%s,%s", info->name, + vid_header_offset); - sprintf(buffer, "mtd=%d", pnum); - memset(&mtd_part, 0, sizeof(mtd_part)); - mtd_part.name = buffer; - mtd_part.size = part->size; - mtd_part.offset = part->offset; - add_mtd_partitions(info, &mtd_part, 1); - - strcpy(ubi_mtd_param_buffer, buffer); - if (vid_header_offset) - sprintf(ubi_mtd_param_buffer, "mtd=%d,%s", pnum, - vid_header_offset); err = ubi_mtd_param_parse(ubi_mtd_param_buffer, NULL); - if (err) { - del_mtd_partitions(info); + if (err) return -err; - } err = ubi_init(); - if (err) { - del_mtd_partitions(info); + if (err) return -err; - } - - ubi_initialized = 1; return 0; } @@ -465,50 +436,33 @@ int ubi_detach(void) /* * Call ubi_exit() before re-initializing the UBI subsystem */ - if (ubi_initialized) { + if (ubi) ubi_exit(); - del_mtd_partitions(ubi_dev.mtd_info); - ubi_initialized = 0; - } - ubi_dev.selected = 0; + ubi = NULL; + return 0; } int ubi_part(char *part_name, const char *vid_header_offset) { + struct mtd_info *mtd; int err = 0; - char mtd_dev[16]; - struct mtd_device *dev; - struct part_info *part; - u8 pnum; ubi_detach(); - /* - * Search the mtd device number where this partition - * is located - */ - if (find_dev_and_part(part_name, &dev, &pnum, &part)) { + + mtd_probe_devices(); + mtd = get_mtd_device_nm(part_name); + if (IS_ERR(mtd)) { printf("Partition %s not found!\n", part_name); return 1; } - sprintf(mtd_dev, "%s%d", MTD_DEV_TYPE(dev->id->type), dev->id->num); - ubi_dev.mtd_info = get_mtd_device_nm(mtd_dev); - if (IS_ERR(ubi_dev.mtd_info)) { - printf("Partition %s not found on device %s!\n", part_name, - mtd_dev); - return 1; - } + put_mtd_device(mtd); - ubi_dev.selected = 1; - - strcpy(ubi_dev.part_name, part_name); - err = ubi_dev_scan(ubi_dev.mtd_info, ubi_dev.part_name, - vid_header_offset); + err = ubi_dev_scan(mtd, vid_header_offset); if (err) { printf("UBI init error %d\n", err); printf("Please check, if the correct MTD partition is used (size big enough?)\n"); - ubi_dev.selected = 0; return err; } @@ -539,13 +493,13 @@ static int do_ubi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) /* Print current partition */ if (argc == 2) { - if (!ubi_dev.selected) { - printf("Error, no UBI device/partition selected!\n"); + if (!ubi) { + printf("Error, no UBI device selected!\n"); return 1; } - printf("Device %d: %s, partition %s\n", - ubi_dev.nr, ubi_dev.mtd_info->name, ubi_dev.part_name); + printf("Device %d: %s, MTD partition %s\n", + ubi->ubi_num, ubi->ubi_name, ubi->mtd->name); return 0; } @@ -558,8 +512,8 @@ static int do_ubi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return ubi_part(argv[2], vid_header_offset); } - if ((strcmp(argv[1], "part") != 0) && (!ubi_dev.selected)) { - printf("Error, no UBI device/partition selected!\n"); + if ((strcmp(argv[1], "part") != 0) && !ubi) { + printf("Error, no UBI device selected!\n"); return 1; } From 938db6fe5da3581ed2c17d64c7e016a7a8df5237 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Sat, 29 Sep 2018 12:58:30 +0200 Subject: [PATCH 34/37] cmd: mtdparts: describe as legacy The 'mtdparts' command is not needed anymore. While the environment variable is still valid (and useful, along with the 'mtdids' one), the command has been replaced by 'mtd' which is much more close to the MTD stack and do not add its own specific glue. Signed-off-by: Miquel Raynal Reviewed-by: Stefan Roese Reviewed-by: Boris Brezillon --- cmd/Kconfig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/Kconfig b/cmd/Kconfig index b571e84..12d19fd 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1699,7 +1699,11 @@ config CMD_MTDPARTS bool "MTD partition support" select MTD_DEVICE if (CMD_NAND || NAND) help - MTD partition support + MTD partitioning tool support. + It is strongly encouraged to avoid using this command + anymore along with 'sf', 'nand', 'onenand'. One can still + declare the partitions in the mtdparts environment variable + but better use the MTD stack and the 'mtd' command instead. config MTDIDS_DEFAULT string "Default MTD IDs" From 6ac5909f51fb4e50604f1a89d0535aefcc51f476 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Fri, 7 Sep 2018 14:25:29 +0800 Subject: [PATCH 35/37] spi: designware_spi: Add reset ctrl to driver Add code to reset all reset signals as in SPI DT node. A reset property is an optional feature, so only print out a warning and do not fail if a reset property is not present. If a reset property is discovered, then use it to deassert, thus bringing the IP out of reset. Release reset when _remove(). Signed-off-by: Ley Foon Tan Acked-by: Marek Vasut Reviewed-by: Jagan Teki --- drivers/spi/designware_spi.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index d8b73ea..5cca414 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -111,6 +112,8 @@ struct dw_spi_priv { void *tx_end; void *rx; void *rx_end; + + struct reset_ctl_bulk resets; }; static inline u32 dw_read(struct dw_spi_priv *priv, u32 offset) @@ -231,6 +234,34 @@ err_rate: return -EINVAL; } +static int dw_spi_reset(struct udevice *bus) +{ + int ret; + struct dw_spi_priv *priv = dev_get_priv(bus); + + ret = reset_get_bulk(bus, &priv->resets); + if (ret) { + /* + * Return 0 if error due to !CONFIG_DM_RESET and reset + * DT property is not present. + */ + if (ret == -ENOENT || ret == -ENOTSUPP) + return 0; + + dev_warn(bus, "Can't get reset: %d\n", ret); + return ret; + } + + ret = reset_deassert_bulk(&priv->resets); + if (ret) { + reset_release_bulk(&priv->resets); + dev_err(bus, "Failed to reset: %d\n", ret); + return ret; + } + + return 0; +} + static int dw_spi_probe(struct udevice *bus) { struct dw_spi_platdata *plat = dev_get_platdata(bus); @@ -244,6 +275,10 @@ static int dw_spi_probe(struct udevice *bus) if (ret) return ret; + ret = dw_spi_reset(bus); + if (ret) + return ret; + /* Currently only bits_per_word == 8 supported */ priv->bits_per_word = 8; @@ -478,6 +513,13 @@ static int dw_spi_set_mode(struct udevice *bus, uint mode) return 0; } +static int dw_spi_remove(struct udevice *bus) +{ + struct dw_spi_priv *priv = dev_get_priv(bus); + + return reset_release_bulk(&priv->resets); +} + static const struct dm_spi_ops dw_spi_ops = { .xfer = dw_spi_xfer, .set_speed = dw_spi_set_speed, @@ -502,4 +544,5 @@ U_BOOT_DRIVER(dw_spi) = { .platdata_auto_alloc_size = sizeof(struct dw_spi_platdata), .priv_auto_alloc_size = sizeof(struct dw_spi_priv), .probe = dw_spi_probe, + .remove = dw_spi_remove, }; From 4ccb2f21153fdaab55b189117b9cbeeb233324e1 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Date: Fri, 7 Sep 2018 09:49:34 +0530 Subject: [PATCH 36/37] driver/spi: fsl_qspi: Remove non-DM stuff Convert fsl_qspi.c to complete DM mode. Signed-off-by: Ashish Kumar Tested-by: Rajat Srivastava Tested-by: Ye Li Reviewed-by: Jagan Teki --- drivers/spi/fsl_qspi.c | 138 ------------------------------------------------- 1 file changed, 138 deletions(-) diff --git a/drivers/spi/fsl_qspi.c b/drivers/spi/fsl_qspi.c index 197f41f..1598c4f 100644 --- a/drivers/spi/fsl_qspi.c +++ b/drivers/spi/fsl_qspi.c @@ -84,7 +84,6 @@ DECLARE_GLOBAL_DATA_PTR; /* QSPI max chipselect signals number */ #define FSL_QSPI_MAX_CHIPSELECT_NUM 4 -#ifdef CONFIG_DM_SPI /** * struct fsl_qspi_platdata - platform data for Freescale QSPI * @@ -105,7 +104,6 @@ struct fsl_qspi_platdata { u32 flash_num; u32 num_chipselect; }; -#endif /** * struct fsl_qspi_priv - private data for Freescale QSPI @@ -136,12 +134,6 @@ struct fsl_qspi_priv { struct fsl_qspi_regs *regs; }; -#ifndef CONFIG_DM_SPI -struct fsl_qspi { - struct spi_slave slave; - struct fsl_qspi_priv priv; -}; -#endif static u32 qspi_read32(u32 flags, u32 *addr) { @@ -869,136 +861,7 @@ void qspi_cfg_smpr(struct fsl_qspi_priv *priv, u32 clear_bits, u32 set_bits) smpr_val |= set_bits; qspi_write32(priv->flags, &priv->regs->smpr, smpr_val); } -#ifndef CONFIG_DM_SPI -static unsigned long spi_bases[] = { - QSPI0_BASE_ADDR, -#ifdef CONFIG_MX6SX - QSPI1_BASE_ADDR, -#endif -}; - -static unsigned long amba_bases[] = { - QSPI0_AMBA_BASE, -#ifdef CONFIG_MX6SX - QSPI1_AMBA_BASE, -#endif -}; - -static inline struct fsl_qspi *to_qspi_spi(struct spi_slave *slave) -{ - return container_of(slave, struct fsl_qspi, slave); -} - -struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, - unsigned int max_hz, unsigned int mode) -{ - u32 mcr_val; - struct fsl_qspi *qspi; - struct fsl_qspi_regs *regs; - u32 total_size; - - if (bus >= ARRAY_SIZE(spi_bases)) - return NULL; - - if (cs >= FSL_QSPI_FLASH_NUM) - return NULL; - - qspi = spi_alloc_slave(struct fsl_qspi, bus, cs); - if (!qspi) - return NULL; - -#ifdef CONFIG_SYS_FSL_QSPI_BE - qspi->priv.flags |= QSPI_FLAG_REGMAP_ENDIAN_BIG; -#endif - - regs = (struct fsl_qspi_regs *)spi_bases[bus]; - qspi->priv.regs = regs; - /* - * According cs, use different amba_base to choose the - * corresponding flash devices. - * - * If not, only one flash device is used even if passing - * different cs using `sf probe` - */ - qspi->priv.cur_amba_base = amba_bases[bus] + cs * FSL_QSPI_FLASH_SIZE; - - qspi->slave.max_write_size = TX_BUFFER_SIZE; - - mcr_val = qspi_read32(qspi->priv.flags, ®s->mcr); - - /* Set endianness to LE for i.mx */ - if (IS_ENABLED(CONFIG_MX6) || IS_ENABLED(CONFIG_MX7)) - mcr_val = QSPI_MCR_END_CFD_LE; - - qspi_write32(qspi->priv.flags, ®s->mcr, - QSPI_MCR_RESERVED_MASK | QSPI_MCR_MDIS_MASK | - (mcr_val & QSPI_MCR_END_CFD_MASK)); - - qspi_cfg_smpr(&qspi->priv, - ~(QSPI_SMPR_FSDLY_MASK | QSPI_SMPR_DDRSMP_MASK | - QSPI_SMPR_FSPHS_MASK | QSPI_SMPR_HSENA_MASK), 0); - - total_size = FSL_QSPI_FLASH_SIZE * FSL_QSPI_FLASH_NUM; - /* - * Any read access to non-implemented addresses will provide - * undefined results. - * - * In case single die flash devices, TOP_ADDR_MEMA2 and - * TOP_ADDR_MEMB2 should be initialized/programmed to - * TOP_ADDR_MEMA1 and TOP_ADDR_MEMB1 respectively - in effect, - * setting the size of these devices to 0. This would ensure - * that the complete memory map is assigned to only one flash device. - */ - qspi_write32(qspi->priv.flags, ®s->sfa1ad, - FSL_QSPI_FLASH_SIZE | amba_bases[bus]); - qspi_write32(qspi->priv.flags, ®s->sfa2ad, - FSL_QSPI_FLASH_SIZE | amba_bases[bus]); - qspi_write32(qspi->priv.flags, ®s->sfb1ad, - total_size | amba_bases[bus]); - qspi_write32(qspi->priv.flags, ®s->sfb2ad, - total_size | amba_bases[bus]); - - qspi_set_lut(&qspi->priv); - -#ifdef CONFIG_SYS_FSL_QSPI_AHB - qspi_init_ahb_read(&qspi->priv); -#endif - - qspi_module_disable(&qspi->priv, 0); - - return &qspi->slave; -} - -void spi_free_slave(struct spi_slave *slave) -{ - struct fsl_qspi *qspi = to_qspi_spi(slave); - - free(qspi); -} -int spi_claim_bus(struct spi_slave *slave) -{ - return 0; -} - -void spi_release_bus(struct spi_slave *slave) -{ - /* Nothing to do */ -} - -int spi_xfer(struct spi_slave *slave, unsigned int bitlen, - const void *dout, void *din, unsigned long flags) -{ - struct fsl_qspi *qspi = to_qspi_spi(slave); - - return qspi_xfer(&qspi->priv, bitlen, dout, din, flags); -} - -void spi_init(void) -{ - /* Nothing to do */ -} -#else static int fsl_qspi_child_pre_probe(struct udevice *dev) { struct spi_slave *slave = dev_get_parent_priv(dev); @@ -1265,4 +1128,3 @@ U_BOOT_DRIVER(fsl_qspi) = { .probe = fsl_qspi_probe, .child_pre_probe = fsl_qspi_child_pre_probe, }; -#endif From b3bec2525604d6b42bb9e7fd719c84b022447db3 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 25 Aug 2018 19:34:24 +0200 Subject: [PATCH 37/37] spi: sh_qspi: Add DM support to SH QSPI driver Add DM support to the SH QSPI driver while retaining non-DM support. The later is required as this driver is used in SPL which has a size limitation of 16 kiB. Signed-off-by: Marek Vasut Cc: Nobuhiro Iwamatsu [jagan: use proper commit head] Reviewed-by: Jagan Teki --- drivers/spi/sh_qspi.c | 215 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 150 insertions(+), 65 deletions(-) diff --git a/drivers/spi/sh_qspi.c b/drivers/spi/sh_qspi.c index e9123e2..64dfd74 100644 --- a/drivers/spi/sh_qspi.c +++ b/drivers/spi/sh_qspi.c @@ -67,15 +67,12 @@ struct sh_qspi_regs { }; struct sh_qspi_slave { +#ifndef CONFIG_DM_SPI struct spi_slave slave; +#endif struct sh_qspi_regs *regs; }; -static inline struct sh_qspi_slave *to_sh_qspi(struct spi_slave *slave) -{ - return container_of(slave, struct sh_qspi_slave, slave); -} - static void sh_qspi_init(struct sh_qspi_slave *ss) { /* QSPI initialize */ @@ -119,15 +116,8 @@ static void sh_qspi_init(struct sh_qspi_slave *ss) setbits_8(&ss->regs->spcr, SPCR_SPE); } -int spi_cs_is_valid(unsigned int bus, unsigned int cs) -{ - return 1; -} - -void spi_cs_activate(struct spi_slave *slave) +static void sh_qspi_cs_activate(struct sh_qspi_slave *ss) { - struct sh_qspi_slave *ss = to_sh_qspi(slave); - /* Set master mode only */ writeb(SPCR_MSTR, &ss->regs->spcr); @@ -147,61 +137,15 @@ void spi_cs_activate(struct spi_slave *slave) setbits_8(&ss->regs->spcr, SPCR_SPE); } -void spi_cs_deactivate(struct spi_slave *slave) +static void sh_qspi_cs_deactivate(struct sh_qspi_slave *ss) { - struct sh_qspi_slave *ss = to_sh_qspi(slave); - /* Disable SPI Function */ clrbits_8(&ss->regs->spcr, SPCR_SPE); } -void spi_init(void) -{ - /* nothing to do */ -} - -struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, - unsigned int max_hz, unsigned int mode) -{ - struct sh_qspi_slave *ss; - - if (!spi_cs_is_valid(bus, cs)) - return NULL; - - ss = spi_alloc_slave(struct sh_qspi_slave, bus, cs); - if (!ss) { - printf("SPI_error: Fail to allocate sh_qspi_slave\n"); - return NULL; - } - - ss->regs = (struct sh_qspi_regs *)SH_QSPI_BASE; - - /* Init SH QSPI */ - sh_qspi_init(ss); - - return &ss->slave; -} - -void spi_free_slave(struct spi_slave *slave) +static int sh_qspi_xfer_common(struct sh_qspi_slave *ss, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) { - struct sh_qspi_slave *spi = to_sh_qspi(slave); - - free(spi); -} - -int spi_claim_bus(struct spi_slave *slave) -{ - return 0; -} - -void spi_release_bus(struct spi_slave *slave) -{ -} - -int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, - void *din, unsigned long flags) -{ - struct sh_qspi_slave *ss = to_sh_qspi(slave); u32 nbyte, chunk; int i, ret = 0; u8 dtdata = 0, drdata; @@ -210,7 +154,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, if (dout == NULL && din == NULL) { if (flags & SPI_XFER_END) - spi_cs_deactivate(slave); + sh_qspi_cs_deactivate(ss); return 0; } @@ -222,7 +166,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, nbyte = bitlen / 8; if (flags & SPI_XFER_BEGIN) { - spi_cs_activate(slave); + sh_qspi_cs_activate(ss); /* Set 1048576 byte */ writel(0x100000, spbmul0); @@ -273,7 +217,148 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, } if (flags & SPI_XFER_END) - spi_cs_deactivate(slave); + sh_qspi_cs_deactivate(ss); return ret; } + +#ifndef CONFIG_DM_SPI +static inline struct sh_qspi_slave *to_sh_qspi(struct spi_slave *slave) +{ + return container_of(slave, struct sh_qspi_slave, slave); +} + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + return 1; +} + +void spi_cs_activate(struct spi_slave *slave) +{ + struct sh_qspi_slave *ss = to_sh_qspi(slave); + + sh_qspi_cs_activate(ss); +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct sh_qspi_slave *ss = to_sh_qspi(slave); + + sh_qspi_cs_deactivate(ss); +} + +void spi_init(void) +{ + /* nothing to do */ +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct sh_qspi_slave *ss; + + if (!spi_cs_is_valid(bus, cs)) + return NULL; + + ss = spi_alloc_slave(struct sh_qspi_slave, bus, cs); + if (!ss) { + printf("SPI_error: Fail to allocate sh_qspi_slave\n"); + return NULL; + } + + ss->regs = (struct sh_qspi_regs *)SH_QSPI_BASE; + + /* Init SH QSPI */ + sh_qspi_init(ss); + + return &ss->slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ + struct sh_qspi_slave *spi = to_sh_qspi(slave); + + free(spi); +} + +int spi_claim_bus(struct spi_slave *slave) +{ + return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct sh_qspi_slave *ss = to_sh_qspi(slave); + + return sh_qspi_xfer_common(ss, bitlen, dout, din, flags); +} + +#else + +#include + +static int sh_qspi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct sh_qspi_slave *ss = dev_get_platdata(bus); + + return sh_qspi_xfer_common(ss, bitlen, dout, din, flags); +} + +static int sh_qspi_set_speed(struct udevice *dev, uint speed) +{ + /* This is a SPI NOR controller, do nothing. */ + return 0; +} + +static int sh_qspi_set_mode(struct udevice *dev, uint mode) +{ + /* This is a SPI NOR controller, do nothing. */ + return 0; +} + +static int sh_qspi_probe(struct udevice *dev) +{ + struct sh_qspi_slave *ss = dev_get_platdata(dev); + + sh_qspi_init(ss); + + return 0; +} + +static int sh_qspi_ofdata_to_platdata(struct udevice *dev) +{ + struct sh_qspi_slave *plat = dev_get_platdata(dev); + + plat->regs = (struct sh_qspi_regs *)dev_read_addr(dev); + + return 0; +} + +static const struct dm_spi_ops sh_qspi_ops = { + .xfer = sh_qspi_xfer, + .set_speed = sh_qspi_set_speed, + .set_mode = sh_qspi_set_mode, +}; + +static const struct udevice_id sh_qspi_ids[] = { + { .compatible = "renesas,qspi" }, + { } +}; + +U_BOOT_DRIVER(sh_qspi) = { + .name = "sh_qspi", + .id = UCLASS_SPI, + .of_match = sh_qspi_ids, + .ops = &sh_qspi_ops, + .ofdata_to_platdata = sh_qspi_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct sh_qspi_slave), + .probe = sh_qspi_probe, +}; +#endif