All of lore.kernel.org
 help / color / mirror / Atom feed
From: Douglas Anderson <dianders@chromium.org>
To: Andrzej Hajda <a.hajda@samsung.com>,
	Neil Armstrong <narmstrong@baylibre.com>
Cc: robdclark@chromium.org, linux-arm-msm@vger.kernel.org,
	bjorn.andersson@linaro.org, seanpaul@chromium.org,
	Jeffrey Hugo <jeffrey.l.hugo@gmail.com>,
	Daniel Vetter <daniel@ffwll.ch>,
	Douglas Anderson <dianders@chromium.org>,
	Jonas Karlman <jonas@kwiboo.se>,
	linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org,
	David Airlie <airlied@linux.ie>,
	Jernej Skrabec <jernej.skrabec@siol.net>,
	Laurent Pinchart <Laurent.pinchart@ideasonboard.com>
Subject: [PATCH v3 9/9] drm/bridge: ti-sn65dsi86: Avoid invalid rates
Date: Wed, 18 Dec 2019 14:35:30 -0800	[thread overview]
Message-ID: <20191218143416.v3.9.Ib59207b66db377380d13748752d6fce5596462c5@changeid> (raw)
In-Reply-To: <20191218223530.253106-1-dianders@chromium.org>

Based on work by Bjorn Andersson <bjorn.andersson@linaro.org>,
Jeffrey Hugo <jeffrey.l.hugo@gmail.com>, and
Rob Clark <robdclark@chromium.org>.

Let's read the SUPPORTED_LINK_RATES and/or MAX_LINK_RATE (depending on
the eDP version of the sink) to figure out what eDP rates are
supported and pick the ideal one.

NOTE: I have only personally tested this code on eDP panels that are
1.3 or older.  Code reading SUPPORTED_LINK_RATES for DP 1.4+ was
tested by hacking the code to pretend that a table was there.

Signed-off-by: Douglas Anderson <dianders@chromium.org>
---

Changes in v3:
- Init rate_valid table, don't rely on stack being 0 (oops).
- Rename rate_times_200khz to rate_per_200khz.
- Loop over the ti_sn_bridge_dp_rate_lut table, making code smaller.
- Use 'true' instead of 1 for bools.
- Added note to commit message noting DP 1.4+ isn't well tested.

Changes in v2:
- Patch ("Avoid invalid rates") replaces ("Skip non-standard DP rates")

 drivers/gpu/drm/bridge/ti-sn65dsi86.c | 100 +++++++++++++++++++-------
 1 file changed, 75 insertions(+), 25 deletions(-)

diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index e1b817ccd9c7..a57c6108cb1f 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -475,39 +475,85 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata)
 	return i;
 }
 
-static int ti_sn_bridge_get_max_dp_rate_idx(struct ti_sn_bridge *pdata)
+static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata,
+					  bool rate_valid[])
 {
-	u8 data;
+	unsigned int rate_per_200khz;
+	unsigned int rate_mhz;
+	u8 dpcd_val;
 	int ret;
+	int i, j;
+
+	ret = drm_dp_dpcd_readb(&pdata->aux, DP_EDP_DPCD_REV, &dpcd_val);
+	if (ret != 1) {
+		DRM_DEV_ERROR(pdata->dev,
+			      "Can't read eDP rev (%d), assuming 1.1\n", ret);
+		dpcd_val = DP_EDP_11;
+	}
+
+	if (dpcd_val >= DP_EDP_14) {
+		/* eDP 1.4 devices must provide a custom table */
+		__le16 sink_rates[DP_MAX_SUPPORTED_RATES];
+
+		ret = drm_dp_dpcd_read(&pdata->aux, DP_SUPPORTED_LINK_RATES,
+				       sink_rates, sizeof(sink_rates));
+
+		if (ret != sizeof(sink_rates)) {
+			DRM_DEV_ERROR(pdata->dev,
+				"Can't read supported rate table (%d)\n", ret);
+
+			/* By zeroing we'll fall back to DP_MAX_LINK_RATE. */
+			memset(sink_rates, 0, sizeof(sink_rates));
+		}
+
+		for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
+			rate_per_200khz = le16_to_cpu(sink_rates[i]);
+
+			if (!rate_per_200khz)
+				break;
+
+			rate_mhz = rate_per_200khz * 200 / 1000;
+			for (j = 0;
+			     j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
+			     j++) {
+				if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
+					rate_valid[j] = true;
+			}
+		}
+
+		for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
+			if (rate_valid[i])
+				return;
+		}
+		DRM_DEV_ERROR(pdata->dev,
+			      "No matching eDP rates in table; falling back\n");
+	}
 
-	ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &data);
+	/* On older versions best we can do is use DP_MAX_LINK_RATE */
+	ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &dpcd_val);
 	if (ret != 1) {
 		DRM_DEV_ERROR(pdata->dev,
 			      "Can't read max rate (%d); assuming 5.4 GHz\n",
 			      ret);
-		return ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1;
+		dpcd_val = DP_LINK_BW_5_4;
 	}
 
-	/*
-	 * Return an index into ti_sn_bridge_dp_rate_lut.  Just hardcode
-	 * these indicies since it's not like the register spec is ever going
-	 * to change and a loop would just be more complicated.  Apparently
-	 * the DP sink can only return these few rates as supported even
-	 * though the bridge allows some rates in between.
-	 */
-	switch (data) {
-	case DP_LINK_BW_1_62:
-		return 1;
-	case DP_LINK_BW_2_7:
-		return 4;
+	switch (dpcd_val) {
+	default:
+		DRM_DEV_ERROR(pdata->dev,
+			      "Unexpected max rate (%#x); assuming 5.4 GHz\n",
+			      (int)dpcd_val);
+		/* fall through */
 	case DP_LINK_BW_5_4:
-		return 7;
+		rate_valid[7] = 1;
+		/* fall through */
+	case DP_LINK_BW_2_7:
+		rate_valid[4] = 1;
+		/* fall through */
+	case DP_LINK_BW_1_62:
+		rate_valid[1] = 1;
+		break;
 	}
-
-	DRM_DEV_ERROR(pdata->dev,
-		      "Unexpected max data rate (%#x); assuming 5.4 GHz\n",
-		      (int)data);
-	return ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1;
 }
 
 static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata)
@@ -609,9 +655,9 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx,
 static void ti_sn_bridge_enable(struct drm_bridge *bridge)
 {
 	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { };
 	const char *last_err_str = "No supported DP rate";
 	int dp_rate_idx;
-	int max_dp_rate_idx;
 	unsigned int val;
 	int ret = -EINVAL;
 
@@ -655,11 +701,15 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
 	regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
 			   val);
 
+	ti_sn_bridge_read_valid_rates(pdata, rate_valid);
+
 	/* Train until we run out of rates */
-	max_dp_rate_idx = ti_sn_bridge_get_max_dp_rate_idx(pdata);
 	for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata);
-	     dp_rate_idx <= max_dp_rate_idx;
+	     dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
 	     dp_rate_idx++) {
+		if (!rate_valid[dp_rate_idx])
+			continue;
+
 		ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
 		if (!ret)
 			break;
-- 
2.24.1.735.g03f4e72817-goog


WARNING: multiple messages have this Message-ID (diff)
From: Douglas Anderson <dianders@chromium.org>
To: Andrzej Hajda <a.hajda@samsung.com>,
	Neil Armstrong <narmstrong@baylibre.com>
Cc: robdclark@chromium.org, Jernej Skrabec <jernej.skrabec@siol.net>,
	Jeffrey Hugo <jeffrey.l.hugo@gmail.com>,
	David Airlie <airlied@linux.ie>,
	linux-arm-msm@vger.kernel.org, Jonas Karlman <jonas@kwiboo.se>,
	Douglas Anderson <dianders@chromium.org>,
	dri-devel@lists.freedesktop.org, bjorn.andersson@linaro.org,
	seanpaul@chromium.org,
	Laurent Pinchart <Laurent.pinchart@ideasonboard.com>,
	linux-kernel@vger.kernel.org
Subject: [PATCH v3 9/9] drm/bridge: ti-sn65dsi86: Avoid invalid rates
Date: Wed, 18 Dec 2019 14:35:30 -0800	[thread overview]
Message-ID: <20191218143416.v3.9.Ib59207b66db377380d13748752d6fce5596462c5@changeid> (raw)
In-Reply-To: <20191218223530.253106-1-dianders@chromium.org>

Based on work by Bjorn Andersson <bjorn.andersson@linaro.org>,
Jeffrey Hugo <jeffrey.l.hugo@gmail.com>, and
Rob Clark <robdclark@chromium.org>.

Let's read the SUPPORTED_LINK_RATES and/or MAX_LINK_RATE (depending on
the eDP version of the sink) to figure out what eDP rates are
supported and pick the ideal one.

NOTE: I have only personally tested this code on eDP panels that are
1.3 or older.  Code reading SUPPORTED_LINK_RATES for DP 1.4+ was
tested by hacking the code to pretend that a table was there.

Signed-off-by: Douglas Anderson <dianders@chromium.org>
---

Changes in v3:
- Init rate_valid table, don't rely on stack being 0 (oops).
- Rename rate_times_200khz to rate_per_200khz.
- Loop over the ti_sn_bridge_dp_rate_lut table, making code smaller.
- Use 'true' instead of 1 for bools.
- Added note to commit message noting DP 1.4+ isn't well tested.

Changes in v2:
- Patch ("Avoid invalid rates") replaces ("Skip non-standard DP rates")

 drivers/gpu/drm/bridge/ti-sn65dsi86.c | 100 +++++++++++++++++++-------
 1 file changed, 75 insertions(+), 25 deletions(-)

diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index e1b817ccd9c7..a57c6108cb1f 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -475,39 +475,85 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata)
 	return i;
 }
 
-static int ti_sn_bridge_get_max_dp_rate_idx(struct ti_sn_bridge *pdata)
+static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata,
+					  bool rate_valid[])
 {
-	u8 data;
+	unsigned int rate_per_200khz;
+	unsigned int rate_mhz;
+	u8 dpcd_val;
 	int ret;
+	int i, j;
+
+	ret = drm_dp_dpcd_readb(&pdata->aux, DP_EDP_DPCD_REV, &dpcd_val);
+	if (ret != 1) {
+		DRM_DEV_ERROR(pdata->dev,
+			      "Can't read eDP rev (%d), assuming 1.1\n", ret);
+		dpcd_val = DP_EDP_11;
+	}
+
+	if (dpcd_val >= DP_EDP_14) {
+		/* eDP 1.4 devices must provide a custom table */
+		__le16 sink_rates[DP_MAX_SUPPORTED_RATES];
+
+		ret = drm_dp_dpcd_read(&pdata->aux, DP_SUPPORTED_LINK_RATES,
+				       sink_rates, sizeof(sink_rates));
+
+		if (ret != sizeof(sink_rates)) {
+			DRM_DEV_ERROR(pdata->dev,
+				"Can't read supported rate table (%d)\n", ret);
+
+			/* By zeroing we'll fall back to DP_MAX_LINK_RATE. */
+			memset(sink_rates, 0, sizeof(sink_rates));
+		}
+
+		for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
+			rate_per_200khz = le16_to_cpu(sink_rates[i]);
+
+			if (!rate_per_200khz)
+				break;
+
+			rate_mhz = rate_per_200khz * 200 / 1000;
+			for (j = 0;
+			     j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
+			     j++) {
+				if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
+					rate_valid[j] = true;
+			}
+		}
+
+		for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
+			if (rate_valid[i])
+				return;
+		}
+		DRM_DEV_ERROR(pdata->dev,
+			      "No matching eDP rates in table; falling back\n");
+	}
 
-	ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &data);
+	/* On older versions best we can do is use DP_MAX_LINK_RATE */
+	ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &dpcd_val);
 	if (ret != 1) {
 		DRM_DEV_ERROR(pdata->dev,
 			      "Can't read max rate (%d); assuming 5.4 GHz\n",
 			      ret);
-		return ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1;
+		dpcd_val = DP_LINK_BW_5_4;
 	}
 
-	/*
-	 * Return an index into ti_sn_bridge_dp_rate_lut.  Just hardcode
-	 * these indicies since it's not like the register spec is ever going
-	 * to change and a loop would just be more complicated.  Apparently
-	 * the DP sink can only return these few rates as supported even
-	 * though the bridge allows some rates in between.
-	 */
-	switch (data) {
-	case DP_LINK_BW_1_62:
-		return 1;
-	case DP_LINK_BW_2_7:
-		return 4;
+	switch (dpcd_val) {
+	default:
+		DRM_DEV_ERROR(pdata->dev,
+			      "Unexpected max rate (%#x); assuming 5.4 GHz\n",
+			      (int)dpcd_val);
+		/* fall through */
 	case DP_LINK_BW_5_4:
-		return 7;
+		rate_valid[7] = 1;
+		/* fall through */
+	case DP_LINK_BW_2_7:
+		rate_valid[4] = 1;
+		/* fall through */
+	case DP_LINK_BW_1_62:
+		rate_valid[1] = 1;
+		break;
 	}
-
-	DRM_DEV_ERROR(pdata->dev,
-		      "Unexpected max data rate (%#x); assuming 5.4 GHz\n",
-		      (int)data);
-	return ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1;
 }
 
 static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata)
@@ -609,9 +655,9 @@ static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx,
 static void ti_sn_bridge_enable(struct drm_bridge *bridge)
 {
 	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { };
 	const char *last_err_str = "No supported DP rate";
 	int dp_rate_idx;
-	int max_dp_rate_idx;
 	unsigned int val;
 	int ret = -EINVAL;
 
@@ -655,11 +701,15 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
 	regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
 			   val);
 
+	ti_sn_bridge_read_valid_rates(pdata, rate_valid);
+
 	/* Train until we run out of rates */
-	max_dp_rate_idx = ti_sn_bridge_get_max_dp_rate_idx(pdata);
 	for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata);
-	     dp_rate_idx <= max_dp_rate_idx;
+	     dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
 	     dp_rate_idx++) {
+		if (!rate_valid[dp_rate_idx])
+			continue;
+
 		ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
 		if (!ret)
 			break;
-- 
2.24.1.735.g03f4e72817-goog

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

  parent reply	other threads:[~2019-12-18 22:36 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-12-18 22:35 [PATCH v3 0/9] drm/bridge: ti-sn65dsi86: Improve support for AUO B116XAK01 + other DP Douglas Anderson
2019-12-18 22:35 ` Douglas Anderson
2019-12-18 22:35 ` [PATCH v3 1/9] drm/bridge: ti-sn65dsi86: Split the setting of the dp and dsi rates Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:31   ` Bjorn Andersson
2020-02-03 23:31     ` Bjorn Andersson
2019-12-18 22:35 ` [PATCH v3 2/9] drm/bridge: ti-sn65dsi86: zero is never greater than an unsigned int Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:32   ` Bjorn Andersson
2020-02-03 23:32     ` Bjorn Andersson
2019-12-18 22:35 ` [PATCH v3 3/9] drm/bridge: ti-sn65dsi86: Don't use MIPI variables for DP link Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:33   ` Bjorn Andersson
2020-02-03 23:33     ` Bjorn Andersson
2019-12-18 22:35 ` [PATCH v3 4/9] drm/bridge: ti-sn65dsi86: Config number of DP lanes Mo' Betta Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:34   ` Bjorn Andersson
2020-02-03 23:34     ` Bjorn Andersson
2019-12-18 22:35 ` [PATCH v3 5/9] drm/bridge: ti-sn65dsi86: Read num lanes from the DP sink Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:35   ` Bjorn Andersson
2020-02-03 23:35     ` Bjorn Andersson
2019-12-18 22:35 ` [PATCH v3 6/9] drm/bridge: ti-sn65dsi86: Use 18-bit DP if we can Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:37   ` Bjorn Andersson
2020-02-03 23:37     ` Bjorn Andersson
2020-02-04  0:21     ` Doug Anderson
2020-02-04  0:21       ` Doug Anderson
2020-02-12 23:04       ` Doug Anderson
2020-02-12 23:04         ` Doug Anderson
2020-02-13  9:17         ` Neil Armstrong
2020-02-13  9:17           ` Neil Armstrong
     [not found]   ` <20200710011935.GA7056@gentoo.org>
2020-07-10  1:38     ` Doug Anderson
2020-07-10  1:38       ` Doug Anderson
2020-07-10  2:14       ` Doug Anderson
2020-07-10  2:14         ` Doug Anderson
2020-07-10  3:12         ` Steev Klimaszewski
2020-07-10  3:12           ` Steev Klimaszewski
2020-07-10  3:17           ` Steev Klimaszewski
2020-07-10  3:17             ` Steev Klimaszewski
2020-07-10  3:43             ` Steev Klimaszewski
2020-07-10  3:43               ` Steev Klimaszewski
2020-07-10  4:12               ` Doug Anderson
2020-07-10  4:12                 ` Doug Anderson
2020-07-10  6:15                 ` Steev Klimaszewski
2020-07-10  6:15                   ` Steev Klimaszewski
2020-07-10 14:16                   ` Rob Clark
2020-07-10 14:16                     ` Rob Clark
2020-07-10 14:47                   ` Doug Anderson
2020-07-10 14:47                     ` Doug Anderson
2020-07-10 17:10                     ` Steev Klimaszewski
2020-07-10 17:10                       ` Steev Klimaszewski
2020-07-14 15:31                       ` Doug Anderson
2020-07-14 15:31                         ` Doug Anderson
2020-09-02 14:37                         ` Doug Anderson
2020-09-02 14:37                           ` Doug Anderson
2019-12-18 22:35 ` [PATCH v3 7/9] drm/bridge: ti-sn65dsi86: Group DP link training bits in a function Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:39   ` Bjorn Andersson
2020-02-03 23:39     ` Bjorn Andersson
2019-12-18 22:35 ` [PATCH v3 8/9] drm/bridge: ti-sn65dsi86: Train at faster rates if slower ones fail Douglas Anderson
2019-12-18 22:35   ` Douglas Anderson
2020-02-03 23:41   ` Bjorn Andersson
2020-02-03 23:41     ` Bjorn Andersson
2019-12-18 22:35 ` Douglas Anderson [this message]
2019-12-18 22:35   ` [PATCH v3 9/9] drm/bridge: ti-sn65dsi86: Avoid invalid rates Douglas Anderson
2020-02-03 23:43   ` Bjorn Andersson
2020-02-03 23:43     ` Bjorn Andersson
2020-01-06 22:47 ` [PATCH v3 0/9] drm/bridge: ti-sn65dsi86: Improve support for AUO B116XAK01 + other DP Doug Anderson
2020-01-06 22:47   ` Doug Anderson
2020-02-03 23:45 ` Bjorn Andersson
2020-02-03 23:45   ` Bjorn Andersson
2020-02-13  9:51 ` Neil Armstrong
2020-02-13  9:51   ` Neil Armstrong

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=20191218143416.v3.9.Ib59207b66db377380d13748752d6fce5596462c5@changeid \
    --to=dianders@chromium.org \
    --cc=Laurent.pinchart@ideasonboard.com \
    --cc=a.hajda@samsung.com \
    --cc=airlied@linux.ie \
    --cc=bjorn.andersson@linaro.org \
    --cc=daniel@ffwll.ch \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jeffrey.l.hugo@gmail.com \
    --cc=jernej.skrabec@siol.net \
    --cc=jonas@kwiboo.se \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=narmstrong@baylibre.com \
    --cc=robdclark@chromium.org \
    --cc=seanpaul@chromium.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.