All of lore.kernel.org
 help / color / mirror / Atom feed
From: CGEL <cgel.zte@gmail.com>
To: Miquel Raynal <miquel.raynal@bootlin.com>
Cc: Richard Weinberger <richard@nod.at>,
	Vignesh Raghavendra <vigneshr@ti.com>,
	Patrice Chotard <patrice.chotard@foss.st.com>,
	Boris Brezillon <boris.brezillon@collabora.com>,
	Christophe Kerello <christophe.kerello@foss.st.com>,
	Mark Brown <broonie@kernel.org>, Daniel Palmer <daniel@0x0f.com>,
	Alexander Lobakin <alobakin@pm.me>,
	linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org,
	Ren Xiaohui <ren.xiaohui@zte.com.cn>
Subject: [PATCH] mtd: spinand: add merge-two-spinand function
Date: Thu, 23 Sep 2021 01:52:41 +0000	[thread overview]
Message-ID: <20210923015241.248910-1-ren.xiaohui@zte.com.cn> (raw)

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

Combine the two SPI NAND flash in the MTD layer

Signed-off-by: Ren Xiaohui <ren.xiaohui@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@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@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



WARNING: multiple messages have this Message-ID (diff)
From: CGEL <cgel.zte@gmail.com>
To: Miquel Raynal <miquel.raynal@bootlin.com>
Cc: Richard Weinberger <richard@nod.at>,
	Vignesh Raghavendra <vigneshr@ti.com>,
	Patrice Chotard <patrice.chotard@foss.st.com>,
	Boris Brezillon <boris.brezillon@collabora.com>,
	Christophe Kerello <christophe.kerello@foss.st.com>,
	Mark Brown <broonie@kernel.org>, Daniel Palmer <daniel@0x0f.com>,
	Alexander Lobakin <alobakin@pm.me>,
	linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org,
	Ren Xiaohui <ren.xiaohui@zte.com.cn>
Subject: [PATCH] mtd: spinand: add merge-two-spinand function
Date: Thu, 23 Sep 2021 01:52:41 +0000	[thread overview]
Message-ID: <20210923015241.248910-1-ren.xiaohui@zte.com.cn> (raw)

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

Combine the two SPI NAND flash in the MTD layer

Signed-off-by: Ren Xiaohui <ren.xiaohui@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@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@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



______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

             reply	other threads:[~2021-09-23  1:53 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-23  1:52 CGEL [this message]
2021-09-23  1:52 ` [PATCH] mtd: spinand: add merge-two-spinand function CGEL
2021-09-23  6:42 ` Boris Brezillon
2021-09-23  6:42   ` Boris Brezillon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210923015241.248910-1-ren.xiaohui@zte.com.cn \
    --to=cgel.zte@gmail.com \
    --cc=alobakin@pm.me \
    --cc=boris.brezillon@collabora.com \
    --cc=broonie@kernel.org \
    --cc=christophe.kerello@foss.st.com \
    --cc=daniel@0x0f.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=miquel.raynal@bootlin.com \
    --cc=patrice.chotard@foss.st.com \
    --cc=ren.xiaohui@zte.com.cn \
    --cc=richard@nod.at \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.