Devicetree
 help / color / mirror / Atom feed
From: Kaustabh Chakraborty <kauschluss@disroot.org>
To: "Lee Jones" <lee@kernel.org>, "Pavel Machek" <pavel@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	"MyungJoo Ham" <myungjoo.ham@samsung.com>,
	"Chanwoo Choi" <cw00.choi@samsung.com>,
	"Sebastian Reichel" <sre@kernel.org>,
	"Krzysztof Kozlowski" <krzk@kernel.org>,
	"André Draszik" <andre.draszik@linaro.org>,
	"Alexandre Belloni" <alexandre.belloni@bootlin.com>,
	"Jonathan Corbet" <corbet@lwn.net>,
	"Shuah Khan" <skhan@linuxfoundation.org>,
	"Nam Tran" <trannamatk@gmail.com>,
	"Łukasz Lebiedziński" <kernel@lvkasz.us>
Cc: linux-leds@vger.kernel.org, devicetree@vger.kernel.org,
	 linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org,
	 linux-samsung-soc@vger.kernel.org, linux-rtc@vger.kernel.org,
	 linux-doc@vger.kernel.org,
	Kaustabh Chakraborty <kauschluss@disroot.org>
Subject: [PATCH v6 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB LED device
Date: Fri, 15 May 2026 16:09:04 +0530	[thread overview]
Message-ID: <20260515-s2mu005-pmic-v6-8-1979106992d4@disroot.org> (raw)
In-Reply-To: <20260515-s2mu005-pmic-v6-0-1979106992d4@disroot.org>

Add support for the RGB LEDs found in certain Samsung S2M series PMICs.
The device has three LED channels, controlled as a single device. These
LEDs are typically used as status indicators in mobile phones.

The driver includes initial support for the S2MU005 PMIC RGB LEDs.

Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
 drivers/leds/rgb/Kconfig        |  11 ++
 drivers/leds/rgb/Makefile       |   1 +
 drivers/leds/rgb/leds-s2m-rgb.c | 416 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+)

diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 28ef4c487367c..cf57d85e574ec 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -75,6 +75,17 @@ config LEDS_QCOM_LPG
 
 	  If compiled as a module, the module will be named leds-qcom-lpg.
 
+config LEDS_S2M_RGB
+	tristate "Samsung S2M series PMICs RGB LED support"
+	depends on LEDS_CLASS
+	depends on MFD_SEC_CORE
+	select REGMAP_IRQ
+	help
+	  This option enables support for the S2MU005 RGB LEDs. These devices
+	  have three LED channels, with 8-bit brightness control for each
+	  channel. The S2MU005 usually found in mobile phones as status
+	  indicators.
+
 config LEDS_MT6370_RGB
 	tristate "LED Support for MediaTek MT6370 PMIC"
 	depends on MFD_MT6370
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
index be45991f63f50..98050e1aa4255 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -6,4 +6,5 @@ obj-$(CONFIG_LEDS_LP5812)		+= leds-lp5812.o
 obj-$(CONFIG_LEDS_NCP5623)		+= leds-ncp5623.o
 obj-$(CONFIG_LEDS_PWM_MULTICOLOR)	+= leds-pwm-multicolor.o
 obj-$(CONFIG_LEDS_QCOM_LPG)		+= leds-qcom-lpg.o
+obj-$(CONFIG_LEDS_S2M_RGB)		+= leds-s2m-rgb.o
 obj-$(CONFIG_LEDS_MT6370_RGB)		+= leds-mt6370-rgb.o
diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c
new file mode 100644
index 0000000000000..e7295d0b150da
--- /dev/null
+++ b/drivers/leds/rgb/leds-s2m-rgb.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RGB LED Driver for Samsung S2M series PMICs.
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd
+ * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org>
+ */
+
+#include <linux/container_of.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mfd/samsung/core.h>
+#include <linux/mfd/samsung/s2mu005.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+struct s2m_rgb {
+	struct device *dev;
+	struct regmap *regmap;
+	struct led_classdev_mc mc;
+	/*
+	 * The mutex object prevents race conditions when evaluation and
+	 * application of LED pattern state.
+	 */
+	struct mutex lock;
+	/*
+	 * State variables representing the current LED pattern, these only to
+	 * be accessed when lock is held.
+	 */
+	u8 ramp_up;
+	u8 ramp_dn;
+	u8 stay_hi;
+	u8 stay_lo;
+};
+
+static struct led_classdev_mc *to_s2m_mc(struct led_classdev *cdev)
+{
+	return container_of(cdev, struct led_classdev_mc, led_cdev);
+}
+
+static struct s2m_rgb *to_s2m_rgb(struct led_classdev_mc *mc)
+{
+	return container_of(mc, struct s2m_rgb, mc);
+}
+
+static const u32 s2mu005_rgb_lut_ramp[] = {
+	0,	100,	200,	300,	400,	500,	600,	700,
+	800,	1000,	1200,	1400,	1600,	1800,	2000,	2200,
+};
+
+static const u32 s2mu005_rgb_lut_stay_hi[] = {
+	100,	200,	300,	400,	500,	750,	1000,	1250,
+	1500,	1750,	2000,	2250,	2500,	2750,	3000,	3250,
+};
+
+static const u32 s2mu005_rgb_lut_stay_lo[] = {
+	0,	500,	1000,	1500,	2000,	2500,	3000,	3500,
+	4000,	4500,	5000,	6000,	7000,	8000,	10000,	12000,
+};
+
+static int s2mu005_rgb_apply_params(struct s2m_rgb *rgb)
+{
+	struct regmap *regmap = rgb->regmap;
+	unsigned int ramp_val = 0;
+	unsigned int stay_val = 0;
+	int ret;
+
+	ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_UP, rgb->ramp_up);
+	ramp_val |= FIELD_PREP(S2MU005_RGB_CH_RAMP_DN, rgb->ramp_dn);
+
+	stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_HI, rgb->stay_hi);
+	stay_val |= FIELD_PREP(S2MU005_RGB_CH_STAY_LO, rgb->stay_lo);
+
+	ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
+	if (ret) {
+		dev_err(rgb->dev, "failed to reset RGB LEDs\n");
+		return ret;
+	}
+
+	for (int i = 0; i < rgb->mc.num_colors; i++) {
+		ret = regmap_write(regmap, S2MU005_REG_RGB_CH_CTRL(i),
+				   rgb->mc.subled_info[i].brightness);
+		if (ret) {
+			dev_err(rgb->dev, "failed to set LED brightness\n");
+			return ret;
+		}
+
+		ret = regmap_write(regmap, S2MU005_REG_RGB_CH_RAMP(i), ramp_val);
+		if (ret) {
+			dev_err(rgb->dev, "failed to set ramp timings\n");
+			return ret;
+		}
+
+		ret = regmap_write(regmap, S2MU005_REG_RGB_CH_STAY(i), stay_val);
+		if (ret) {
+			dev_err(rgb->dev, "failed to set stay timings\n");
+			return ret;
+		}
+	}
+
+	ret = regmap_update_bits(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_SLOPE,
+				 S2MU005_RGB_SLOPE_SMOOTH);
+	if (ret) {
+		dev_err(rgb->dev, "failed to set ramp slope\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int s2mu005_rgb_reset_params(struct s2m_rgb *rgb)
+{
+	struct regmap *regmap = rgb->regmap;
+	int ret;
+
+	ret = regmap_write(regmap, S2MU005_REG_RGB_EN, S2MU005_RGB_RESET);
+	if (ret) {
+		dev_err(rgb->dev, "failed to reset RGB LEDs\n");
+		return ret;
+	}
+
+	rgb->ramp_up = 0;
+	rgb->ramp_dn = 0;
+	rgb->stay_hi = 0;
+	rgb->stay_lo = 0;
+
+	return 0;
+}
+
+/*
+ * s2m_rgb_lut_get_closest_duration - find closest duration in look-up table
+ * @lut: the look-up table to search for the closest timing
+ * @len: number of elements in the look-up table array
+ * @duration: the timing duration requested
+ *
+ * This function does a binary search on the given array, and finds the closest
+ * value to the requested timing. It is expected that the look-up table to be
+ * provided, is already sorted.
+ *
+ * This function returns a negative error code, or a non-negative index of the
+ * value in the look-up table closest to the one requested.
+ */
+static int s2m_rgb_lut_get_closest_duration(const u32 *lut, const size_t len, const u32 duration)
+{
+	u32 closest_distance = abs(duration - lut[0]);
+	int closest_index = 0;
+	int lo = 0;
+	int hi = len - 1;
+
+	/* Bounds checking */
+	if (duration < lut[0] || duration > lut[len - 1])
+		return -EINVAL;
+
+	while (lo <= hi) {
+		int mid = lo + (hi - lo) / 2;
+
+		/* Narrow down search window as per binary-search algorithm. */
+		if (duration < lut[mid])
+			hi = mid - 1;
+		else
+			lo = mid + 1;
+
+		if (abs(duration - lut[mid]) < closest_distance) {
+			closest_distance = abs(duration - lut[mid]);
+			closest_index = mid;
+		}
+	}
+
+	return closest_index;
+}
+
+static int s2m_rgb_pattern_set(struct led_classdev *cdev, struct led_pattern *pattern, u32 len,
+			       int repeat)
+{
+	struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
+	const u32 *lut_ramp_up, *lut_ramp_dn, *lut_stay_hi, *lut_stay_lo;
+	size_t lut_ramp_up_len, lut_ramp_dn_len, lut_stay_hi_len, lut_stay_lo_len;
+	int brightness_peak = 0;
+	u32 time_hi = 0, time_lo = 0;
+	bool ramp_up_en, ramp_dn_en;
+	int ret;
+
+	/*
+	 * The typical pattern supported by this device can be represented with
+	 * the following graph:
+	 *
+	 *  255 T ''''''-.                         .-'''''''-.
+	 *      |         '.                     .'           '.
+	 *      |           \                   /               \
+	 *      |            '.               .'                 '.
+	 *      |              '-...........-'                     '-
+	 *    0 +----------------------------------------------------> time (s)
+	 *
+	 *       <---- HIGH ----><-- LOW --><-------- HIGH --------->
+	 *       <-----><-------><---------><-------><-----><------->
+	 *       stay_hi ramp_dn   stay_lo   ramp_up stay_hi ramp_dn
+	 *
+	 * There are two states, named HIGH and LOW. HIGH has a non-zero
+	 * brightness level, while LOW is of zero brightness. The pattern
+	 * provided should mention only one zero and non-zero brightness level.
+	 * The hardware always starts the pattern from the HIGH state, as shown
+	 * in the graph.
+	 *
+	 * The HIGH state can be divided in three somewhat equal timings:
+	 * ramp_up, stay_hi, and ramp_dn. The LOW state has only one timing:
+	 * stay_lo.
+	 */
+
+	/* Only indefinitely looping patterns are supported. */
+	if (repeat != -1)
+		return -EINVAL;
+
+	/* Pattern should consist of at least two tuples. */
+	if (len < 2)
+		return -EINVAL;
+
+	for (int i = 0; i < len; i++) {
+		int brightness = pattern[i].brightness;
+		u32 delta_t = pattern[i].delta_t;
+
+		if (brightness) {
+			/*
+			 * The pattern shold define only one non-zero
+			 * brightness in the HIGH state. The device doesn't
+			 * have any provisions to handle multiple peak
+			 * brightness levels.
+			 */
+			if (brightness_peak && brightness_peak != brightness)
+				return -EINVAL;
+
+			brightness_peak = brightness;
+			time_hi += delta_t;
+			ramp_dn_en = !!delta_t;
+		} else {
+			time_lo += delta_t;
+			ramp_up_en = !!delta_t;
+		}
+	}
+
+	/* LUTs are specific to device variant. */
+	lut_ramp_up = s2mu005_rgb_lut_ramp;
+	lut_ramp_up_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
+	lut_ramp_dn = s2mu005_rgb_lut_ramp;
+	lut_ramp_dn_len = ARRAY_SIZE(s2mu005_rgb_lut_ramp);
+	lut_stay_hi = s2mu005_rgb_lut_stay_hi;
+	lut_stay_hi_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_hi);
+	lut_stay_lo = s2mu005_rgb_lut_stay_lo;
+	lut_stay_lo_len = ARRAY_SIZE(s2mu005_rgb_lut_stay_lo);
+
+	mutex_lock(&rgb->lock);
+
+	/*
+	 * The timings ramp_up, stay_hi, and ramp_dn of the HIGH state are
+	 * roughly equal. Firstly, calculate and set timings for ramp_up and
+	 * ramp_dn (making sure they're exactly equal).
+	 */
+	rgb->ramp_up = 0;
+	rgb->ramp_dn = 0;
+
+	if (ramp_up_en) {
+		ret = s2m_rgb_lut_get_closest_duration(lut_ramp_up, lut_ramp_up_len, time_hi / 3);
+		if (ret < 0)
+			goto param_fail;
+		rgb->ramp_up = (u8)ret;
+	}
+
+	if (ramp_dn_en) {
+		ret = s2m_rgb_lut_get_closest_duration(lut_ramp_dn, lut_ramp_dn_len, time_hi / 3);
+		if (ret < 0)
+			goto param_fail;
+		rgb->ramp_dn = (u8)ret;
+	}
+
+	/*
+	 * Subtract the allocated ramp timings from time_hi (and also making
+	 * sure it doesn't underflow!). The remaining time is allocated to
+	 * stay_hi.
+	 */
+	time_hi -= min(time_hi, lut_ramp_up[rgb->ramp_up]);
+	time_hi -= min(time_hi, lut_ramp_dn[rgb->ramp_dn]);
+
+	ret = s2m_rgb_lut_get_closest_duration(lut_stay_hi, lut_stay_hi_len, time_hi);
+	if (ret < 0)
+		goto param_fail;
+	rgb->stay_hi = (u8)ret;
+
+	ret = s2m_rgb_lut_get_closest_duration(lut_stay_lo, lut_stay_lo_len, time_lo);
+	if (ret < 0)
+		goto param_fail;
+	rgb->stay_lo = (u8)ret;
+
+	led_mc_calc_color_components(&rgb->mc, brightness_peak);
+	/* Apply params with variant-specific implementation. */
+	ret = s2mu005_rgb_apply_params(rgb);
+	if (ret)
+		goto param_fail;
+
+	mutex_unlock(&rgb->lock);
+
+	return 0;
+
+param_fail:
+	rgb->ramp_up = 0;
+	rgb->ramp_dn = 0;
+	rgb->stay_hi = 0;
+	rgb->stay_lo = 0;
+
+	mutex_unlock(&rgb->lock);
+
+	return ret;
+}
+
+static int s2m_rgb_pattern_clear(struct led_classdev *cdev)
+{
+	struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
+	int ret = 0;
+
+	mutex_lock(&rgb->lock);
+
+	/* Reset params with variant-specific implementation. */
+	ret = s2mu005_rgb_reset_params(rgb);
+
+	mutex_unlock(&rgb->lock);
+
+	return ret;
+}
+
+static int s2m_rgb_brightness_set(struct led_classdev *cdev, enum led_brightness value)
+{
+	struct s2m_rgb *rgb = to_s2m_rgb(to_s2m_mc(cdev));
+	int ret = 0;
+
+	if (!value)
+		return s2m_rgb_pattern_clear(cdev);
+
+	mutex_lock(&rgb->lock);
+
+	led_mc_calc_color_components(&rgb->mc, value);
+	/* Apply params with variant-specific implementation. */
+	ret = s2mu005_rgb_apply_params(rgb);
+
+	mutex_unlock(&rgb->lock);
+
+	return ret;
+}
+
+static struct mc_subled s2mu005_rgb_subled_info[] = {
+	{ .channel = 0, .color_index = LED_COLOR_ID_BLUE },
+	{ .channel = 1, .color_index = LED_COLOR_ID_GREEN },
+	{ .channel = 2, .color_index = LED_COLOR_ID_RED },
+};
+
+static int s2m_rgb_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent);
+	struct s2m_rgb *rgb;
+	struct led_init_data init_data = {};
+	int ret;
+
+	rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
+	if (!rgb)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, rgb);
+	rgb->dev = dev;
+	rgb->regmap = pmic_drvdata->regmap_pmic;
+
+	/* Configure variant-specific details. */
+	rgb->mc.subled_info = s2mu005_rgb_subled_info;
+	rgb->mc.num_colors = ARRAY_SIZE(s2mu005_rgb_subled_info);
+
+	rgb->mc.led_cdev.max_brightness = 255;
+	rgb->mc.led_cdev.brightness_set_blocking = s2m_rgb_brightness_set;
+	rgb->mc.led_cdev.pattern_set = s2m_rgb_pattern_set;
+	rgb->mc.led_cdev.pattern_clear = s2m_rgb_pattern_clear;
+
+	ret = devm_mutex_init(dev, &rgb->lock);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to create mutex lock\n");
+
+	init_data.fwnode = of_fwnode_handle(dev->of_node);
+	ret = devm_led_classdev_multicolor_register_ext(dev, &rgb->mc, &init_data);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to create LED device\n");
+
+	return 0;
+}
+
+static const struct platform_device_id s2m_rgb_id_table[] = {
+	{ "s2mu005-rgb", S2MU005 },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, s2m_rgb_id_table);
+
+static const struct of_device_id s2m_rgb_of_match_table[] = {
+	{ .compatible = "samsung,s2mu005-rgb", .data = (void *)S2MU005 },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, s2m_rgb_of_match_table);
+
+static struct platform_driver s2m_rgb_driver = {
+	.driver = {
+		.name = "s2m-rgb",
+	},
+	.probe = s2m_rgb_probe,
+	.id_table = s2m_rgb_id_table,
+};
+module_platform_driver(s2m_rgb_driver);
+
+MODULE_DESCRIPTION("RGB LED Driver for Samsung S2M Series PMICs");
+MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
+MODULE_LICENSE("GPL");

-- 
2.53.0


  parent reply	other threads:[~2026-05-15 10:41 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-15 10:38 [PATCH v6 00/11] Support for Samsung S2MU005 PMIC and its sub-devices Kaustabh Chakraborty
2026-05-15 10:38 ` [PATCH v6 01/11] dt-bindings: leds: document Samsung S2M series PMIC flash LED device Kaustabh Chakraborty
2026-05-15 10:38 ` [PATCH v6 02/11] dt-bindings: extcon: document Samsung S2M series PMIC extcon device Kaustabh Chakraborty
2026-05-15 11:05   ` sashiko-bot
2026-05-15 17:15   ` Conor Dooley
2026-05-15 10:38 ` [PATCH v6 03/11] dt-bindings: mfd: add documentation for S2MU005 PMIC Kaustabh Chakraborty
2026-05-15 10:52   ` sashiko-bot
2026-05-15 17:14   ` Conor Dooley
2026-05-15 10:39 ` [PATCH v6 04/11] mfd: sec: add support " Kaustabh Chakraborty
2026-05-15 11:16   ` sashiko-bot
2026-05-15 10:39 ` [PATCH v6 05/11] mfd: sec: set DMA coherent mask Kaustabh Chakraborty
2026-05-15 11:10   ` sashiko-bot
2026-05-15 10:39 ` [PATCH v6 06/11] mfd: sec: resolve PMIC revision in S2MU005 Kaustabh Chakraborty
2026-05-15 10:39 ` [PATCH v6 07/11] leds: flash: add support for Samsung S2M series PMIC flash LED device Kaustabh Chakraborty
2026-05-15 11:05   ` sashiko-bot
2026-05-15 10:39 ` Kaustabh Chakraborty [this message]
2026-05-15 11:13   ` [PATCH v6 08/11] leds: rgb: add support for Samsung S2M series PMIC RGB " sashiko-bot
2026-05-15 10:39 ` [PATCH v6 09/11] Documentation: leds: document pattern behavior of Samsung S2M series PMIC RGB LEDs Kaustabh Chakraborty
2026-05-15 11:03   ` sashiko-bot
2026-05-15 10:39 ` [PATCH v6 10/11] extcon: add support for Samsung S2M series PMIC extcon devices Kaustabh Chakraborty
2026-05-15 11:17   ` sashiko-bot
2026-05-15 10:39 ` [PATCH v6 11/11] power: supply: add support for Samsung S2M series PMIC charger device Kaustabh Chakraborty
2026-05-15 11:20   ` sashiko-bot

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=20260515-s2mu005-pmic-v6-8-1979106992d4@disroot.org \
    --to=kauschluss@disroot.org \
    --cc=alexandre.belloni@bootlin.com \
    --cc=andre.draszik@linaro.org \
    --cc=conor+dt@kernel.org \
    --cc=corbet@lwn.net \
    --cc=cw00.choi@samsung.com \
    --cc=devicetree@vger.kernel.org \
    --cc=kernel@lvkasz.us \
    --cc=krzk+dt@kernel.org \
    --cc=krzk@kernel.org \
    --cc=lee@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-rtc@vger.kernel.org \
    --cc=linux-samsung-soc@vger.kernel.org \
    --cc=myungjoo.ham@samsung.com \
    --cc=pavel@kernel.org \
    --cc=robh@kernel.org \
    --cc=skhan@linuxfoundation.org \
    --cc=sre@kernel.org \
    --cc=trannamatk@gmail.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox