[PATCH] mtd: spinand: add merge-two-spinand function

CGEL cgel.zte at gmail.com
Wed Sep 22 18:52:41 PDT 2021


From: Ren Xiaohui <ren.xiaohui at zte.com.cn>

Combine the two SPI NAND flash in the MTD layer

Signed-off-by: Ren Xiaohui <ren.xiaohui at zte.com.cn>
---
 drivers/mtd/nand/spi/Kconfig          |   8 +
 drivers/mtd/nand/spi/Makefile         |   2 +-
 drivers/mtd/nand/spi/core.c           |  36 +++-
 drivers/mtd/nand/spi/spi_nand_merge.c | 350 ++++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h           |  14 +-
 5 files changed, 400 insertions(+), 10 deletions(-)
 create mode 100644 drivers/mtd/nand/spi/spi_nand_merge.c

diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig
index 3d7649a..6aec3ef 100644
--- a/drivers/mtd/nand/spi/Kconfig
+++ b/drivers/mtd/nand/spi/Kconfig
@@ -7,3 +7,11 @@ menuconfig MTD_SPI_NAND
 	select SPI_MEM
 	help
 	  This is the framework for the SPI NAND device drivers.
+
+menuconfig MTD_SPI_NAND_MERGE
+	tristate "Two SPI NAND merge a mtd device"
+	select MTD_NAND_CORE
+	depends on MTD_SPI_NAND
+	select SPI_MEM
+	help
+	  This is the framework for Two SPI NAND merge a mtd device.
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index 9662b9c..5d6475d 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,3 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
-spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o
+spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o spi_nand_merge.o toshiba.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 2c8685f..ee5e653 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -624,7 +624,7 @@ static int spinand_write_page(struct spinand_device *spinand,
 	return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req);
 }
 
-static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
+int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
 			    struct mtd_oob_ops *ops)
 {
 	struct spinand_device *spinand = mtd_to_spinand(mtd);
@@ -669,8 +669,9 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
 
 	return ret ? ret : max_bitflips;
 }
+EXPORT_SYMBOL(spinand_mtd_read);
 
-static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
+int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
 			     struct mtd_oob_ops *ops)
 {
 	struct spinand_device *spinand = mtd_to_spinand(mtd);
@@ -704,6 +705,7 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
 
 	return ret;
 }
+EXPORT_SYMBOL(spinand_mtd_write);
 
 static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
 {
@@ -725,7 +727,7 @@ static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
 	return false;
 }
 
-static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
+int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
 {
 	struct nand_device *nand = mtd_to_nanddev(mtd);
 	struct spinand_device *spinand = nand_to_spinand(nand);
@@ -739,6 +741,7 @@ static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
 
 	return ret;
 }
+EXPORT_SYMBOL(spinand_mtd_block_isbad);
 
 static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
 {
@@ -764,7 +767,7 @@ static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
 	return spinand_write_page(spinand, &req);
 }
 
-static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
+int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
 {
 	struct nand_device *nand = mtd_to_nanddev(mtd);
 	struct spinand_device *spinand = nand_to_spinand(nand);
@@ -778,6 +781,7 @@ static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
 
 	return ret;
 }
+EXPORT_SYMBOL(spinand_mtd_block_markbad);
 
 static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos)
 {
@@ -808,8 +812,7 @@ static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos)
 	return ret;
 }
 
-static int spinand_mtd_erase(struct mtd_info *mtd,
-			     struct erase_info *einfo)
+int spinand_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo)
 {
 	struct spinand_device *spinand = mtd_to_spinand(mtd);
 	int ret;
@@ -820,8 +823,9 @@ static int spinand_mtd_erase(struct mtd_info *mtd,
 
 	return ret;
 }
+EXPORT_SYMBOL(spinand_mtd_erase);
 
-static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
+int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
 {
 	struct spinand_device *spinand = mtd_to_spinand(mtd);
 	struct nand_device *nand = mtd_to_nanddev(mtd);
@@ -835,6 +839,7 @@ static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
 
 	return ret;
 }
+EXPORT_SYMBOL(spinand_mtd_block_isreserved);
 
 static int spinand_create_dirmap(struct spinand_device *spinand,
 				 unsigned int plane)
@@ -1017,7 +1022,8 @@ spinand_select_op_variant(struct spinand_device *spinand,
  * @table_size: size of the device description table
  * @rdid_method: read id method to match
  *
- * Match between a device ID retrieved through the READ_ID command and an
+ * 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.
@@ -1295,6 +1301,10 @@ static int spinand_probe(struct spi_mem *mem)
 	mtd = spinand_to_mtd(spinand);
 	mtd->dev.parent = &mem->spi->dev;
 
+#ifdef CONFIG_MTD_SPI_NAND_MERGE
+	merge_mtd_register(mtd);
+#endif
+
 	ret = spinand_init(spinand);
 	if (ret)
 		return ret;
@@ -1320,10 +1330,20 @@ static int spinand_remove(struct spi_mem *mem)
 	spinand = spi_mem_get_drvdata(mem);
 	mtd = spinand_to_mtd(spinand);
 
+#ifdef CONFIG_MTD_SPI_NAND_MERGE
+	if (mtd == get_merge_mtd(0) || mtd == get_merge_mtd(1)) {
+		pr_warn("this mtd device is merging, It is illegal.");
+		return 0;
+	}
 	ret = mtd_device_unregister(mtd);
 	if (ret)
 		return ret;
+	spinand_cleanup(spinand);
+#endif
 
+	ret = mtd_device_unregister(mtd);
+	if (ret)
+		return ret;
 	spinand_cleanup(spinand);
 
 	return 0;
diff --git a/drivers/mtd/nand/spi/spi_nand_merge.c b/drivers/mtd/nand/spi/spi_nand_merge.c
new file mode 100644
index 0000000..fde1810
--- /dev/null
+++ b/drivers/mtd/nand/spi/spi_nand_merge.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Merge Nand Chips driver
+ *
+ * Copyright 2021 - 2099 ZTE, Inc
+ *
+ * Author:
+ * Ren Xiaohui <ren.xiaohui at zte.com.cn>
+ */
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spinand.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+struct merge_mtd_info {
+	struct mtd_info *merge_spinand_mtd[2];
+	int merge_mtd_count;
+};
+static struct merge_mtd_info merge_mtd = {0};
+
+struct mtd_info *merge_mtd_register(struct mtd_info *mtd)
+{
+	if (merge_mtd.merge_mtd_count < 2)
+		merge_mtd.merge_spinand_mtd[merge_mtd.merge_mtd_count++] = mtd;
+
+	return 0;
+}
+EXPORT_SYMBOL(merge_mtd_register);
+
+struct mtd_info *get_merge_mtd(int index)
+{
+	return merge_mtd.merge_spinand_mtd[index];
+}
+EXPORT_SYMBOL(get_merge_mtd);
+
+static inline loff_t second_chip_addr(loff_t from)
+{
+	return from - merge_mtd.merge_spinand_mtd[0]->size;
+}
+
+static inline int select_chip(loff_t from, size_t len)
+{
+	int chip;
+	loff_t size = merge_mtd.merge_spinand_mtd[0]->size;
+
+	if ((from + len) <= size)
+		chip = 0;
+	else if (from >= size)
+		chip = 1;
+	else
+		chip = 2;
+
+	return chip;
+}
+
+static int merge_nand_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	int ret, chip;
+	struct erase_info *erase_idx = NULL;
+
+	erase_idx = instr;
+	erase_idx->fail_addr = 0;
+
+	chip = select_chip(erase_idx->addr, erase_idx->len);
+	if (chip == 0) {
+		ret = spinand_mtd_erase(merge_mtd.merge_spinand_mtd[chip], erase_idx);
+	} else if (chip == 1) {
+
+		/* second chip */
+		instr->addr = second_chip_addr(erase_idx->addr);
+		instr->len = erase_idx->len;
+		ret =
+			spinand_mtd_erase(merge_mtd.merge_spinand_mtd[chip], erase_idx);
+	} else {
+		size_t len;
+		/* cross boundary */
+		len = erase_idx->len;
+		erase_idx->len =
+			merge_mtd.merge_spinand_mtd[0]->size - erase_idx->addr;
+		ret = spinand_mtd_erase(merge_mtd.merge_spinand_mtd[0], erase_idx);
+		if (ret)
+			goto bail_out;
+		erase_idx->addr = 0;
+		erase_idx->len = len - erase_idx->len;
+		ret = spinand_mtd_erase(merge_mtd.merge_spinand_mtd[1], erase_idx);
+	}
+
+bail_out:
+	return ret;
+}
+
+static int merge_spinand_read_oob(struct mtd_info *mtd, loff_t from,
+			 struct mtd_oob_ops *ops)
+{
+	int ret, chip;
+	size_t len = 0;
+
+	if (ops->datbuf)
+		len = ops->len;
+	else {
+		pr_warn("This ops->datbuf is NULL!\n");
+		return 0;
+	}
+
+	chip = select_chip(from, len);
+	if (chip == 0) {
+		ret = spinand_mtd_read(merge_mtd.merge_spinand_mtd[chip], from, ops);
+	} else if (chip == 1) {
+		/* second chip */
+		ret = spinand_mtd_read(merge_mtd.merge_spinand_mtd[chip],
+					second_chip_addr(from), ops);
+	} else {
+		loff_t _from[2];
+		size_t orig_len = ops->len;
+		uint8_t	*orig_datbuf = ops->datbuf;
+		size_t	retlen[2];
+		size_t	oobretlen[2];
+
+		/* cross boundary */
+		WARN_ON(ops->datbuf == NULL);
+		_from[0] = from;
+		ops->len = merge_mtd.merge_spinand_mtd[0]->size - from;
+		ret = spinand_mtd_read(merge_mtd.merge_spinand_mtd[0], _from[0], ops);
+		retlen[0] = ops->retlen;
+		oobretlen[0] = ops->oobretlen;
+		if (ret) {
+			ops->len = orig_len;
+			pr_warn("first chip read oob %llu err %d, abort read second chip\n",
+						_from[0], ret);
+			goto bail_out;
+		}
+
+		_from[1] = 0;
+		ops->len = orig_len - ops->len;
+		ops->datbuf += (merge_mtd.merge_spinand_mtd[1]->size - from);
+		ret = spinand_mtd_read(merge_mtd.merge_spinand_mtd[1], _from[1], ops);
+		retlen[1] = ops->retlen;
+		oobretlen[1] = ops->oobretlen;
+		ops->len = orig_len;
+		ops->datbuf = orig_datbuf;
+		ops->retlen = retlen[0] + retlen[1];
+		ops->oobretlen = oobretlen[0] + oobretlen[1];
+	}
+bail_out:
+	return ret;
+}
+
+static int merge_spinand_write_oob(struct mtd_info *mtd, loff_t to,
+			 struct mtd_oob_ops *ops)
+{
+	int ret, chip;
+	size_t len = 0;
+
+	if (ops->datbuf)
+		len = ops->len;
+	else {
+		pr_warn("This ops->datbuf is NULL!\n");
+		return 0;
+	}
+
+	chip = select_chip(to, len);
+	if (chip == 0) {
+		ret =
+				spinand_mtd_write(merge_mtd.merge_spinand_mtd[chip], to, ops);
+	} else if (chip == 1) {
+		/* second chip */
+		ret = spinand_mtd_write(merge_mtd.merge_spinand_mtd[chip],
+					second_chip_addr(to), ops);
+	} else {
+		loff_t _to[2];
+		size_t orig_len = ops->len;
+		uint8_t	*orig_datbuf = ops->datbuf;
+		size_t	retlen[2];
+		size_t	oobretlen[2];
+
+		/* cross boundary */
+		WARN_ON(ops->datbuf == NULL);
+		_to[0] = to;
+		ops->len = merge_mtd.merge_spinand_mtd[0]->size - to;
+		ret = spinand_mtd_write(merge_mtd.merge_spinand_mtd[0], _to[0], ops);
+		retlen[0] = ops->retlen;
+		oobretlen[0] = ops->oobretlen;
+		if (ret) {
+			ops->len = orig_len;
+			pr_warn("first chip write oob %llu err %d, abort write second chip\n",
+						_to[0], ret);
+			goto bail_out;
+		}
+
+		_to[1] = 0;
+		ops->len = orig_len - ops->len;
+		ops->datbuf += (merge_mtd.merge_spinand_mtd[0]->size - to);
+		ret = spinand_mtd_write(merge_mtd.merge_spinand_mtd[1], _to[1], ops);
+		retlen[1] = ops->retlen;
+		oobretlen[1] = ops->oobretlen;
+		ops->len = orig_len;
+		ops->datbuf = orig_datbuf;
+		ops->retlen = retlen[0] + retlen[1];
+		ops->oobretlen = oobretlen[0] + oobretlen[1];
+	}
+bail_out:
+	return ret;
+}
+
+static int merge_spinand_block_isreserved(struct mtd_info *mtd, loff_t ofs)
+{
+	int chip = select_chip(ofs, 0);
+
+	if (chip == 0)
+		return spinand_mtd_block_isreserved(merge_mtd.merge_spinand_mtd[0], ofs);
+	else
+		return spinand_mtd_block_isreserved(merge_mtd.merge_spinand_mtd[1],
+					second_chip_addr(ofs));
+}
+
+static int merge_spinand_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+	int chip = select_chip(ofs, 0);
+
+	if (chip == 0)
+		return spinand_mtd_block_isbad(merge_mtd.merge_spinand_mtd[0],
+						ofs);
+	else
+		return spinand_mtd_block_isbad(merge_mtd.merge_spinand_mtd[1],
+					second_chip_addr(ofs));
+}
+
+static int merge_spinand_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+	int chip = select_chip(ofs, 0);
+
+	if (chip == 0)
+		return spinand_mtd_block_markbad(merge_mtd.merge_spinand_mtd[0], ofs);
+	else
+		return spinand_mtd_block_markbad(merge_mtd.merge_spinand_mtd[1],
+					second_chip_addr(ofs));
+}
+
+static int merge_nanddev_mtd_max_bad_blocks(struct mtd_info *mtd,
+					loff_t offs, size_t len)
+{
+	int chip = select_chip(offs, len);
+
+	if (chip == 0)
+		return nanddev_mtd_max_bad_blocks(merge_mtd.merge_spinand_mtd[0],
+						offs, len);
+	else
+		return nanddev_mtd_max_bad_blocks(merge_mtd.merge_spinand_mtd[1],
+					second_chip_addr(offs), len);
+}
+
+static int merge_mtds(void)
+{
+	int ret = 0;
+	struct mtd_info *merge_dev = NULL;
+
+	merge_dev = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
+	if (!merge_dev) {
+		pr_warn("The reason of merge failure\n");
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	merge_dev->name = "merge_mtd";
+	merge_dev->size =
+		merge_mtd.merge_spinand_mtd[0]->size + merge_mtd.merge_spinand_mtd[1]->size;
+
+	if (merge_mtd.merge_spinand_mtd[0]->erasesize ==
+				merge_mtd.merge_spinand_mtd[1]->erasesize)
+		merge_dev->erasesize = merge_mtd.merge_spinand_mtd[0]->erasesize;
+	else {
+		pr_warn("error: These two spinands have different erasesize!\n");
+		return -1;
+	}
+
+	if (merge_mtd.merge_spinand_mtd[0]->writesize ==
+				merge_mtd.merge_spinand_mtd[1]->writesize)
+		merge_dev->writesize = merge_mtd.merge_spinand_mtd[0]->writesize;
+	else {
+		pr_warn("error: These two spinands have different writesize!\n");
+		return -1;
+	}
+	merge_dev->writebufsize = merge_mtd.merge_spinand_mtd[0]->writebufsize;
+	merge_dev->type = MTD_NANDFLASH;
+	merge_dev->flags = MTD_CAP_NANDFLASH;
+	merge_dev->_read_oob = merge_spinand_read_oob;
+	merge_dev->_write_oob = merge_spinand_write_oob;
+	merge_dev->_block_isbad = merge_spinand_block_isbad;
+	merge_dev->_block_markbad = merge_spinand_block_markbad;
+	merge_dev->_block_isreserved = merge_spinand_block_isreserved;
+	merge_dev->_erase = merge_nand_erase;
+	merge_dev->_max_bad_blocks = merge_nanddev_mtd_max_bad_blocks;
+
+	mtd_device_register(merge_dev, NULL, 0);
+	pr_notice("mtd%d: [%s] erase_size = %dKiB [%d], total_size = %lldKiB [%lld] ",
+				merge_dev->index,
+				merge_dev->name + strlen("merge_mtd: "),
+				merge_dev->erasesize >> 10, merge_dev->erasesize,
+				merge_dev->size >> 10, merge_dev->size);
+
+	return 0;
+}
+
+static int __init merge_mtd_init(void)
+{
+	int ret = 0;
+
+	pr_notice("merge starting...\n");
+
+	if (merge_mtd.merge_spinand_mtd[0] == NULL ||
+				merge_mtd.merge_spinand_mtd[1] == NULL) {
+		pr_warn("whoo...can not get any mtd device\n");
+		return -1;
+	}
+
+	if (merge_mtd.merge_spinand_mtd[0]->name == NULL ||
+		merge_mtd.merge_spinand_mtd[1]->name == NULL) {
+		pr_warn("error:Can not get mtd device name.");
+		pr_warn("the first spinand name:%s, second spinand name:%s.\n",
+			merge_mtd.merge_spinand_mtd[0]->name, merge_mtd.merge_spinand_mtd[1]->name);
+		return -1;
+	}
+	pr_notice("merge_spinand_mtd[0]->name = %s, merge_mtd.merge_spinand_mtd[1]->name = %s\n",
+		merge_mtd.merge_spinand_mtd[0]->name, merge_mtd.merge_spinand_mtd[1]->name);
+
+
+	ret = merge_mtds();
+
+	return ret;
+}
+
+
+static void merge_mtd_exit(void)
+{
+	pr_notice("exit ");
+	mtd_device_unregister(merge_mtd.merge_spinand_mtd[0]);
+	mtd_device_unregister(merge_mtd.merge_spinand_mtd[1]);
+}
+
+late_initcall(merge_mtd_init);
+module_exit(merge_mtd_exit);
+
+MODULE_DESCRIPTION("Merge two spi nand chips under one mtd device");
+MODULE_AUTHOR("renxiaohui <ren.xiaohui at zte.com.cn>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 6988956..9c5fb0f 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -504,12 +504,24 @@ static inline void spinand_set_of_node(struct spinand_device *spinand,
 	nanddev_set_of_node(&spinand->base, np);
 }
 
-int spinand_match_and_init(struct spinand_device *spinand,
+
+struct mtd_info *merge_mtd_register(struct mtd_info *mtd);
+struct mtd_info *get_merge_mtd(int index);
+
+int spinand_match_and_init(struct spinand_device *dev,
 			   const struct spinand_info *table,
 			   unsigned int table_size,
 			   enum spinand_readid_method rdid_method);
 
 int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
 int spinand_select_target(struct spinand_device *spinand, unsigned int target);
+int spinand_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo);
+int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs);
+int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs);
+int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs);
+int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
+			     struct mtd_oob_ops *ops);
+int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
+			    struct mtd_oob_ops *ops);
 
 #endif /* __LINUX_MTD_SPINAND_H */
-- 
2.15.2





More information about the linux-mtd mailing list