All of lore.kernel.org
 help / color / mirror / Atom feed
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
To: linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	lm-sensors@lm-sensors.org, linux-input@vger.kernel.org,
	linux-watchdog@vger.kernel.org, linux-leds@vger.kernel.org
Cc: Alessandro Zummo <a.zummo@towertech.it>,
	Andrew Jones <drjones@redhat.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Samuel Ortiz <sameo@linux.intel.com>,
	Ashish Jangam <ashish.jangam@kpitcummins.com>,
	Mark Brown <broonie@opensource.wolfsonmicro.com>,
	Donggeun Kim <dg77.kim@samsung.com>,
	Wim Van Sebroeck <wim@iguana.be>,
	Richard Purdie <rpurdie@rpsys.net>,
	Bryan Wu <bryan.wu@canonical.com>, Liam Girdwood <lrg@ti.com>
Subject: [PATCH 4/8] hwmon: Add DA906x hardware monitoring support.
Date: Fri, 24 Aug 2012 09:32:00 +0100	[thread overview]
Message-ID: <201208240834@sw-eng-lt-dc-vm2> (raw)
In-Reply-To: <201208240833@sw-eng-lt-dc-vm2>

DA906x PMIC provides ADC for voltage and temperature monitoring.

The driver provides results of following ADC channels:
 - in0 - system voltage (2500 - 5500 mV)
 - in1 - universal voltage channel #1 (0 - 2500 mV)
 - in2 - universal voltage channel #2 (0 - 2500 mV)
 - in3 - universal voltage channel #3 (0 - 2500 mV)
 - in4 - backup battery voltage (0 - 5000 mV)
 - temp1 - PMIC internal junction temperature (-88 - 333 Celcius degrees)

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
 drivers/hwmon/Kconfig        |    6 +
 drivers/hwmon/Makefile       |    1 +
 drivers/hwmon/da906x-hwmon.c |  393 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 400 insertions(+), 0 deletions(-)
 create mode 100644 drivers/hwmon/da906x-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b0a2e4c..7abc9a0 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1373,6 +1373,12 @@ config SENSORS_WM8350
 	  This driver can also be built as a module.  If so, the module
 	  will be called wm8350-hwmon.
 
+config SENSORS_DA906X
+	tristate "DA906X HWMON device drivers"
+	depends on MFD_DA906X
+	help
+	  Support for the HWMON DA906X device driver.
+
 config SENSORS_ULTRA45
 	tristate "Sun Ultra45 PIC16F747"
 	depends on SPARC64
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 7aa9811..ffbe151 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
+obj-$(CONFIG_SENSORS_DA906X)	+= da906x-hwmon.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
diff --git a/drivers/hwmon/da906x-hwmon.c b/drivers/hwmon/da906x-hwmon.c
new file mode 100644
index 0000000..8ece931
--- /dev/null
+++ b/drivers/hwmon/da906x-hwmon.c
@@ -0,0 +1,393 @@
+/*
+ * HW Monitor support for Dialog DA906X PMIC series
+ *
+ * Copyright 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.com>,
+ *         Michal Hajduk <michal.hajduk@diasemi.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/da906x/core.h>
+#include <linux/mfd/da906x/pdata.h>
+
+/* ADC resolutions for manual and auto modes */
+#define DA906X_ADC_RES		\
+		(1 << (DA906X_ADC_RES_L_BITS + DA906X_ADC_RES_M_BITS))
+#define DA906X_ADC_MAX		(DA906X_ADC_RES - 1)
+#define	DA906X_ADC_AUTO_RES	(1 << DA906X_ADC_RES_M_BITS)
+#define	DA906X_ADC_AUTO_MAX	(DA906X_ADC_AUTO_RES - 1)
+
+/* Define interpolation table to calculate ADC values  */
+struct i_table {
+	int x0;
+	int a;
+	int b;
+};
+#define ILINE(x1, x2, y1, y2)	{ \
+		.x0 = (x1), \
+		.a = ((y2) - (y1)) * DA906X_ADC_RES / ((x2) - (x1)), \
+		.b = (y1) - ((y2) - (y1)) * (x1) / ((x2) - (x1)), \
+	}
+
+struct channel_info {
+	const char *name;
+	const struct i_table *tbl;
+	int tbl_max;
+	u16 reg_auto_en;
+};
+
+enum da906x_adc {
+	DA906X_VSYS,
+	DA906X_ADCIN1,
+	DA906X_ADCIN2,
+	DA906X_ADCIN3,
+	DA906X_TJUNC,
+	DA906X_VBBAT,
+
+	DA906X_CHAN_NUM
+};
+
+static const struct i_table vsys_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 2500, 5500)
+};
+
+static const struct i_table adcin_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 0, 2500)
+};
+
+static const struct i_table tjunc_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 333, -86)
+};
+
+static const struct i_table vbbat_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 0, 5000)
+};
+
+static const struct channel_info da906x_channels[] = {
+	[DA906X_VSYS]	= { "VSYS",
+			    vsys_tbl, ARRAY_SIZE(vsys_tbl) - 1,
+			    DA906X_REG_VSYS_RES },
+	[DA906X_ADCIN1]	= { "ADCIN1",
+			    adcin_tbl,	ARRAY_SIZE(adcin_tbl) - 1,
+			    DA906X_REG_ADCIN1_RES },
+	[DA906X_ADCIN2]	= { "ADCIN2",
+			    adcin_tbl,	ARRAY_SIZE(adcin_tbl) - 1,
+			    DA906X_REG_ADCIN2_RES },
+	[DA906X_ADCIN3]	= { "ADCIN3",
+			    adcin_tbl,	ARRAY_SIZE(adcin_tbl) - 1,
+			    DA906X_REG_ADCIN3_RES },
+	[DA906X_TJUNC]	= { "TJUNC",
+			    tjunc_tbl,	ARRAY_SIZE(tjunc_tbl) - 1 },
+	[DA906X_VBBAT]	= { "VBBAT",
+			    vbbat_tbl,	ARRAY_SIZE(vbbat_tbl) - 1}
+};
+#define DA906X_ADC_AUTO_MODE_SUPPORT_MASK	(DA906X_ADC_AUTO_VSYS_EN | \
+						 DA906X_ADC_AUTO_AD1_EN | \
+						 DA906X_ADC_AUTO_AD2_EN | \
+						 DA906X_ADC_AUTO_AD3_EN)
+
+struct da906x_hwmon {
+	struct da906x *da906x;
+	struct device *class_dev;
+	struct completion man_adc_rdy;	/* Manual read completion flag */
+	struct mutex hwmon_mutex;	/* Queue concurent manual reads */
+	int irq;
+	u8 adc_auto_en;     /* Bitmask of channels with auto mode enabled */
+	s8 tjunc_offset;    /* Calibration offset for junction temperature */
+};
+
+int da906x_adc_convert(int channel, int x)
+{
+	const struct channel_info *info = &da906x_channels[channel];
+	int i, ret;
+
+	for (i = info->tbl_max; i > 0; i--)
+		if (info->tbl[i].x0 <= x)
+			break;
+
+	ret = info->tbl[i].a * x;
+	if (ret >= 0)
+		ret += DA906X_ADC_RES / 2;
+	else
+		ret -= DA906X_ADC_RES / 2;
+	ret = ret / DA906X_ADC_RES + info->tbl[i].b;
+	return ret;
+}
+
+static int da906x_adc_manual_read(struct da906x_hwmon *hwmon, int channel)
+{
+	int ret;
+	u8 data[2];
+
+	mutex_lock(&hwmon->hwmon_mutex);
+
+	init_completion(&hwmon->man_adc_rdy);
+
+	/* Start measurment on selected channel */
+	data[0] = (channel << DA906X_ADC_MUX_SHIFT) & DA906X_ADC_MUX_MASK;
+	data[0] |= DA906X_ADC_MAN;
+	ret = da906x_reg_update(hwmon->da906x, DA906X_REG_ADC_MAN,
+				DA906X_ADC_MUX_MASK | DA906X_ADC_MAN, data[0]);
+	if (ret < 0)
+		goto out;
+
+	/* Wait for interrupt from ADC */
+	ret = wait_for_completion_timeout(&hwmon->man_adc_rdy,
+					  msecs_to_jiffies(1000));
+	if (ret == 0) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Get results */
+	ret = da906x_block_read(hwmon->da906x, DA906X_REG_ADC_RES_L, 2, data);
+	if (ret < 0)
+		goto out;
+	ret = (data[0] & DA906X_ADC_RES_L_MASK) >> DA906X_ADC_RES_L_SHIFT;
+	ret |= data[1] << DA906X_ADC_RES_L_BITS;
+out:
+	mutex_unlock(&hwmon->hwmon_mutex);
+	return ret;
+}
+
+static int da906x_adc_auto_read(struct da906x *da906x, int channel)
+{
+	const struct channel_info *info = &da906x_channels[channel];
+
+	return da906x_reg_read(da906x, info->reg_auto_en);
+}
+
+static irqreturn_t da906x_hwmon_irq_handler(int irq, void *irq_data)
+{
+	struct da906x_hwmon *hwmon = irq_data;
+
+	complete(&hwmon->man_adc_rdy);
+
+	return IRQ_HANDLED;
+}
+
+static ssize_t da906x_adc_read(struct device *dev,
+			       struct device_attribute *devattr, char *buf)
+{
+	struct da906x_hwmon *hwmon = dev_get_drvdata(dev);
+	int channel = to_sensor_dev_attr(devattr)->index;
+	int val;
+
+	if (hwmon->adc_auto_en & (1 << channel)) {
+		val = da906x_adc_auto_read(hwmon->da906x, channel);
+		if (val < 0)
+			return val;
+
+		val *= DA906X_ADC_RES / DA906X_ADC_AUTO_RES;
+		val = da906x_adc_convert(channel, val);
+	} else {
+		val = da906x_adc_manual_read(hwmon, channel);
+		if (val < 0)
+			return val;
+
+		if (channel == DA906X_TJUNC)
+			val += hwmon->tjunc_offset;
+		val = da906x_adc_convert(channel, val);
+	}
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t da906x_show_name(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, DA906X_DRVNAME_HWMON "\n");
+}
+
+static ssize_t da906x_show_label(struct device *dev,
+				 struct device_attribute *devattr, char *buf)
+{
+	const struct channel_info *info;
+
+	info = &da906x_channels[to_sensor_dev_attr(devattr)->index];
+	return sprintf(buf, "%s\n", info->name);
+}
+
+/* Vsys voltage */
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_VSYS);
+static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_VSYS);
+
+/* Universal ADC channel #1 */
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_ADCIN1);
+static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_ADCIN1);
+
+/* Universal ADC channel #2 */
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO,
+			  da906x_adc_read, NULL,
+			  DA906X_ADCIN2);
+static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_ADCIN2);
+
+/* Universal ADC channel #3 */
+static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_ADCIN3);
+static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_ADCIN3);
+
+/* Backup battery voltage */
+static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_VBBAT);
+static SENSOR_DEVICE_ATTR(in4_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_VBBAT);
+
+/* Junction temperature */
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_TJUNC);
+
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_TJUNC);
+
+/* Device name */
+static DEVICE_ATTR(name, S_IRUGO, da906x_show_name, NULL);
+
+static struct attribute *da906x_attributes[] = {
+	&dev_attr_name.attr,
+	&sensor_dev_attr_in0_input.dev_attr.attr,
+	&sensor_dev_attr_in0_label.dev_attr.attr,
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_label.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_label.dev_attr.attr,
+	&sensor_dev_attr_in3_input.dev_attr.attr,
+	&sensor_dev_attr_in3_label.dev_attr.attr,
+	&sensor_dev_attr_in4_input.dev_attr.attr,
+	&sensor_dev_attr_in4_label.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_label.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group da906x_attr_group = {
+	.attrs = da906x_attributes,
+};
+
+static int __devinit da906x_hwmon_probe(struct platform_device *pdev)
+{
+	struct da906x *da906x = dev_get_drvdata(pdev->dev.parent);
+	struct da906x_pdata *da906x_pdata = dev_get_platdata(da906x->dev);
+	struct da906x_hwmon *hwmon;
+	int ret;
+
+	hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da906x_hwmon),
+			     GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+
+	mutex_init(&hwmon->hwmon_mutex);
+	init_completion(&hwmon->man_adc_rdy);
+	hwmon->da906x = da906x;
+
+	ret = da906x_reg_read(da906x, DA906X_REG_ADC_CONT);
+	if (ret < 0)
+		return ret;
+	hwmon->adc_auto_en = ret & DA906X_ADC_AUTO_MODE_SUPPORT_MASK;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN0_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_VSYS_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN0_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_VSYS_EN;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN1_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_AD1_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN1_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_AD1_EN;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN2_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_AD2_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN2_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_AD2_EN;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN3_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_AD3_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN3_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_AD3_EN;
+
+	ret = da906x_reg_update(da906x, DA906X_REG_ADC_CONT,
+				DA906X_ADC_AUTO_MODE_SUPPORT_MASK,
+				hwmon->adc_auto_en);
+	if (ret < 0)
+		return ret;
+
+	hwmon->class_dev = hwmon_device_register(&pdev->dev);
+	if (IS_ERR(hwmon->class_dev))
+		return PTR_ERR(hwmon->class_dev);
+
+	hwmon->irq = platform_get_irq_byname(pdev, DA906X_DRVNAME_HWMON);
+	ret = devm_request_threaded_irq(&pdev->dev, hwmon->irq, NULL,
+					da906x_hwmon_irq_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"HWMON", hwmon);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ.\n");
+		goto err;
+	}
+
+	platform_set_drvdata(pdev, hwmon);
+
+	ret = da906x_reg_read(da906x, DA906X_REG_T_OFFSET);
+	if (ret < 0)
+		dev_warn(&pdev->dev, "Could not read temp1 callibration offset.\n");
+	else
+		hwmon->tjunc_offset = (s8)ret;
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &da906x_attr_group);
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	hwmon_device_unregister(hwmon->class_dev);
+	return ret;
+}
+
+static int __devexit da906x_hwmon_remove(struct platform_device *pdev)
+{
+	struct da906x_hwmon *hwmon = platform_get_drvdata(pdev);
+
+	hwmon_device_unregister(hwmon->class_dev);
+	sysfs_remove_group(&pdev->dev.kobj, &da906x_attr_group);
+
+	return 0;
+}
+
+static struct platform_driver da906x_hwmon_driver = {
+	.probe = da906x_hwmon_probe,
+	.remove = __devexit_p(da906x_hwmon_remove),
+	.driver = {
+		.name = DA906X_DRVNAME_HWMON,
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(da906x_hwmon_driver);
+
+MODULE_DESCRIPTION("DA906X Hardware monitoring");
+MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>, Michal Hajduk <michal.hajduk@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("paltform:" DA906X_DRVNAME_HWMON);
-- 
1.7.0.4


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

WARNING: multiple messages have this Message-ID (diff)
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
To: linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	lm-sensors@lm-sensors.org, linux-input@vger.kernel.org,
	linux-watchdog@vger.kernel.org, linux-leds@vger.kernel.org
Cc: Alessandro Zummo <a.zummo@towertech.it>,
	Andrew Jones <drjones@redhat.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Samuel Ortiz <sameo@linux.intel.com>,
	Ashish Jangam <ashish.jangam@kpitcummins.com>,
	Mark Brown <broonie@opensource.wolfsonmicro.com>,
	Donggeun Kim <dg77.kim@samsung.com>,
	Wim Van Sebroeck <wim@iguana.be>,
	Richard Purdie <rpurdie@rpsys.net>,
	Bryan Wu <bryan.wu@canonical.com>, Liam Girdwood <lrg@ti.com>
Subject: [lm-sensors] [PATCH 4/8] hwmon: Add DA906x hardware monitoring support.
Date: Fri, 24 Aug 2012 08:32:00 +0000	[thread overview]
Message-ID: <201208240834@sw-eng-lt-dc-vm2> (raw)
In-Reply-To: <201208240833@sw-eng-lt-dc-vm2>

DA906x PMIC provides ADC for voltage and temperature monitoring.

The driver provides results of following ADC channels:
 - in0 - system voltage (2500 - 5500 mV)
 - in1 - universal voltage channel #1 (0 - 2500 mV)
 - in2 - universal voltage channel #2 (0 - 2500 mV)
 - in3 - universal voltage channel #3 (0 - 2500 mV)
 - in4 - backup battery voltage (0 - 5000 mV)
 - temp1 - PMIC internal junction temperature (-88 - 333 Celcius degrees)

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
 drivers/hwmon/Kconfig        |    6 +
 drivers/hwmon/Makefile       |    1 +
 drivers/hwmon/da906x-hwmon.c |  393 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 400 insertions(+), 0 deletions(-)
 create mode 100644 drivers/hwmon/da906x-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b0a2e4c..7abc9a0 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1373,6 +1373,12 @@ config SENSORS_WM8350
 	  This driver can also be built as a module.  If so, the module
 	  will be called wm8350-hwmon.
 
+config SENSORS_DA906X
+	tristate "DA906X HWMON device drivers"
+	depends on MFD_DA906X
+	help
+	  Support for the HWMON DA906X device driver.
+
 config SENSORS_ULTRA45
 	tristate "Sun Ultra45 PIC16F747"
 	depends on SPARC64
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 7aa9811..ffbe151 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
+obj-$(CONFIG_SENSORS_DA906X)	+= da906x-hwmon.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
diff --git a/drivers/hwmon/da906x-hwmon.c b/drivers/hwmon/da906x-hwmon.c
new file mode 100644
index 0000000..8ece931
--- /dev/null
+++ b/drivers/hwmon/da906x-hwmon.c
@@ -0,0 +1,393 @@
+/*
+ * HW Monitor support for Dialog DA906X PMIC series
+ *
+ * Copyright 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.com>,
+ *         Michal Hajduk <michal.hajduk@diasemi.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/da906x/core.h>
+#include <linux/mfd/da906x/pdata.h>
+
+/* ADC resolutions for manual and auto modes */
+#define DA906X_ADC_RES		\
+		(1 << (DA906X_ADC_RES_L_BITS + DA906X_ADC_RES_M_BITS))
+#define DA906X_ADC_MAX		(DA906X_ADC_RES - 1)
+#define	DA906X_ADC_AUTO_RES	(1 << DA906X_ADC_RES_M_BITS)
+#define	DA906X_ADC_AUTO_MAX	(DA906X_ADC_AUTO_RES - 1)
+
+/* Define interpolation table to calculate ADC values  */
+struct i_table {
+	int x0;
+	int a;
+	int b;
+};
+#define ILINE(x1, x2, y1, y2)	{ \
+		.x0 = (x1), \
+		.a = ((y2) - (y1)) * DA906X_ADC_RES / ((x2) - (x1)), \
+		.b = (y1) - ((y2) - (y1)) * (x1) / ((x2) - (x1)), \
+	}
+
+struct channel_info {
+	const char *name;
+	const struct i_table *tbl;
+	int tbl_max;
+	u16 reg_auto_en;
+};
+
+enum da906x_adc {
+	DA906X_VSYS,
+	DA906X_ADCIN1,
+	DA906X_ADCIN2,
+	DA906X_ADCIN3,
+	DA906X_TJUNC,
+	DA906X_VBBAT,
+
+	DA906X_CHAN_NUM
+};
+
+static const struct i_table vsys_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 2500, 5500)
+};
+
+static const struct i_table adcin_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 0, 2500)
+};
+
+static const struct i_table tjunc_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 333, -86)
+};
+
+static const struct i_table vbbat_tbl[] = {
+	ILINE(0, DA906X_ADC_MAX, 0, 5000)
+};
+
+static const struct channel_info da906x_channels[] = {
+	[DA906X_VSYS]	= { "VSYS",
+			    vsys_tbl, ARRAY_SIZE(vsys_tbl) - 1,
+			    DA906X_REG_VSYS_RES },
+	[DA906X_ADCIN1]	= { "ADCIN1",
+			    adcin_tbl,	ARRAY_SIZE(adcin_tbl) - 1,
+			    DA906X_REG_ADCIN1_RES },
+	[DA906X_ADCIN2]	= { "ADCIN2",
+			    adcin_tbl,	ARRAY_SIZE(adcin_tbl) - 1,
+			    DA906X_REG_ADCIN2_RES },
+	[DA906X_ADCIN3]	= { "ADCIN3",
+			    adcin_tbl,	ARRAY_SIZE(adcin_tbl) - 1,
+			    DA906X_REG_ADCIN3_RES },
+	[DA906X_TJUNC]	= { "TJUNC",
+			    tjunc_tbl,	ARRAY_SIZE(tjunc_tbl) - 1 },
+	[DA906X_VBBAT]	= { "VBBAT",
+			    vbbat_tbl,	ARRAY_SIZE(vbbat_tbl) - 1}
+};
+#define DA906X_ADC_AUTO_MODE_SUPPORT_MASK	(DA906X_ADC_AUTO_VSYS_EN | \
+						 DA906X_ADC_AUTO_AD1_EN | \
+						 DA906X_ADC_AUTO_AD2_EN | \
+						 DA906X_ADC_AUTO_AD3_EN)
+
+struct da906x_hwmon {
+	struct da906x *da906x;
+	struct device *class_dev;
+	struct completion man_adc_rdy;	/* Manual read completion flag */
+	struct mutex hwmon_mutex;	/* Queue concurent manual reads */
+	int irq;
+	u8 adc_auto_en;     /* Bitmask of channels with auto mode enabled */
+	s8 tjunc_offset;    /* Calibration offset for junction temperature */
+};
+
+int da906x_adc_convert(int channel, int x)
+{
+	const struct channel_info *info = &da906x_channels[channel];
+	int i, ret;
+
+	for (i = info->tbl_max; i > 0; i--)
+		if (info->tbl[i].x0 <= x)
+			break;
+
+	ret = info->tbl[i].a * x;
+	if (ret >= 0)
+		ret += DA906X_ADC_RES / 2;
+	else
+		ret -= DA906X_ADC_RES / 2;
+	ret = ret / DA906X_ADC_RES + info->tbl[i].b;
+	return ret;
+}
+
+static int da906x_adc_manual_read(struct da906x_hwmon *hwmon, int channel)
+{
+	int ret;
+	u8 data[2];
+
+	mutex_lock(&hwmon->hwmon_mutex);
+
+	init_completion(&hwmon->man_adc_rdy);
+
+	/* Start measurment on selected channel */
+	data[0] = (channel << DA906X_ADC_MUX_SHIFT) & DA906X_ADC_MUX_MASK;
+	data[0] |= DA906X_ADC_MAN;
+	ret = da906x_reg_update(hwmon->da906x, DA906X_REG_ADC_MAN,
+				DA906X_ADC_MUX_MASK | DA906X_ADC_MAN, data[0]);
+	if (ret < 0)
+		goto out;
+
+	/* Wait for interrupt from ADC */
+	ret = wait_for_completion_timeout(&hwmon->man_adc_rdy,
+					  msecs_to_jiffies(1000));
+	if (ret = 0) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Get results */
+	ret = da906x_block_read(hwmon->da906x, DA906X_REG_ADC_RES_L, 2, data);
+	if (ret < 0)
+		goto out;
+	ret = (data[0] & DA906X_ADC_RES_L_MASK) >> DA906X_ADC_RES_L_SHIFT;
+	ret |= data[1] << DA906X_ADC_RES_L_BITS;
+out:
+	mutex_unlock(&hwmon->hwmon_mutex);
+	return ret;
+}
+
+static int da906x_adc_auto_read(struct da906x *da906x, int channel)
+{
+	const struct channel_info *info = &da906x_channels[channel];
+
+	return da906x_reg_read(da906x, info->reg_auto_en);
+}
+
+static irqreturn_t da906x_hwmon_irq_handler(int irq, void *irq_data)
+{
+	struct da906x_hwmon *hwmon = irq_data;
+
+	complete(&hwmon->man_adc_rdy);
+
+	return IRQ_HANDLED;
+}
+
+static ssize_t da906x_adc_read(struct device *dev,
+			       struct device_attribute *devattr, char *buf)
+{
+	struct da906x_hwmon *hwmon = dev_get_drvdata(dev);
+	int channel = to_sensor_dev_attr(devattr)->index;
+	int val;
+
+	if (hwmon->adc_auto_en & (1 << channel)) {
+		val = da906x_adc_auto_read(hwmon->da906x, channel);
+		if (val < 0)
+			return val;
+
+		val *= DA906X_ADC_RES / DA906X_ADC_AUTO_RES;
+		val = da906x_adc_convert(channel, val);
+	} else {
+		val = da906x_adc_manual_read(hwmon, channel);
+		if (val < 0)
+			return val;
+
+		if (channel = DA906X_TJUNC)
+			val += hwmon->tjunc_offset;
+		val = da906x_adc_convert(channel, val);
+	}
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t da906x_show_name(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, DA906X_DRVNAME_HWMON "\n");
+}
+
+static ssize_t da906x_show_label(struct device *dev,
+				 struct device_attribute *devattr, char *buf)
+{
+	const struct channel_info *info;
+
+	info = &da906x_channels[to_sensor_dev_attr(devattr)->index];
+	return sprintf(buf, "%s\n", info->name);
+}
+
+/* Vsys voltage */
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_VSYS);
+static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_VSYS);
+
+/* Universal ADC channel #1 */
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_ADCIN1);
+static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_ADCIN1);
+
+/* Universal ADC channel #2 */
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO,
+			  da906x_adc_read, NULL,
+			  DA906X_ADCIN2);
+static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_ADCIN2);
+
+/* Universal ADC channel #3 */
+static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_ADCIN3);
+static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_ADCIN3);
+
+/* Backup battery voltage */
+static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_VBBAT);
+static SENSOR_DEVICE_ATTR(in4_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_VBBAT);
+
+/* Junction temperature */
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
+			  da906x_adc_read, NULL, DA906X_TJUNC);
+
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO,
+			  da906x_show_label, NULL, DA906X_TJUNC);
+
+/* Device name */
+static DEVICE_ATTR(name, S_IRUGO, da906x_show_name, NULL);
+
+static struct attribute *da906x_attributes[] = {
+	&dev_attr_name.attr,
+	&sensor_dev_attr_in0_input.dev_attr.attr,
+	&sensor_dev_attr_in0_label.dev_attr.attr,
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_label.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_label.dev_attr.attr,
+	&sensor_dev_attr_in3_input.dev_attr.attr,
+	&sensor_dev_attr_in3_label.dev_attr.attr,
+	&sensor_dev_attr_in4_input.dev_attr.attr,
+	&sensor_dev_attr_in4_label.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_label.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group da906x_attr_group = {
+	.attrs = da906x_attributes,
+};
+
+static int __devinit da906x_hwmon_probe(struct platform_device *pdev)
+{
+	struct da906x *da906x = dev_get_drvdata(pdev->dev.parent);
+	struct da906x_pdata *da906x_pdata = dev_get_platdata(da906x->dev);
+	struct da906x_hwmon *hwmon;
+	int ret;
+
+	hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da906x_hwmon),
+			     GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+
+	mutex_init(&hwmon->hwmon_mutex);
+	init_completion(&hwmon->man_adc_rdy);
+	hwmon->da906x = da906x;
+
+	ret = da906x_reg_read(da906x, DA906X_REG_ADC_CONT);
+	if (ret < 0)
+		return ret;
+	hwmon->adc_auto_en = ret & DA906X_ADC_AUTO_MODE_SUPPORT_MASK;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN0_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_VSYS_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN0_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_VSYS_EN;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN1_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_AD1_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN1_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_AD1_EN;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN2_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_AD2_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN2_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_AD2_EN;
+
+	if (da906x_pdata->flags & DA906X_FLG_FORCE_IN3_MANUAL_MODE)
+		hwmon->adc_auto_en &= ~DA906X_ADC_AUTO_AD3_EN;
+	else if (da906x_pdata->flags & DA906X_FLG_FORCE_IN3_AUTO_MODE)
+		hwmon->adc_auto_en |= DA906X_ADC_AUTO_AD3_EN;
+
+	ret = da906x_reg_update(da906x, DA906X_REG_ADC_CONT,
+				DA906X_ADC_AUTO_MODE_SUPPORT_MASK,
+				hwmon->adc_auto_en);
+	if (ret < 0)
+		return ret;
+
+	hwmon->class_dev = hwmon_device_register(&pdev->dev);
+	if (IS_ERR(hwmon->class_dev))
+		return PTR_ERR(hwmon->class_dev);
+
+	hwmon->irq = platform_get_irq_byname(pdev, DA906X_DRVNAME_HWMON);
+	ret = devm_request_threaded_irq(&pdev->dev, hwmon->irq, NULL,
+					da906x_hwmon_irq_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"HWMON", hwmon);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ.\n");
+		goto err;
+	}
+
+	platform_set_drvdata(pdev, hwmon);
+
+	ret = da906x_reg_read(da906x, DA906X_REG_T_OFFSET);
+	if (ret < 0)
+		dev_warn(&pdev->dev, "Could not read temp1 callibration offset.\n");
+	else
+		hwmon->tjunc_offset = (s8)ret;
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &da906x_attr_group);
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	hwmon_device_unregister(hwmon->class_dev);
+	return ret;
+}
+
+static int __devexit da906x_hwmon_remove(struct platform_device *pdev)
+{
+	struct da906x_hwmon *hwmon = platform_get_drvdata(pdev);
+
+	hwmon_device_unregister(hwmon->class_dev);
+	sysfs_remove_group(&pdev->dev.kobj, &da906x_attr_group);
+
+	return 0;
+}
+
+static struct platform_driver da906x_hwmon_driver = {
+	.probe = da906x_hwmon_probe,
+	.remove = __devexit_p(da906x_hwmon_remove),
+	.driver = {
+		.name = DA906X_DRVNAME_HWMON,
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(da906x_hwmon_driver);
+
+MODULE_DESCRIPTION("DA906X Hardware monitoring");
+MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>, Michal Hajduk <michal.hajduk@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("paltform:" DA906X_DRVNAME_HWMON);
-- 
1.7.0.4


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

  reply	other threads:[~2012-08-24  8:32 UTC|newest]

Thread overview: 60+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-08-24  8:32 [RFC PATCH 0/8] DA906x PMIC driver Krystian Garbaciak
2012-08-24  8:32 ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32 ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Krystian Garbaciak
2012-08-24  8:32   ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32   ` [PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Krystian Garbaciak
2012-08-24  8:32     ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32     ` [PATCH 3/8] rtc: Add RTC driver for DA906x PMIC Krystian Garbaciak
2012-08-24  8:32       ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32       ` Krystian Garbaciak [this message]
2012-08-24  8:32         ` [lm-sensors] [PATCH 4/8] hwmon: Add DA906x hardware monitoring support Krystian Garbaciak
2012-08-24  8:32         ` [PATCH 5/8] input: Add support for DA906x PMIC OnKey detection Krystian Garbaciak
2012-08-24  8:32           ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:45 [RFC PATCH 0/8] DA906x PMIC driver Krystian Garbaciak
2012-08-24 13:45 ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:50 ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Krystian Garbaciak
2012-08-24 13:50   ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:55   ` [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Krystian Garbaciak
2012-08-24 13:55     ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:00     ` [RFC PATCH 3/8] rtc: Add RTC driver for DA906x PMIC Krystian Garbaciak
2012-08-24 14:00       ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:05       ` [RFC PATCH 4/8] hwmon: Add DA906x hardware monitoring support Krystian Garbaciak
2012-08-24 14:05         ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:10         ` [RFC PATCH 5/8] input: Add support for DA906x PMIC OnKey detection Krystian Garbaciak
2012-08-24 14:10           ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:15           ` [RFC PATCH 6/8] input: Add support for DA906x vibration motor driver Krystian Garbaciak
2012-08-24 14:15             ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:20             ` [RFC PATCH 7/8] watchdog: Add DA906x PMIC watchdog driver Krystian Garbaciak
2012-08-24 14:20               ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:25               ` [RFC PATCH 8/8] leds: Add DA906x PMIC LED driver Krystian Garbaciak
2012-08-24 14:25                 ` [lm-sensors] " Krystian Garbaciak
2012-08-24 18:45         ` [RFC PATCH 4/8] hwmon: Add DA906x hardware monitoring support Guenter Roeck
2012-08-24 18:45           ` [lm-sensors] " Guenter Roeck
2012-08-29 13:25           ` [PATCH] regulator: Fix bug in regulator_mode_to_status() core function Krystian Garbaciak
2012-08-29 13:25             ` [lm-sensors] " Krystian Garbaciak
2012-08-25 15:10     ` [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Mark Brown
2012-08-25 15:10       ` [lm-sensors] " Mark Brown
2012-08-29 14:50       ` Krystian Garbaciak
2012-08-29 14:50         ` [lm-sensors] " Krystian Garbaciak
2012-08-29 14:50         ` Krystian Garbaciak
2012-08-30 17:47         ` Mark Brown
2012-08-30 17:47           ` [lm-sensors] " Mark Brown
2012-08-31 10:00           ` Krystian Garbaciak
2012-08-31 10:00             ` [lm-sensors] " Krystian Garbaciak
2012-08-31 10:00             ` Krystian Garbaciak
2013-05-09 14:05             ` Guennadi Liakhovetski
2013-05-09 14:18               ` Anthony Olech
2013-05-09 14:28                 ` Guennadi Liakhovetski
2013-05-09 14:42                   ` Anthony Olech
2013-05-09 14:50                     ` Guennadi Liakhovetski
2012-08-25 18:31   ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Mark Brown
2012-08-25 18:31     ` [lm-sensors] " Mark Brown
2012-08-31 11:20     ` Krystian Garbaciak
2012-08-31 11:20       ` [lm-sensors] " Krystian Garbaciak
2012-08-31 11:20       ` Krystian Garbaciak
2012-08-31 11:37       ` Philippe Rétornaz
2012-08-31 11:37         ` [lm-sensors] " Philippe Rétornaz
2012-08-31 11:37         ` Philippe Rétornaz
2012-08-31 11:37         ` Philippe Rétornaz
2012-08-31 17:16       ` Mark Brown
2012-08-31 17:16         ` [lm-sensors] " Mark Brown

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=201208240834@sw-eng-lt-dc-vm2 \
    --to=krystian.garbaciak@diasemi.com \
    --cc=a.zummo@towertech.it \
    --cc=ashish.jangam@kpitcummins.com \
    --cc=broonie@opensource.wolfsonmicro.com \
    --cc=bryan.wu@canonical.com \
    --cc=dg77.kim@samsung.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=drjones@redhat.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=lm-sensors@lm-sensors.org \
    --cc=lrg@ti.com \
    --cc=rpurdie@rpsys.net \
    --cc=rtc-linux@googlegroups.com \
    --cc=sameo@linux.intel.com \
    --cc=wim@iguana.be \
    /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.