All of lore.kernel.org
 help / color / mirror / Atom feed
From: Padmarao Begari <padmarao.begari@microchip.com>
To: <u-boot@lists.denx.de>, <bmeng.cn@gmail.com>,
	<rick@andestech.com>, <anup.patel@wdc.com>, <atish.patra@wdc.com>,
	<hs@denx.de>
Cc: <cyril.jean@microchip.com>, <lewis.hanly@microchip.com>,
	<conor.dooley@microchip.com>, <daire.mcnamara@microchip.com>,
	<ivan.griffin@microchip.com>,
	<valentina.fernandezalanis@microchip.com>,
	Padmarao Begari <padmarao.begari@microchip.com>
Subject: [PATCH v1 3/5] i2c: Add Microchip PolarFire SoC I2C driver
Date: Fri, 22 Oct 2021 14:26:46 +0530	[thread overview]
Message-ID: <20211022085648.134655-4-padmarao.begari@microchip.com> (raw)
In-Reply-To: <20211022085648.134655-1-padmarao.begari@microchip.com>

Add I2C driver code for the Microchip PolarFire SoC.
This driver supports I2C data transfer and probe for I2C
slave addresses.

Signed-off-by: Padmarao Begari <padmarao.begari@microchip.com>
---
 drivers/i2c/Kconfig         |   6 +
 drivers/i2c/Makefile        |   1 +
 drivers/i2c/i2c-microchip.c | 482 ++++++++++++++++++++++++++++++++++++
 3 files changed, 489 insertions(+)
 create mode 100644 drivers/i2c/i2c-microchip.c

diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 7c447a8aa0..5482a4a470 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -250,6 +250,12 @@ config SYS_I2C_MESON
 	  internal buffer holding up to 8 bytes for transfers and supports
 	  both 7-bit and 10-bit addresses.
 
+config SYS_I2C_MICROCHIP
+	bool "Microchip I2C driver"
+	help
+	  Add support for the Microchip I2C driver. This is operating on
+	  standard mode up to 100 kbits/s and fast mode up to 400 kbits/s.
+
 config SYS_I2C_MXC
 	bool "NXP MXC I2C driver"
 	help
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index fca6b157f8..9d41f379bb 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_SYS_I2C_IPROC) += iproc_i2c.o
 obj-$(CONFIG_SYS_I2C_KONA) += kona_i2c.o
 obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o
 obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o
+obj-$(CONFIG_SYS_I2C_MICROCHIP) += i2c-microchip.o
 obj-$(CONFIG_SYS_I2C_MV) += mv_i2c.o
 obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o
 obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o
diff --git a/drivers/i2c/i2c-microchip.c b/drivers/i2c/i2c-microchip.c
new file mode 100644
index 0000000000..12f65d0af7
--- /dev/null
+++ b/drivers/i2c/i2c-microchip.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Microchip I2C controller driver
+ *
+ * Copyright (C) 2021 Microchip Technology Inc.
+ * Padmarao Begari <padmarao.begari@microchip.com>
+ */
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <i2c.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+
+#define	MICROCHIP_I2C_TIMEOUT	(1000 * 60)
+
+#define MPFS_I2C_CTRL	(0x00)
+#define	CTRL_CR0		(0x00)
+#define	CTRL_CR1		(0x01)
+#define	CTRL_AA			BIT(2)
+#define	CTRL_SI			BIT(3)
+#define	CTRL_STO		BIT(4)
+#define	CTRL_STA		BIT(5)
+#define	CTRL_ENS1		BIT(6)
+#define	CTRL_CR2		(0x07)
+#define MPFS_I2C_STATUS							(0x04)
+#define	STATUS_BUS_ERROR						(0x00)
+#define	STATUS_M_START_SENT						(0x08)
+#define	STATUS_M_REPEATED_START_SENT			(0x10)
+#define	STATUS_M_SLAW_ACK						(0x18)
+#define	STATUS_M_SLAW_NACK						(0x20)
+#define	STATUS_M_TX_DATA_ACK					(0x28)
+#define	STATUS_M_TX_DATA_NACK					(0x30)
+#define	STATUS_M_ARB_LOST						(0x38)
+#define	STATUS_M_SLAR_ACK						(0x40)
+#define	STATUS_M_SLAR_NACK						(0x48)
+#define	STATUS_M_RX_DATA_ACKED					(0x50)
+#define	STATUS_M_RX_DATA_NACKED					(0x58)
+#define	STATUS_S_SLAW_ACKED						(0x60)
+#define	STATUS_S_ARB_LOST_SLAW_ACKED			(0x68)
+#define	STATUS_S_GENERAL_CALL_ACKED				(0x70)
+#define	STATUS_S_ARB_LOST_GENERAL_CALL_ACKED	(0x78)
+#define	STATUS_S_RX_DATA_ACKED					(0x80)
+#define	STATUS_S_RX_DATA_NACKED					(0x88)
+#define	STATUS_S_GENERAL_CALL_RX_DATA_ACKED		(0x90)
+#define	STATUS_S_GENERAL_CALL_RX_DATA_NACKED	(0x98)
+#define	STATUS_S_RX_STOP						(0xA0)
+#define	STATUS_S_SLAR_ACKED						(0xA8)
+#define	STATUS_S_ARB_LOST_SLAR_ACKED			(0xB0)
+#define	STATUS_S_TX_DATA_ACK					(0xb8)
+#define	STATUS_S_TX_DATA_NACK					(0xC0)
+#define	STATUS_LAST_DATA_ACK					(0xC8)
+#define	STATUS_M_SMB_MASTER_RESET				(0xD0)
+#define	STATUS_S_SCL_LOW_TIMEOUT				(0xD8)
+#define	STATUS_NO_STATE_INFO					(0xF8)
+#define MPFS_I2C_DATA			(0x08)
+#define MPFS_I2C_SLAVE0_ADDR	(0x0c)
+#define MPFS_I2C_SMBUS			(0x10)
+#define MPFS_I2C_FREQ			(0x14)
+#define MPFS_I2C_GLITCHREG		(0x18)
+#define MPFS_I2C_SLAVE1_ADDR	(0x1c)
+
+#define PCLK_DIV_256	((0 << CTRL_CR0) | (0 << CTRL_CR1) | (0 << CTRL_CR2))
+#define PCLK_DIV_224	((1 << CTRL_CR0) | (0 << CTRL_CR1) | (0 << CTRL_CR2))
+#define PCLK_DIV_192	((0 << CTRL_CR0) | (1 << CTRL_CR1) | (0 << CTRL_CR2))
+#define PCLK_DIV_160	((1 << CTRL_CR0) | (1 << CTRL_CR1) | (0 << CTRL_CR2))
+#define PCLK_DIV_960	((0 << CTRL_CR0) | (0 << CTRL_CR1) | (1 << CTRL_CR2))
+#define PCLK_DIV_120	((1 << CTRL_CR0) | (0 << CTRL_CR1) | (1 << CTRL_CR2))
+#define PCLK_DIV_60		((0 << CTRL_CR0) | (1 << CTRL_CR1) | (1 << CTRL_CR2))
+#define BCLK_DIV_8		((1 << CTRL_CR0) | (1 << CTRL_CR1) | (1 << CTRL_CR2))
+#define CLK_MASK		((1 << CTRL_CR0) | (1 << CTRL_CR1) | (1 << CTRL_CR2))
+
+/*
+ * mpfs_i2c_bus - I2C bus context
+ * @base: pointer to register struct
+ * @msg_len: number of bytes transferred in msg
+ * @msg_err: error code for completed message
+ * @i2c_clk: clock reference for i2c input clock
+ * @clk_rate: current i2c bus clock rate
+ * @buf: ptr to msg buffer for easier use.
+ * @addr: i2c address.
+ * @isr_status: cached copy of local ISR status.
+ */
+struct mpfs_i2c_bus {
+	void __iomem *base;
+	size_t msg_len;
+	int msg_err;
+	struct clk i2c_clk;
+	u32 clk_rate;
+	u8 *buf;
+	u8 addr;
+	u32 isr_status;
+};
+
+static inline u8 i2c_8bit_addr_from_msg(const struct i2c_msg *msg)
+{
+	return (msg->addr << 1) | (msg->flags & I2C_M_RD ? 1 : 0);
+}
+
+static void mpfs_i2c_int_clear(struct mpfs_i2c_bus *bus)
+{
+	u8 ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl &= ~CTRL_SI;
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+}
+
+static void mpfs_i2c_core_disable(struct mpfs_i2c_bus *bus)
+{
+	u8 ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl &= ~CTRL_ENS1;
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+}
+
+static void mpfs_i2c_core_enable(struct mpfs_i2c_bus *bus)
+{
+	u8 ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl |= CTRL_ENS1;
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+}
+
+static void mpfs_i2c_reset(struct mpfs_i2c_bus *bus)
+{
+	mpfs_i2c_core_disable(bus);
+	mpfs_i2c_core_enable(bus);
+}
+
+static inline void mpfs_i2c_stop(struct mpfs_i2c_bus *bus)
+{
+	u8 ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl |= CTRL_STO;
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+}
+
+static inline int mpfs_generate_divisor(u32 rate, u8 *code)
+{
+	int ret = 0;
+
+	if (rate >= 960)
+		*code = PCLK_DIV_960;
+	else if (rate >= 256)
+		*code = PCLK_DIV_256;
+	else if (rate >= 224)
+		*code = PCLK_DIV_224;
+	else if (rate >= 192)
+		*code = PCLK_DIV_192;
+	else if (rate >= 160)
+		*code = PCLK_DIV_160;
+	else if (rate >= 120)
+		*code = PCLK_DIV_120;
+	else if (rate >= 60)
+		*code = PCLK_DIV_60;
+	else if (rate >= 8)
+		*code = BCLK_DIV_8;
+	else
+		ret = -EINVAL;
+
+	return ret;
+}
+
+static int mpfs_i2c_init(struct mpfs_i2c_bus *bus, struct udevice *dev)
+{
+	u32 clk_rate, divisor;
+	u8 clkval, ctrl;
+	int ret;
+
+	ret = clk_get_by_index(dev, 0, &bus->i2c_clk);
+	if (ret)
+		return -EINVAL;
+
+	ret = clk_enable(&bus->i2c_clk);
+	if (ret)
+		return ret;
+
+	clk_rate = clk_get_rate(&bus->i2c_clk);
+	if (!clk_rate)
+		return -EINVAL;
+
+	clk_free(&bus->i2c_clk);
+
+	divisor = clk_rate / bus->clk_rate;
+
+	ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl &= ~CLK_MASK;
+
+	ret = mpfs_generate_divisor(divisor, &clkval);
+	if (ret)
+		return -EINVAL;
+
+	ctrl |= clkval;
+
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+
+	ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	/* Reset I2C core */
+	mpfs_i2c_reset(bus);
+
+	return 0;
+}
+
+static void mpfs_i2c_transfer(struct mpfs_i2c_bus *bus, u32 data)
+{
+	if (bus->msg_len > 0)
+		writel(data, bus->base + MPFS_I2C_DATA);
+}
+
+static void mpfs_i2c_empty_rx(struct mpfs_i2c_bus *bus)
+{
+	u8 ctrl;
+	u8 data_read;
+
+	if (bus->msg_len > 0) {
+		data_read = readl(bus->base + MPFS_I2C_DATA);
+		*bus->buf++ = data_read;
+		bus->msg_len--;
+	}
+
+	if (bus->msg_len == 0) {
+		ctrl = readl(bus->base + MPFS_I2C_CTRL);
+		ctrl &= ~CTRL_AA;
+		writel(ctrl, bus->base + MPFS_I2C_CTRL);
+	}
+}
+
+static int mpfs_i2c_fill_tx(struct mpfs_i2c_bus *bus)
+{
+	mpfs_i2c_transfer(bus, *bus->buf++);
+	bus->msg_len--;
+
+	return 0;
+}
+
+static int mpfs_i2c_service_handler(struct mpfs_i2c_bus *bus)
+{
+	bool finish = false;
+	u32 status;
+	u8 ctrl;
+
+	status = bus->isr_status;
+
+	switch (status)	{
+	case STATUS_M_START_SENT:
+	case STATUS_M_REPEATED_START_SENT:
+		ctrl = readl(bus->base + MPFS_I2C_CTRL);
+		ctrl &= ~CTRL_STA;
+		writel(bus->addr, bus->base + MPFS_I2C_DATA);
+		writel(ctrl, bus->base + MPFS_I2C_CTRL);
+		break;
+	case STATUS_M_SLAW_ACK:
+	case STATUS_M_TX_DATA_ACK:
+		if (bus->msg_len > 0) {
+			mpfs_i2c_fill_tx(bus);
+		} else {
+			/* On the last byte to be transmitted, send STOP */
+			mpfs_i2c_stop(bus);
+			finish = true;
+		}
+		break;
+	case STATUS_M_SLAR_ACK:
+		ctrl = readl(bus->base + MPFS_I2C_CTRL);
+		ctrl |= CTRL_AA;
+		writel(ctrl, bus->base + MPFS_I2C_CTRL);
+		if (bus->msg_len == 0) {
+			/* On the last byte to be transmitted, send STOP */
+			mpfs_i2c_stop(bus);
+			finish = true;
+		}
+		break;
+	case STATUS_M_RX_DATA_ACKED:
+		mpfs_i2c_empty_rx(bus);
+		if (bus->msg_len == 0) {
+			/* On the last byte to be transmitted, send STOP */
+			mpfs_i2c_stop(bus);
+			finish = true;
+		}
+		break;
+	case STATUS_M_TX_DATA_NACK:
+	case STATUS_M_RX_DATA_NACKED:
+	case STATUS_M_SLAR_NACK:
+	case STATUS_M_SLAW_NACK:
+		bus->msg_err = -ENXIO;
+		mpfs_i2c_stop(bus);
+		finish = true;
+		break;
+
+	case STATUS_M_ARB_LOST:
+		/* Handle Lost Arbitration */
+		bus->msg_err = -EAGAIN;
+		finish = true;
+		break;
+	default:
+		break;
+	}
+
+	if (finish) {
+		ctrl = readl(bus->base + MPFS_I2C_CTRL);
+		ctrl &= ~CTRL_AA;
+		writel(ctrl, bus->base + MPFS_I2C_CTRL);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int mpfs_i2c_service(struct mpfs_i2c_bus *bus)
+{
+	int ret = 0;
+	int si_bit;
+
+	si_bit = readl(bus->base + MPFS_I2C_CTRL);
+	if (si_bit & CTRL_SI) {
+		bus->isr_status = readl(bus->base + MPFS_I2C_STATUS);
+		ret = mpfs_i2c_service_handler(bus);
+	}
+	/* Clear the si flag */
+	mpfs_i2c_int_clear(bus);
+	si_bit = readl(bus->base + MPFS_I2C_CTRL);
+
+	return ret;
+}
+
+static int mpfs_i2c_check_service_change(struct mpfs_i2c_bus *bus)
+{
+	u8 ctrl;
+	u32 count = 0;
+
+	while (1) {
+		ctrl = readl(bus->base + MPFS_I2C_CTRL);
+		if (ctrl & CTRL_SI)
+			break;
+		udelay(1);
+		count += 1;
+		if (count == MICROCHIP_I2C_TIMEOUT)
+			return -ETIMEDOUT;
+	}
+	return 0;
+}
+
+static int mpfs_i2c_poll_device(struct mpfs_i2c_bus *bus)
+{
+	int ret;
+
+	while (1) {
+		ret = mpfs_i2c_check_service_change(bus);
+		if (ret)
+			return ret;
+
+		ret = mpfs_i2c_service(bus);
+		if (!ret)
+			/* all messages have been transferred */
+			return ret;
+	}
+}
+
+static int mpfs_i2c_xfer_msg(struct mpfs_i2c_bus *bus, struct i2c_msg *msg)
+{
+	u8 ctrl;
+	int ret;
+
+	if (!msg->len || !msg->buf)
+		return -EINVAL;
+
+	bus->addr = i2c_8bit_addr_from_msg(msg);
+	bus->msg_len = msg->len;
+	bus->buf = msg->buf;
+	bus->msg_err = 0;
+
+	mpfs_i2c_core_enable(bus);
+
+	ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl |= CTRL_STA;
+
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+
+	ret = mpfs_i2c_poll_device(bus);
+	if (ret)
+		return ret;
+
+	return bus->msg_err;
+}
+
+static int mpfs_i2c_xfer(struct udevice *dev, struct i2c_msg *msgs, int num_msgs)
+{
+	struct mpfs_i2c_bus *bus = dev_get_priv(dev);
+	int idx, ret;
+
+	if (!msgs || !num_msgs)
+		return -EINVAL;
+
+	for (idx = 0; idx < num_msgs; idx++) {
+		ret = mpfs_i2c_xfer_msg(bus, msgs++);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+static int mpfs_i2c_probe_chip(struct udevice *dev, uint addr, uint flags)
+{
+	struct mpfs_i2c_bus *bus = dev_get_priv(dev);
+	int ret;
+	u8 ctrl, reg = 0;
+
+	/*
+	 * Send the chip address and verify that the
+	 * address was <ACK>ed.
+	 */
+	bus->addr = addr << 1 | I2C_M_RD;
+	bus->buf = &reg;
+	bus->msg_len = 0;
+	bus->msg_err = 0;
+
+	mpfs_i2c_core_enable(bus);
+
+	ctrl = readl(bus->base + MPFS_I2C_CTRL);
+
+	ctrl |= CTRL_STA;
+
+	writel(ctrl, bus->base + MPFS_I2C_CTRL);
+
+	ret = mpfs_i2c_poll_device(bus);
+	if (ret)
+		return ret;
+
+	return bus->msg_err;
+}
+
+static int mpfs_i2c_probe(struct udevice *dev)
+{
+	int ret;
+	u32 val;
+	struct mpfs_i2c_bus *bus = dev_get_priv(dev);
+
+	bus->base = dev_read_addr_ptr(dev);
+	if (!bus->base)
+		return -EINVAL;
+
+	val = dev_read_u32(dev, "clock-frequency", &bus->clk_rate);
+	if (val) {
+		printf("Default to 100kHz\n");
+		/* default clock rate */
+		bus->clk_rate = 100000;
+	}
+
+	if (bus->clk_rate > 400000 || bus->clk_rate <= 0) {
+		printf("Invalid clock-frequency %d\n", bus->clk_rate);
+		return -EINVAL;
+	}
+
+	ret = mpfs_i2c_init(bus, dev);
+
+	return ret;
+}
+
+static const struct dm_i2c_ops mpfs_i2c_ops = {
+	.xfer = mpfs_i2c_xfer,
+	.probe_chip = mpfs_i2c_probe_chip,
+};
+
+static const struct udevice_id mpfs_i2c_ids[] = {
+	{.compatible = "microchip,mpfs-i2c"},
+	{}
+};
+
+U_BOOT_DRIVER(mpfs_i2c) = {
+	.name = "mpfs_i2c",
+	.id = UCLASS_I2C,
+	.of_match = mpfs_i2c_ids,
+	.ops = &mpfs_i2c_ops,
+	.probe = mpfs_i2c_probe,
+	.priv_auto = sizeof(struct mpfs_i2c_bus),
+};
-- 
2.25.1


  parent reply	other threads:[~2021-10-22  8:59 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-22  8:56 [PATCH v1 0/5] Update Microchip PolarFire SoC support Padmarao Begari
2021-10-22  8:56 ` [PATCH v1 1/5] riscv: dts: Split Microchip device tree Padmarao Begari
2021-11-01  8:34   ` Leo Liang
2021-11-01  8:41   ` Bin Meng
2021-11-02  9:52     ` Padmarao Begari
2021-10-22  8:56 ` [PATCH v1 2/5] riscv: Update Microchip MPFS Icicle Kit support Padmarao Begari
2021-11-01  8:36   ` Leo Liang
2021-11-01  8:43   ` Bin Meng
2021-11-02 10:38     ` Padmarao Begari
2021-10-22  8:56 ` Padmarao Begari [this message]
2021-11-01  8:53   ` [PATCH v1 3/5] i2c: Add Microchip PolarFire SoC I2C driver Leo Liang
2021-10-22  8:56 ` [PATCH v1 4/5] net: macb: Compatible as per device tree Padmarao Begari
2021-11-01  8:30   ` Leo Liang
2021-11-01  8:44   ` Bin Meng
2021-11-02 11:03     ` Padmarao Begari
2021-11-02 12:45       ` Bin Meng
2021-11-03 11:47         ` Padmarao Begari
2021-11-03 13:10           ` Padmarao Begari
2021-11-11  8:07             ` Bin Meng
2021-11-11  9:06               ` Padmarao Begari
2021-11-11 12:54                 ` Michael Walle
2021-11-11 13:17                   ` Ivan.Griffin
2021-11-12  1:28                     ` Bin Meng
2021-11-12  9:36                       ` Padmarao Begari
2021-11-11 13:20                   ` Conor.Dooley
2021-11-11  9:41   ` macb clock handling (Was: [PATCH v1 4/5] net: macb: Compatible as per device tree) Heiko Stübner
2021-11-25 19:32     ` Heiko Stübner
2021-10-22  8:56 ` [PATCH v1 5/5] doc: board: Update Microchip MPFS Icicle Kit doc Padmarao Begari
2021-11-01  8:33   ` Leo Liang
2021-11-01  8:46   ` Bin Meng
2021-11-02 11:04     ` Padmarao Begari

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=20211022085648.134655-4-padmarao.begari@microchip.com \
    --to=padmarao.begari@microchip.com \
    --cc=anup.patel@wdc.com \
    --cc=atish.patra@wdc.com \
    --cc=bmeng.cn@gmail.com \
    --cc=conor.dooley@microchip.com \
    --cc=cyril.jean@microchip.com \
    --cc=daire.mcnamara@microchip.com \
    --cc=hs@denx.de \
    --cc=ivan.griffin@microchip.com \
    --cc=lewis.hanly@microchip.com \
    --cc=rick@andestech.com \
    --cc=u-boot@lists.denx.de \
    --cc=valentina.fernandezalanis@microchip.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.