linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
From: Anton Vorontsov <avorontsov@ru.mvista.com>
To: Andrew Morton <akpm@linux-foundation.org>
Cc: David Brownell <dbrownell@users.sourceforge.net>,
	Artem Bityutskiy <dedekind1@gmail.com>,
	linux-kernel@vger.kernel.org, linuxppc-dev@ozlabs.org,
	linux-mtd@lists.infradead.org,
	David Woodhouse <dwmw2@infradead.org>
Subject: [PATCH 2/2] mtd: m25p80: Add support for CAT25xxx serial EEPROMs
Date: Wed, 19 Aug 2009 01:46:28 +0400	[thread overview]
Message-ID: <20090818214628.GB22651@oksana.dev.rtsoft.ru> (raw)
In-Reply-To: <20090818214449.GA12848@oksana.dev.rtsoft.ru>

CAT25 chips (as manufactured by On Semiconductor, previously Catalyst
Semiconductor) are similar to the original M25Px0 chips, except:

- Address width can vary (1-2 bytes, in contrast to 3 bytes in M25P
  chips). So, implement convenient m25p_addr2cmd() and m25p_cmdsz()
  calls, and place address width information into flash_info struct;

- Page size can vary, therefore we shouldn't hardcode it, so get rid
  of FLASH_PAGESIZE definition, and place the page size information
  into flash_info struct;

- CAT25 EEPROMs don't need to be erased, so add NO_ERASE flag, and
  propagate it to the mtd subsystem.

Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com>
---
 drivers/mtd/devices/m25p80.c |  172 ++++++++++++++++++++++++------------------
 1 files changed, 97 insertions(+), 75 deletions(-)

diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index b75e319..8930266 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -29,9 +29,6 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/flash.h>
 
-
-#define FLASH_PAGESIZE		256
-
 /* Flash opcodes. */
 #define	OPCODE_WREN		0x06	/* Write enable */
 #define	OPCODE_RDSR		0x05	/* Read status register */
@@ -56,7 +53,7 @@
 
 /* Define max times to check status register before we give up. */
 #define	MAX_READY_WAIT_JIFFIES	(40 * HZ)	/* M25P16 specs 40s max chip erase */
-#define	CMD_SIZE		4
+#define	MAX_CMD_SIZE		4
 
 #ifdef CONFIG_M25PXX_USE_FAST_READ
 #define OPCODE_READ 	OPCODE_FAST_READ
@@ -73,8 +70,10 @@ struct m25p {
 	struct mutex		lock;
 	struct mtd_info		mtd;
 	unsigned		partitioned:1;
+	u16			page_size;
+	u16			addr_width;
 	u8			erase_opcode;
-	u8			command[CMD_SIZE + FAST_READ_DUMMY_BYTE];
+	u8			command[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE];
 };
 
 static inline struct m25p *mtd_to_m25p(struct mtd_info *mtd)
@@ -184,6 +183,19 @@ static int erase_chip(struct m25p *flash)
 	return 0;
 }
 
+static void m25p_addr2cmd(struct m25p *flash, unsigned int addr, u8 *cmd)
+{
+	/* opcode is in cmd[0] */
+	cmd[1] = addr >> (flash->addr_width * 8 -  8);
+	cmd[2] = addr >> (flash->addr_width * 8 - 16);
+	cmd[3] = addr >> (flash->addr_width * 8 - 24);
+}
+
+static int m25p_cmdsz(struct m25p *flash)
+{
+	return 1 + flash->addr_width;
+}
+
 /*
  * Erase one sector of flash memory at offset ``offset'' which is any
  * address within the sector which should be erased.
@@ -205,11 +217,9 @@ static int erase_sector(struct m25p *flash, u32 offset)
 
 	/* Set up command buffer. */
 	flash->command[0] = flash->erase_opcode;
-	flash->command[1] = offset >> 16;
-	flash->command[2] = offset >> 8;
-	flash->command[3] = offset;
+	m25p_addr2cmd(flash, offset, flash->command);
 
-	spi_write(flash->spi, flash->command, CMD_SIZE);
+	spi_write(flash->spi, flash->command, m25p_cmdsz(flash));
 
 	return 0;
 }
@@ -311,7 +321,7 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,
 	 * Should add 1 byte DUMMY_BYTE.
 	 */
 	t[0].tx_buf = flash->command;
-	t[0].len = CMD_SIZE + FAST_READ_DUMMY_BYTE;
+	t[0].len = m25p_cmdsz(flash) + FAST_READ_DUMMY_BYTE;
 	spi_message_add_tail(&t[0], &m);
 
 	t[1].rx_buf = buf;
@@ -338,13 +348,11 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,
 
 	/* Set up the write data buffer. */
 	flash->command[0] = OPCODE_READ;
-	flash->command[1] = from >> 16;
-	flash->command[2] = from >> 8;
-	flash->command[3] = from;
+	m25p_addr2cmd(flash, from, flash->command);
 
 	spi_sync(flash->spi, &m);
 
-	*retlen = m.actual_length - CMD_SIZE - FAST_READ_DUMMY_BYTE;
+	*retlen = m.actual_length - m25p_cmdsz(flash) - FAST_READ_DUMMY_BYTE;
 
 	mutex_unlock(&flash->lock);
 
@@ -382,7 +390,7 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
 	memset(t, 0, (sizeof t));
 
 	t[0].tx_buf = flash->command;
-	t[0].len = CMD_SIZE;
+	t[0].len = m25p_cmdsz(flash);
 	spi_message_add_tail(&t[0], &m);
 
 	t[1].tx_buf = buf;
@@ -400,41 +408,36 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
 
 	/* Set up the opcode in the write buffer. */
 	flash->command[0] = OPCODE_PP;
-	flash->command[1] = to >> 16;
-	flash->command[2] = to >> 8;
-	flash->command[3] = to;
+	m25p_addr2cmd(flash, to, flash->command);
 
-	/* what page do we start with? */
-	page_offset = to % FLASH_PAGESIZE;
+	page_offset = to & (flash->page_size - 1);
 
 	/* do all the bytes fit onto one page? */
-	if (page_offset + len <= FLASH_PAGESIZE) {
+	if (page_offset + len <= flash->page_size) {
 		t[1].len = len;
 
 		spi_sync(flash->spi, &m);
 
-		*retlen = m.actual_length - CMD_SIZE;
+		*retlen = m.actual_length - m25p_cmdsz(flash);
 	} else {
 		u32 i;
 
 		/* the size of data remaining on the first page */
-		page_size = FLASH_PAGESIZE - page_offset;
+		page_size = flash->page_size - page_offset;
 
 		t[1].len = page_size;
 		spi_sync(flash->spi, &m);
 
-		*retlen = m.actual_length - CMD_SIZE;
+		*retlen = m.actual_length - m25p_cmdsz(flash);
 
-		/* write everything in PAGESIZE chunks */
+		/* write everything in flash->page_size chunks */
 		for (i = page_size; i < len; i += page_size) {
 			page_size = len - i;
-			if (page_size > FLASH_PAGESIZE)
-				page_size = FLASH_PAGESIZE;
+			if (page_size > flash->page_size)
+				page_size = flash->page_size;
 
 			/* write the next page to flash */
-			flash->command[1] = (to + i) >> 16;
-			flash->command[2] = (to + i) >> 8;
-			flash->command[3] = (to + i);
+			m25p_addr2cmd(flash, to + i, flash->command);
 
 			t[1].tx_buf = buf + i;
 			t[1].len = page_size;
@@ -446,7 +449,7 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
 			spi_sync(flash->spi, &m);
 
 			if (retlen)
-				*retlen += m.actual_length - CMD_SIZE;
+				*retlen += m.actual_length - m25p_cmdsz(flash);
 		}
 	}
 
@@ -476,16 +479,23 @@ struct flash_info {
 	unsigned	sector_size;
 	u16		n_sectors;
 
+	u16		page_size;
+	u16		addr_width;
+
 	u16		flags;
 #define	SECT_4K		0x01		/* OPCODE_BE_4K works uniformly */
+#define	M25P_NO_ERASE	0x02		/* No erase command needed */
 };
 
-#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags)	\
+#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _page_size,	\
+		_addr_width, _flags)					\
 	((kernel_ulong_t)&(struct flash_info) {				\
 		.jedec_id = (_jedec_id),				\
 		.ext_id = (_ext_id),					\
 		.sector_size = (_sector_size),				\
 		.n_sectors = (_n_sectors),				\
+		.page_size = (_page_size),				\
+		.addr_width = (_addr_width),				\
 		.flags = (_flags),					\
 	})
 
@@ -495,63 +505,70 @@ struct flash_info {
  */
 static const struct spi_device_id m25p_ids[] = {
 	/* Atmel -- some are (confusingly) marketed as "DataFlash" */
-	{ "at25fs010",  INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) },
-	{ "at25fs040",  INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) },
+	{ "at25fs010",  INFO(0x1f6601, 0, 32 * 1024, 4, 256, 3, SECT_4K) },
+	{ "at25fs040",  INFO(0x1f6604, 0, 64 * 1024, 8, 256, 3, SECT_4K) },
 
-	{ "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) },
-	{ "at25df641",  INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },
+	{ "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, 256, 3, SECT_4K) },
+	{ "at25df641",  INFO(0x1f4800, 0, 64 * 1024, 128, 256, 3, SECT_4K) },
 
-	{ "at26f004",   INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) },
-	{ "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
-	{ "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) },
-	{ "at26df321",  INFO(0x1f4701, 0, 64 * 1024, 64, SECT_4K) },
+	{ "at26f004",   INFO(0x1f0400, 0, 64 * 1024, 8, 256, 3, SECT_4K) },
+	{ "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, 256, 3, SECT_4K) },
+	{ "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, 256, 3, SECT_4K) },
+	{ "at26df321",  INFO(0x1f4701, 0, 64 * 1024, 64, 256, 3, SECT_4K) },
 
 	/* Macronix */
-	{ "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) },
+	{ "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 256, 3, 0) },
 
 	/* Spansion -- single (large) sector size only, at least
 	 * for the chips listed here (without boot sectors).
 	 */
-	{ "s25sl004a",  INFO(0x010212, 0, 64 * 1024, 8, 0) },
-	{ "s25sl008a",  INFO(0x010213, 0, 64 * 1024, 16, 0) },
-	{ "s25sl016a",  INFO(0x010214, 0, 64 * 1024, 32, 0) },
-	{ "s25sl032a",  INFO(0x010215, 0, 64 * 1024, 64, 0) },
-	{ "s25sl064a",  INFO(0x010216, 0, 64 * 1024, 128, 0) },
-	{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) },
-	{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
+	{ "s25sl004a",  INFO(0x010212, 0, 64 * 1024, 8, 256, 3, 0) },
+	{ "s25sl008a",  INFO(0x010213, 0, 64 * 1024, 16, 256, 3, 0) },
+	{ "s25sl016a",  INFO(0x010214, 0, 64 * 1024, 32, 256, 3, 0) },
+	{ "s25sl032a",  INFO(0x010215, 0, 64 * 1024, 64, 256, 3, 0) },
+	{ "s25sl064a",  INFO(0x010216, 0, 64 * 1024, 128, 256, 3, 0) },
+	{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 256, 3, 0) },
+	{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 256, 3, 0) },
 
 	/* SST -- large erase sizes are "overlays", "sectors" are 4K */
-	{ "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K) },
-	{ "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, SECT_4K) },
-	{ "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, SECT_4K) },
-	{ "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, SECT_4K) },
+	{ "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, 256, 3, SECT_4K) },
+	{ "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, 256, 3, SECT_4K) },
+	{ "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, 256, 3, SECT_4K) },
+	{ "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, 256, 3, SECT_4K) },
 
 	/* ST Microelectronics -- newer production may have feature updates */
-	{ "m25p05",  INFO(0x202010,  0, 32 * 1024, 2, 0) },
-	{ "m25p10",  INFO(0x202011,  0, 32 * 1024, 4, 0) },
-	{ "m25p20",  INFO(0x202012,  0, 64 * 1024, 4, 0) },
-	{ "m25p40",  INFO(0x202013,  0, 64 * 1024, 8, 0) },
-	{ "m25p80",  INFO(0x202014,  0, 64 * 1024, 16, 0) },
-	{ "m25p16",  INFO(0x202015,  0, 64 * 1024, 32, 0) },
-	{ "m25p32",  INFO(0x202016,  0, 64 * 1024, 64, 0) },
-	{ "m25p64",  INFO(0x202017,  0, 64 * 1024, 128, 0) },
-	{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
-
-	{ "m45pe10", INFO(0x204011,  0, 64 * 1024, 2, 0) },
-	{ "m45pe80", INFO(0x204014,  0, 64 * 1024, 16, 0) },
-	{ "m45pe16", INFO(0x204015,  0, 64 * 1024, 32, 0) },
-
-	{ "m25pe80", INFO(0x208014,  0, 64 * 1024, 16, 0) },
-	{ "m25pe16", INFO(0x208015,  0, 64 * 1024, 32, SECT_4K) },
+	{ "m25p05",  INFO(0x202010,  0, 32 * 1024, 2, 256, 3, 0) },
+	{ "m25p10",  INFO(0x202011,  0, 32 * 1024, 4, 256, 3, 0) },
+	{ "m25p20",  INFO(0x202012,  0, 64 * 1024, 4, 256, 3, 0) },
+	{ "m25p40",  INFO(0x202013,  0, 64 * 1024, 8, 256, 3, 0) },
+	{ "m25p80",  INFO(0x202014,  0, 64 * 1024, 16, 256, 3, 0) },
+	{ "m25p16",  INFO(0x202015,  0, 64 * 1024, 32, 256, 3, 0) },
+	{ "m25p32",  INFO(0x202016,  0, 64 * 1024, 64, 256, 3, 0) },
+	{ "m25p64",  INFO(0x202017,  0, 64 * 1024, 128, 256, 3, 0) },
+	{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 256, 3, 0) },
+
+	{ "m45pe10", INFO(0x204011,  0, 64 * 1024, 2, 256, 3, 0) },
+	{ "m45pe80", INFO(0x204014,  0, 64 * 1024, 16, 256, 3, 0) },
+	{ "m45pe16", INFO(0x204015,  0, 64 * 1024, 32, 256, 3, 0) },
+
+	{ "m25pe80", INFO(0x208014,  0, 64 * 1024, 16, 256, 3, 0) },
+	{ "m25pe16", INFO(0x208015,  0, 64 * 1024, 32, 256, 3, SECT_4K) },
 
 	/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
-	{ "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) },
-	{ "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) },
-	{ "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) },
-	{ "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) },
-	{ "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) },
-	{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
-	{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
+	{ "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, 256, 3, SECT_4K) },
+	{ "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, 256, 3, SECT_4K) },
+	{ "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, 256, 3, SECT_4K) },
+	{ "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, 256, 3, SECT_4K) },
+	{ "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, 256, 3, SECT_4K) },
+	{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, 256, 3, SECT_4K) },
+	{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, 256, 3, SECT_4K) },
+
+	/* Catalyst / On Semiconductor -- non-JEDEC */
+	{ "cat25c11",  INFO(0x0, 0,   16, 8, 16, 1, M25P_NO_ERASE) },
+	{ "cat25c03",  INFO(0x0, 0,   32, 8, 16, 2, M25P_NO_ERASE) },
+	{ "cat25c09",  INFO(0x0, 0,  128, 8, 32, 2, M25P_NO_ERASE) },
+	{ "cat25c17",  INFO(0x0, 0,  256, 8, 32, 2, M25P_NO_ERASE) },
+	{ "cat25128",  INFO(0x0, 0, 2048, 8, 64, 2, M25P_NO_ERASE) },
 	{ },
 };
 MODULE_DEVICE_TABLE(spi, m25p_ids);
@@ -702,7 +719,12 @@ static int __devinit m25p_probe(struct spi_device *spi)
 		flash->mtd.erasesize = info->sector_size;
 	}
 
+	if (info->flags & M25P_NO_ERASE)
+		flash->mtd.flags |= MTD_NO_ERASE;
+
 	flash->mtd.dev.parent = &spi->dev;
+	flash->page_size = info->page_size;
+	flash->addr_width = info->addr_width;
 
 	dev_info(&spi->dev, "%s (%lld Kbytes)\n", id->name,
 			(long long)flash->mtd.size >> 10);
-- 
1.6.3.3

  parent reply	other threads:[~2009-08-18 21:46 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-07-31  0:39 [PATCH v2 0/6] Device table matching for SPI subsystem Anton Vorontsov
2009-07-31  0:40 ` [PATCH 1/6] spi: Add support for device table matching Anton Vorontsov
2009-07-31  0:41 ` [PATCH 2/6] mtd: m25p80: Convert to " Anton Vorontsov
2009-08-04  2:54   ` David Brownell
2009-08-18 21:44     ` Anton Vorontsov
2009-08-18 21:46       ` [PATCH 1/2] mtd: m25p80: Rework probing/JEDEC code Anton Vorontsov
2010-06-12  6:27         ` Barry Song
2010-06-18 13:32           ` Anton Vorontsov
2010-06-21  2:42             ` [Uclinux-dist-devel] [PATCH 1/2] mtd: m25p80: Reworkprobing/JEDEC code Song, Barry
2010-06-21  3:27               ` Barry Song
2010-06-21  7:15                 ` Anton Vorontsov
2010-06-21  7:22                   ` Barry Song
2010-06-21  7:39                     ` Anton Vorontsov
2010-06-21 10:31                       ` Barry Song
2010-06-21 11:20                         ` Anton Vorontsov
2010-06-21 16:34                           ` Mike Frysinger
2010-06-21 16:47                             ` Anton Vorontsov
2010-06-21 16:54                               ` Mike Frysinger
2010-06-22  6:37                           ` Barry Song
2010-06-22 16:55                             ` Anton Vorontsov
2010-06-22 16:57                               ` [PATCH 1/2] mtd: m25p80: Fix false-positive probing Anton Vorontsov
2010-06-22 17:56                                 ` Mike Frysinger
2010-07-08  5:57                                 ` Artem Bityutskiy
2010-06-22 16:57                               ` [PATCH 2/2] mtd: m25p80: Make jedec_probe() return proper errno values Anton Vorontsov
2009-08-18 21:46       ` Anton Vorontsov [this message]
2009-09-22 23:25         ` [PATCH 2/2] mtd: m25p80: Add support for CAT25xxx serial EEPROMs Andrew Morton
2009-09-22 23:40           ` Anton Vorontsov
2009-09-22 21:17     ` [PATCH 2/6] mtd: m25p80: Convert to device table matching Andrew Morton
2009-09-22 23:01       ` Anton Vorontsov
2009-09-22 23:43         ` David Woodhouse
2009-09-22 23:52           ` Andrew Morton
2009-09-22 23:55           ` Anton Vorontsov
2009-09-23  0:02             ` Andrew Morton
2009-08-12 20:45   ` Michael Barkowski
2009-08-12 20:58     ` Anton Vorontsov
2009-08-12 20:58       ` Michael Barkowski
2009-07-31  0:41 ` [PATCH 3/6] of: Remove "stm,m25p40" alias Anton Vorontsov
2009-07-31  0:41 ` [PATCH 4/6] hwmon: adxx: Convert to device table matching Anton Vorontsov
2009-07-31  0:41 ` [PATCH 5/6] hwmon: lm70: " Anton Vorontsov
2009-07-31  0:41 ` [PATCH 6/6] spi: Prefix modalias with "spi:" Anton Vorontsov
2009-08-10  7:35 ` [PATCH v2 0/6] Device table matching for SPI subsystem Artem Bityutskiy
2009-08-18 21:44   ` Anton Vorontsov
2009-08-25 14:14     ` Artem Bityutskiy

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=20090818214628.GB22651@oksana.dev.rtsoft.ru \
    --to=avorontsov@ru.mvista.com \
    --cc=akpm@linux-foundation.org \
    --cc=dbrownell@users.sourceforge.net \
    --cc=dedekind1@gmail.com \
    --cc=dwmw2@infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linuxppc-dev@ozlabs.org \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).