All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@cam.ac.uk>
To: Anatolij Gustschin <agust@denx.de>
Cc: linux-kernel@vger.kernel.org, akpm@linux-foundation.org, dzu@denx.de
Subject: Re: [PATCH 1/2] misc/eeprom: add driver for 93xx46 EEPROMs over GPIO
Date: Wed, 25 May 2011 10:31:46 +0100	[thread overview]
Message-ID: <4DDCCC82.90604@cam.ac.uk> (raw)
In-Reply-To: <1306252963-20746-1-git-send-email-agust@denx.de>

On 05/24/11 17:02, Anatolij Gustschin wrote:
> 93xx46 EEPROMs can be connected using GPIO lines. Add a generic
> 93xx46 EEPROM driver using common GPIO API for such configurations.
> A platform is supposed to register appropriate 93xx46 gpio device
> providing GPIO interface description and using this driver
> read/write/erase access to the EEPROM chip can be easily done
> over sysfs files.
Could you explain why this makes more sense than an spi driver and
use of spi_gpio ?

It's microwire compatible according to random google provided datasheet,
which iirc is a particular form of spi (half duplex, spi mode 0 according
to wikipedia)

That would give us a more generally useful driver.
> 
> Signed-off-by: Anatolij Gustschin <agust@denx.de>
> ---
>  drivers/misc/eeprom/Kconfig       |   10 +
>  drivers/misc/eeprom/Makefile      |    1 +
>  drivers/misc/eeprom/gpio-93xx46.c |  525 +++++++++++++++++++++++++++++++++++++
>  include/linux/gpio-93xx46.h       |   19 ++
>  4 files changed, 555 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/misc/eeprom/gpio-93xx46.c
>  create mode 100644 include/linux/gpio-93xx46.h
> 
> diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
> index 9118613..fcceffd 100644
> --- a/drivers/misc/eeprom/Kconfig
> +++ b/drivers/misc/eeprom/Kconfig
> @@ -70,4 +70,14 @@ config EEPROM_93CX6
>  
>  	  If unsure, say N.
>  
> +config EEPROM_GPIO_93XX46
> +	tristate "EEPROM 93XX46 over GPIO support"
> +	depends on GPIOLIB && SYSFS
> +	help
> +	  Driver for the EEPROM chipsets 93xx46x connected with GPIO.
> +	  The driver supports both read and write commands and also
> +	  the command to erase the whole EEPROM.
> +
> +	  If unsure, say N.
> +
>  endmenu
> diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
> index df3d68f..38d8259 100644
> --- a/drivers/misc/eeprom/Makefile
> +++ b/drivers/misc/eeprom/Makefile
> @@ -3,3 +3,4 @@ obj-$(CONFIG_EEPROM_AT25)	+= at25.o
>  obj-$(CONFIG_EEPROM_LEGACY)	+= eeprom.o
>  obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
>  obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
> +obj-$(CONFIG_EEPROM_GPIO_93XX46) += gpio-93xx46.o
> diff --git a/drivers/misc/eeprom/gpio-93xx46.c b/drivers/misc/eeprom/gpio-93xx46.c
> new file mode 100644
> index 0000000..5c7d7dd
> --- /dev/null
> +++ b/drivers/misc/eeprom/gpio-93xx46.c
> @@ -0,0 +1,525 @@
> +/*
> + * Driver for 93xx46 EEPROMs over GPIO lines.
> + *
> + * (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/fs.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio-93xx46.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/sysctl.h>
> +
> +#define OP_START	0x4
> +#define OP_WRITE	(OP_START | 0x1)
> +#define OP_READ		(OP_START | 0x2)
> +#define OP_ERASE	(OP_START | 0x3)
> +#define OP_EWEN		(OP_START | 0x0)
> +#define OP_EWDS		(OP_START | 0x0)
> +#define ADDR_EWDS	0x00
> +#define ADDR_ERAL	0x20
> +#define ADDR_EWEN	0x30
> +#define DELAY		450
> +
> +struct gpio_93xx46_dev {
> +	struct device *dev;
> +	struct gpio_93xx46_platform_data *pdata;
> +	struct bin_attribute bin;
> +	int addrlen;
> +
> +	struct gpio pins[4];
> +};
> +
> +static DEFINE_MUTEX(gpio_93xx46_mutex);
> +
> +static int gpio_93xx46_request_gpios(struct gpio_93xx46_dev *edev)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +	const char *name =  to_platform_device(edev->dev)->name;
> +	int ret;
> +
> +	edev->pins[0].gpio = pd->clk;
> +	edev->pins[0].flags = GPIOF_OUT_INIT_LOW;
> +	edev->pins[0].label = name;
> +	edev->pins[1].gpio = pd->cs;
> +	edev->pins[1].flags = GPIOF_OUT_INIT_LOW;
> +	edev->pins[1].label = name;
> +	edev->pins[2].gpio = pd->din;
> +	edev->pins[2].flags = GPIOF_OUT_INIT_LOW;
> +	edev->pins[2].label = name;
> +	edev->pins[3].gpio = pd->dout;
> +	edev->pins[3].flags = GPIOF_IN;
> +	edev->pins[3].label = name;
> +
> +	ret = gpio_request_array(edev->pins, ARRAY_SIZE(edev->pins));
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static void gpio_93xx46_tx_bit(struct gpio_93xx46_dev *edev, bool bit)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +
> +	if (bit)
> +		gpio_set_value(pd->din, 1);
> +	else
> +		gpio_set_value(pd->din, 0);
> +
> +	ndelay(DELAY);
> +	gpio_set_value(pd->clk, 1);
> +	ndelay(DELAY);
> +	gpio_set_value(pd->clk, 0);
> +}
> +
> +static inline unsigned char
> +gpio_93xx46_rx_byte(struct gpio_93xx46_platform_data *pd)
> +{
> +	int data = 0, i;
> +
> +	for (i = 0; i < 8 ; i++) {
> +		gpio_set_value(pd->clk, 1);
> +		ndelay(DELAY);
> +		gpio_set_value(pd->clk, 0);
> +
> +		if (gpio_get_value(pd->dout))
> +			data |= 1;
> +		data <<= 1;
> +		ndelay(DELAY);
> +	}
> +	return data >>= 1;
> +}
> +
> +static void gpio_93xx46_read(struct gpio_93xx46_dev *edev,
> +			     char *buf, unsigned offs, size_t cnt)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +	int active = !(pd->flags & EE_CS_LOW);
> +	int cmd_addr, len, mask;
> +	int i;
> +
> +	cmd_addr = (OP_READ << edev->addrlen);
> +	if (edev->addrlen == 7) {
> +		cmd_addr |= (offs & 0x7f);
> +		len = 10;
> +	} else {
> +		cmd_addr |= (offs & 0x3f);
> +		len = 9;
> +	}
> +	mask = 1 << (len - 1);
> +
> +	mutex_lock(&gpio_93xx46_mutex);
> +
> +	gpio_set_value(pd->cs, !active);
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 1);
> +	ndelay(DELAY);
> +
> +	if (pd->prepare)
> +		pd->prepare(edev);
> +
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < len; i++, mask >>= 1)
> +		gpio_93xx46_tx_bit(edev, !!(cmd_addr & mask));
> +
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < cnt; i++)
> +		buf[i] = gpio_93xx46_rx_byte(pd);
> +
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 0);
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +
> +	if (pd->finish)
> +		pd->finish(edev);
> +
> +	mutex_unlock(&gpio_93xx46_mutex);
> +}
> +
> +static void gpio_93xx46_ewen(struct gpio_93xx46_dev *edev)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +	int active = !(pd->flags & EE_CS_LOW);
> +	int cmd_addr, len, mask;
> +	int i;
> +
> +	cmd_addr = OP_EWEN << edev->addrlen;
> +	if (edev->addrlen == 7) {
> +		cmd_addr |= ADDR_EWEN << 1;
> +		len = 10;
> +	} else {
> +		cmd_addr |= ADDR_EWEN;
> +		len = 9;
> +	}
> +	mask = 1 << (len - 1);
> +
> +	mutex_lock(&gpio_93xx46_mutex);
> +
> +	gpio_set_value(pd->cs, !active);
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 1);
> +	ndelay(DELAY);
> +
> +	if (pd->prepare)
> +		pd->prepare(edev);
> +
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < len; i++, mask >>= 1)
> +		gpio_93xx46_tx_bit(edev, !!(cmd_addr & mask));
> +
> +	ndelay(DELAY);
> +
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 0);
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +
> +	if (pd->finish)
> +		pd->finish(edev);
> +
> +	mutex_unlock(&gpio_93xx46_mutex);
> +}
> +
> +static void gpio_93xx46_ewds(struct gpio_93xx46_dev *edev)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +	int active = !(pd->flags & EE_CS_LOW);
> +	int cmd_addr, len, mask;
> +	int i;
> +
> +	cmd_addr = OP_EWDS << edev->addrlen;
> +	if (edev->addrlen == 7) {
> +		cmd_addr |= ADDR_EWDS << 1;
> +		len = 10;
> +	} else {
> +		cmd_addr |= ADDR_EWDS;
> +		len = 9;
> +	}
> +	mask = 1 << (len - 1);
> +
> +	mutex_lock(&gpio_93xx46_mutex);
> +
> +	gpio_set_value(pd->cs, !active);
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 1);
> +	ndelay(DELAY);
> +
> +	if (pd->prepare)
> +		pd->prepare(edev);
> +
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < len; i++, mask >>= 1)
> +		gpio_93xx46_tx_bit(edev, !!(cmd_addr & mask));
> +
> +	ndelay(DELAY);
> +
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 0);
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +
> +	if (pd->finish)
> +		pd->finish(edev);
> +
> +	mutex_unlock(&gpio_93xx46_mutex);
> +}
> +
> +static void gpio_93xx46_eral(struct gpio_93xx46_dev *edev)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +	int active = !(pd->flags & EE_CS_LOW);
> +	int cmd_addr, len, mask;
> +	int i, to = 10;
> +
> +	cmd_addr = OP_START << edev->addrlen;
> +	if (edev->addrlen == 7) {
> +		cmd_addr |= ADDR_ERAL << 1;
> +		len = 10;
> +	} else {
> +		cmd_addr |= ADDR_ERAL;
> +		len = 9;
> +	}
> +	mask = 1 << (len - 1);
> +
> +	mutex_lock(&gpio_93xx46_mutex);
> +
> +	gpio_set_value(pd->cs, !active);
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 1);
> +	ndelay(DELAY);
> +
> +	if (pd->prepare)
> +		pd->prepare(edev);
> +
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < len; i++, mask >>= 1)
> +		gpio_93xx46_tx_bit(edev, !!(cmd_addr & mask));
> +
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	while (!gpio_get_value(pd->dout)) {
> +		if (!to--) {
> +			dev_err(edev->dev, "erase not ready timeout\n");
> +			break;
> +		}
> +		mdelay(1);
> +	}
> +
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 0);
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +
> +	if (pd->finish)
> +		pd->finish(edev);
> +
> +	mutex_unlock(&gpio_93xx46_mutex);
> +}
> +
> +static void gpio_93xx46_write(struct gpio_93xx46_dev *edev,
> +			      unsigned offs, char data)
> +{
> +	struct gpio_93xx46_platform_data *pd = edev->pdata;
> +	int active = !(pd->flags & EE_CS_LOW);
> +	int cmd_addr, len, mask;
> +	int i, to = 10;
> +
> +	cmd_addr = (OP_WRITE << edev->addrlen);
> +	if (edev->addrlen == 7) {
> +		cmd_addr |= (offs & 0x7f);
> +		len = 10;
> +	} else {
> +		cmd_addr |= (offs & 0x3f);
> +		len = 9;
> +	}
> +	mask = 1 << (len - 1);
> +
> +	mutex_lock(&gpio_93xx46_mutex);
> +
> +	gpio_set_value(pd->cs, !active);
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 1);
> +	ndelay(DELAY);
> +
> +	if (pd->prepare)
> +		pd->prepare(edev);
> +
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < len; i++, mask >>= 1)
> +		gpio_93xx46_tx_bit(edev, !!(cmd_addr & mask));
> +
> +	ndelay(DELAY);
> +
> +	for (i = 0; i < 8; i++) {
> +		gpio_93xx46_tx_bit(edev, !!(data & 0x80));
> +		data <<= 1;
> +	}
> +
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +	gpio_set_value(pd->cs, active);
> +	ndelay(DELAY);
> +
> +	while (!gpio_get_value(pd->dout)) {
> +		if (!to--) {
> +			dev_err(edev->dev, "write not ready timeout\n");
> +			break;
> +		}
> +		mdelay(1);
> +	}
> +
> +	gpio_set_value(pd->clk, 0);
> +	gpio_set_value(pd->din, 0);
> +	gpio_set_value(pd->cs, !active);
> +	ndelay(DELAY);
> +
> +	if (pd->finish)
> +		pd->finish(edev);
> +
> +	mutex_unlock(&gpio_93xx46_mutex);
> +}
> +
> +static ssize_t
> +gpio_93xx46_bin_read(struct file *filp, struct kobject *kobj,
> +		     struct bin_attribute *bin_attr,
> +		     char *buf, loff_t off, size_t count)
> +{
> +	struct device *dev = container_of(kobj, struct device, kobj);
> +	struct gpio_93xx46_dev *edev = dev_get_drvdata(dev);
> +
> +	if (unlikely(!count))
> +		return count;
> +	if (unlikely(off >= edev->bin.size))
> +		return 0;
> +	if ((off + count) > edev->bin.size)
> +		count = edev->bin.size - off;
> +
> +	gpio_93xx46_read(edev, buf, off, count);
> +	return count;
> +}
> +
> +static ssize_t
> +gpio_93xx46_bin_write(struct file *filp, struct kobject *kobj,
> +		      struct bin_attribute *bin_attr,
> +		      char *buf, loff_t off, size_t count)
> +{
> +	struct device *dev = container_of(kobj, struct device, kobj);
> +	struct gpio_93xx46_dev *edev = dev_get_drvdata(dev);
> +	int i;
> +
> +	if (unlikely(!count))
> +		return count;
> +	if (unlikely(off >= edev->bin.size))
> +		return 0;
> +	if ((off + count) > edev->bin.size)
> +		count = edev->bin.size - off;
> +
> +	gpio_93xx46_ewen(edev);
> +
> +	for (i = 0; i < count; i++)
> +		gpio_93xx46_write(edev, off + i, buf[i]);
> +
> +	gpio_93xx46_ewds(edev);
> +
> +	return count;
> +}
> +
> +ssize_t gpio_93xx46_store_erase(struct device *dev,
> +				struct device_attribute *attr,
> +				const char *buf, size_t count)
> +{
> +	struct gpio_93xx46_dev *edev = dev_get_drvdata(dev);
> +	int erase = 0;
> +
> +	sscanf(buf, "%d", &erase);
> +	if (erase) {
> +		gpio_93xx46_ewen(edev);
> +		gpio_93xx46_eral(edev);
> +		gpio_93xx46_ewds(edev);
> +	}
> +
> +	return count;
> +}
> +static DEVICE_ATTR(erase, S_IWUSR, NULL, gpio_93xx46_store_erase);
> +
> +static int __devinit gpio_93xx46_probe(struct platform_device *pdev)
> +{
> +	struct gpio_93xx46_platform_data *pd = pdev->dev.platform_data;
> +	struct gpio_93xx46_dev *edev = NULL;
> +	int err;
> +
> +	if (!pd)
> +		return -ENODEV;
> +
> +	edev = kzalloc(sizeof(*edev), GFP_KERNEL);
> +	if (!edev)
> +		return -ENOMEM;
> +
> +	edev->dev = &pdev->dev;
> +	edev->pdata = pd;
> +	if (pd->flags & EE_ADDR8)
> +		edev->addrlen = 7;
> +	else if (pd->flags & EE_ADDR16)
> +		edev->addrlen = 6;
> +	else {
> +		dev_err(&pdev->dev,
> +			"invalid address flags 0x%x\n", pd->flags);
> +		err = -EINVAL;
> +		goto fail;
> +	}
> +
> +	err = gpio_93xx46_request_gpios(edev);
> +	if (err)
> +		goto fail;
> +
> +	sysfs_bin_attr_init(&edev->bin);
> +	edev->bin.attr.name = "eeprom";
> +	edev->bin.attr.mode = S_IRUSR;
> +	edev->bin.read = gpio_93xx46_bin_read;
> +	edev->bin.size = 128;
> +	if (!(pd->flags & EE_READONLY)) {
> +		edev->bin.write = gpio_93xx46_bin_write;
> +		edev->bin.attr.mode |= S_IWUSR;
> +	}
> +
> +	err = sysfs_create_bin_file(&pdev->dev.kobj, &edev->bin);
> +	if (err)
> +		goto fail;
> +
> +	if (!(pd->flags & EE_READONLY)) {
> +		if (device_create_file(&pdev->dev, &dev_attr_erase))
> +			dev_err(&pdev->dev, "can't create erase interface\n");
> +	}
> +
> +	platform_set_drvdata(pdev, edev);
> +
> +	return 0;
> +fail:
> +	kfree(edev);
> +	return err;
> +}
> +
> +static int __devexit gpio_93xx46_remove(struct platform_device *pdev)
> +{
> +	struct gpio_93xx46_platform_data *pd = pdev->dev.platform_data;
> +	struct gpio_93xx46_dev *edev = platform_get_drvdata(pdev);
> +
> +	if (!(pd->flags & EE_READONLY))
> +		device_remove_file(&pdev->dev, &dev_attr_erase);
> +	sysfs_remove_bin_file(&pdev->dev.kobj, &edev->bin);
> +	gpio_free_array(edev->pins, ARRAY_SIZE(edev->pins));
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(edev);
> +	return 0;
> +}
> +
> +static struct platform_driver gpio_93xx46_driver = {
> +	.probe		= gpio_93xx46_probe,
> +	.remove		= __devexit_p(gpio_93xx46_remove),
> +	.driver		= {
> +		.name	= "gpio-93xx46",
> +		.owner	= THIS_MODULE,
> +	}
> +};
> +
> +static int __init gpio_93xx46_init(void)
> +{
> +	return platform_driver_register(&gpio_93xx46_driver);
> +}
> +module_init(gpio_93xx46_init);
> +
> +static void __exit gpio_93xx46_exit(void)
> +{
> +	platform_driver_unregister(&gpio_93xx46_driver);
> +}
> +module_exit(gpio_93xx46_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs over GPIO");
> +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
> +MODULE_ALIAS("platform:gpio-93xx46");
> diff --git a/include/linux/gpio-93xx46.h b/include/linux/gpio-93xx46.h
> new file mode 100644
> index 0000000..a565de6
> --- /dev/null
> +++ b/include/linux/gpio-93xx46.h
> @@ -0,0 +1,19 @@
> +/*
> + * Module: gpio-93xx46
> + * Interface description for 93xx46 EEPROMs connected over GPIO.
> + */
> +struct gpio_93xx46_platform_data {
> +	unsigned	clk;
> +	unsigned	cs;
> +	unsigned	din;
> +	unsigned	dout;
> +
> +	u8		flags;
> +#define EE_ADDR8	0x01		/*  8 bit addr. cfg */
> +#define EE_ADDR16	0x02		/* 16 bit addr. cfg */
> +#define EE_READONLY	0x08		/* forbid writing */
> +#define EE_CS_LOW	0x10		/* CS is active low */
> +
> +	void (*prepare)(void *);
> +	void (*finish)(void *);
> +};


  parent reply	other threads:[~2011-05-25  9:28 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-05-24 16:02 [PATCH 1/2] misc/eeprom: add driver for 93xx46 EEPROMs over GPIO Anatolij Gustschin
2011-05-24 16:02 ` [PATCH 2/2] misc/eeprom: add eeprom access driver for digsy_mtc board Anatolij Gustschin
2011-05-25  9:31 ` Jonathan Cameron [this message]
2011-05-25 13:35   ` [PATCH 1/2] misc/eeprom: add driver for 93xx46 EEPROMs over GPIO Anatolij Gustschin
2011-05-25 14:07     ` Jonathan Cameron
2011-06-06  8:00   ` Anatolij Gustschin

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=4DDCCC82.90604@cam.ac.uk \
    --to=jic23@cam.ac.uk \
    --cc=agust@denx.de \
    --cc=akpm@linux-foundation.org \
    --cc=dzu@denx.de \
    --cc=linux-kernel@vger.kernel.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 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.