diff mbox

thermal: add generic IIO channel thermal sensor driver

Message ID 1391651007-29813-1-git-send-email-courtney.cavin@sonymobile.com
State Superseded, archived
Headers show

Commit Message

Courtney Cavin Feb. 6, 2014, 1:43 a.m. UTC
This driver is a generic method for using IIO ADC channels as thermal
sensors.

Signed-off-by: Courtney Cavin <courtney.cavin@sonymobile.com>
---
 .../devicetree/bindings/thermal/iio-thermal.txt    |  46 +++++
 drivers/thermal/Kconfig                            |  13 ++
 drivers/thermal/Makefile                           |   1 +
 drivers/thermal/iio_thermal.c                      | 207 +++++++++++++++++++++
 4 files changed, 267 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/iio-thermal.txt
 create mode 100644 drivers/thermal/iio_thermal.c

Comments

Zhang, Rui Feb. 27, 2014, 6:53 a.m. UTC | #1
On Wed, 2014-02-05 at 17:43 -0800, Courtney Cavin wrote:
> This driver is a generic method for using IIO ADC channels as thermal
> sensors.
> 
> Signed-off-by: Courtney Cavin <courtney.cavin@sonymobile.com>

Eduardo,

what do you think of this patch?

thanks,
rui
> ---
>  .../devicetree/bindings/thermal/iio-thermal.txt    |  46 +++++
>  drivers/thermal/Kconfig                            |  13 ++
>  drivers/thermal/Makefile                           |   1 +
>  drivers/thermal/iio_thermal.c                      | 207 +++++++++++++++++++++
>  4 files changed, 267 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/thermal/iio-thermal.txt
>  create mode 100644 drivers/thermal/iio_thermal.c
> 
> diff --git a/Documentation/devicetree/bindings/thermal/iio-thermal.txt b/Documentation/devicetree/bindings/thermal/iio-thermal.txt
> new file mode 100644
> index 0000000..3be11b6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/iio-thermal.txt
> @@ -0,0 +1,46 @@
> +Generic IIO channel thermal sensor bindings
> +
> +compatible:
> +	Usage: required
> +	Type: string
> +	Desc: compatible string, must be "iio-thermal"
> +
> +conversion-method:
> +	Usage: required
> +	Type: string
> +	Desc: How to convert IIO voltage values to temperature, one of:
> +		"interpolation" - interpolate between values in lookup table
> +		"scalar" - use values as multiplier and divisor
> +
> +conversion-values:
> +	Usage: required
> +	Type: u32 array, 2-tuples
> +	Desc: lookup table for conversion, for conversion-method:
> +		"interpolation" - 2-tuples of < uV mK >; micro-volts to
> +				  milli-kelvin; table must ascend
> +		"scalar" - single scalar 2-tuple as < M D >; where:
> +			   mK = uV * M / D
> +
> +io-channels:
> +	Usage: required
> +	Type: prop-encoded-array
> +	Desc: See bindings/iio/iio-bindings.txt; must be a voltage channel
> +
> +Example:
> +
> +vadc: some_vadc {
> +	compatible = "...";
> +	#io-channel-cells = <1>;
> +};
> +
> +iio-thermal {
> +	compatible = "iio-thermal";
> +	io-channels = <&vadc 0>;
> +	conversion-method = "interpolation";
> +	conversion-values = <
> +		  30000 398200
> +		 385000 318200
> +		1738000 233200
> +	>;
> +};
> +
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 35c0664..f83a8e8 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -114,6 +114,19 @@ config THERMAL_EMULATION
>  	  because userland can easily disable the thermal policy by simply
>  	  flooding this sysfs node with low temperature values.
>  
> +config IIO_THERMAL
> +	tristate "Temperature sensor driver for generic IIO channels"
> +	depends on IIO
> +	depends on THERMAL_OF
> +	help
> +	  Support for generic IIO channels, such as ADCs.  This driver allows
> +	  you to expose an IIO voltage channel as a thermal sensor.  This is
> +	  implemented as a thermal sensor, not a thermal zone, and thus
> +	  requires DT defined thermal infrastructure in order to be useful.
> +
> +	  If you aren't sure that you need this support, or haven't configured
> +	  a thermal infrastructure in device tree, you should say 'N' here.
> +
>  config IMX_THERMAL
>  	tristate "Temperature sensor driver for Freescale i.MX SoCs"
>  	depends on CPU_THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index 54e4ec9..0ee2c92 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -25,6 +25,7 @@ obj-y				+= samsung/
>  obj-$(CONFIG_DOVE_THERMAL)  	+= dove_thermal.o
>  obj-$(CONFIG_DB8500_THERMAL)	+= db8500_thermal.o
>  obj-$(CONFIG_ARMADA_THERMAL)	+= armada_thermal.o
> +obj-$(CONFIG_IIO_THERMAL)	+= iio_thermal.o
>  obj-$(CONFIG_IMX_THERMAL)	+= imx_thermal.o
>  obj-$(CONFIG_DB8500_CPUFREQ_COOLING)	+= db8500_cpufreq_cooling.o
>  obj-$(CONFIG_INTEL_POWERCLAMP)	+= intel_powerclamp.o
> diff --git a/drivers/thermal/iio_thermal.c b/drivers/thermal/iio_thermal.c
> new file mode 100644
> index 0000000..df21dbc
> --- /dev/null
> +++ b/drivers/thermal/iio_thermal.c
> @@ -0,0 +1,207 @@
> +/*
> + * An IIO channel based thermal sensor driver
> + *
> + * Copyright (C) 2014 Sony Mobile Communications, AB
> + *
> + * 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; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/consumer.h>
> +#include <linux/thermal.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +
> +#define MILLIKELVIN_0C 273150
> +
> +struct iio_thermal_conv {
> +	s32 *values;
> +	int ntuple;
> +	long (*convert)(const struct iio_thermal_conv *, int val);
> +};
> +
> +struct iio_thermal {
> +	struct thermal_zone_device *tz;
> +	struct iio_channel *chan;
> +	struct iio_thermal_conv conv;
> +};
> +
> +static int iio_bsearch(const struct iio_thermal_conv *conv, s32 input)
> +{
> +	int h = conv->ntuple - 1;
> +	int l = 0;
> +
> +	while (h - 1 > l) {
> +		int m = (l + h) & ~1;
> +		s32 v = conv->values[m];
> +		if (v < input)
> +			l = m >> 1;
> +		else
> +			h = m >> 1;
> +	}
> +
> +	return h;
> +}
> +
> +static long iio_thermal_interpolate(const struct iio_thermal_conv *conv, int v)
> +{
> +	int n;
> +
> +	n = iio_bsearch(conv, v);
> +	if (n == 0 || n == conv->ntuple) {
> +		BUG();
> +	} else {
> +		s32 *pts = &conv->values[(n-1)*2];
> +		s32 sx = pts[3] - pts[1];
> +		s32 sy = pts[2] - pts[0];
> +		return sx * (v - pts[0]) / sy + pts[1];
> +	}
> +}
> +
> +static long iio_thermal_scale(const struct iio_thermal_conv *conv, int v)
> +{
> +	return div_s64((s64)v * conv->values[0], conv->values[1]);
> +}
> +
> +static int iio_thermal_get_temp(void *pdata, long *value)
> +{
> +	struct iio_thermal *iio = pdata;
> +	long temp;
> +	int val;
> +	int rc;
> +
> +	rc = iio_read_channel_processed(iio->chan, &val);
> +	if (rc)
> +		return rc;
> +
> +	temp = iio->conv.convert(&iio->conv, val);
> +
> +	/* thermal core wants milli-celsius; we deal in milli-kelvin */
> +	temp = temp - MILLIKELVIN_0C;
> +
> +	/*
> +	 * Although it would appear that the temperature value here is a
> +	 * signed value for sensors, the underlying thermal zone core
> +	 * doesn't deal with negative temperature. Cut this value off at 0C.
> +	 */
> +	*value = (temp < 0) ? 0 : temp;
> +
> +	return 0;
> +}
> +
> +static int iio_thermal_probe(struct platform_device *pdev)
> +{
> +	struct iio_thermal *iio;
> +	const void *values;
> +	const char *type;
> +	int rc;
> +
> +	dev_info(&pdev->dev, "registering IIO thermal device\n");
> +	iio = devm_kzalloc(&pdev->dev, sizeof(*iio), GFP_KERNEL);
> +	if (!iio)
> +		return -ENOMEM;
> +
> +	values = of_get_property(pdev->dev.of_node,
> +			"conversion-values", &iio->conv.ntuple);
> +	if (values == NULL || (iio->conv.ntuple % (sizeof(u32) * 2)) != 0) {
> +		dev_err(&pdev->dev, "invalid/missing conversion values\n");
> +		return -EINVAL;
> +	}
> +	iio->conv.ntuple /= sizeof(u32) * 2;
> +	iio->conv.values = devm_kzalloc(&pdev->dev,
> +			sizeof(u32) * 2 * iio->conv.ntuple, GFP_KERNEL);
> +	if (!iio->conv.values)
> +		return -ENOMEM;
> +
> +	rc = of_property_read_u32_array(pdev->dev.of_node,
> +			"conversion-values", (u32 *)iio->conv.values,
> +			iio->conv.ntuple * 2);
> +	if (rc) {
> +		dev_err(&pdev->dev, "invalid/missing conversion values\n");
> +		return -EINVAL;
> +	}
> +
> +	rc = of_property_read_string(pdev->dev.of_node,
> +			"conversion-method", &type);
> +	if (rc) {
> +		dev_err(&pdev->dev, "invalid/missing conversion method\n");
> +		return rc;
> +	}
> +	if (!strcmp(type, "interpolation") && iio->conv.ntuple > 1) {
> +		if (iio->conv.values[0] > iio->conv.values[2]) {
> +			dev_err(&pdev->dev,
> +					"conversion values should ascend\n");
> +			return rc;
> +		}
> +		iio->conv.convert = iio_thermal_interpolate;
> +	} else if (!strcmp(type, "scalar") && iio->conv.ntuple == 1) {
> +		iio->conv.convert = iio_thermal_scale;
> +	} else {
> +		dev_err(&pdev->dev, "invalid conversion method for values\n");
> +		return rc;
> +	}
> +
> +	iio->chan = iio_channel_get(&pdev->dev, NULL);
> +	if (IS_ERR(iio->chan)) {
> +		dev_err(&pdev->dev, "invalid/missing iio channel\n");
> +		return PTR_ERR(iio->chan);
> +	}
> +	if (iio->chan->channel->type != IIO_VOLTAGE) {
> +		dev_err(&pdev->dev, "specified iio channel is not voltage\n");
> +		iio_channel_release(iio->chan);
> +		return -EINVAL;
> +	}
> +	platform_set_drvdata(pdev, iio);
> +
> +	iio->tz = thermal_zone_of_sensor_register(&pdev->dev, 0, iio,
> +			iio_thermal_get_temp, NULL);
> +	if (IS_ERR(iio->tz)) {
> +		dev_err(&pdev->dev, "failed to register thermal sensor\n");
> +		iio_channel_release(iio->chan);
> +		return PTR_ERR(iio->tz);
> +	}
> +	dev_info(&pdev->dev, "successfully registered IIO thermal sensor\n");
> +
> +	return 0;
> +}
> +
> +static int iio_thermal_remove(struct platform_device *pdev)
> +{
> +	struct iio_thermal *iio;
> +
> +	iio = platform_get_drvdata(pdev);
> +
> +	thermal_zone_of_sensor_unregister(&pdev->dev, iio->tz);
> +	iio_channel_release(iio->chan);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id iio_thermal_match[] = {
> +	{ .compatible = "iio-thermal", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, iio_thermal_match);
> +
> +static struct platform_driver iio_thermal = {
> +	.driver = {
> +		.name = "iio-thermal",
> +		.owner = THIS_MODULE,
> +		.of_match_table = iio_thermal_match,
> +	},
> +	.probe = iio_thermal_probe,
> +	.remove = iio_thermal_remove,
> +};
> +module_platform_driver(iio_thermal);
> +
> +MODULE_DESCRIPTION("Thermal driver for IIO ADCs");
> +MODULE_LICENSE("GPL v2");


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Courtney Cavin March 14, 2014, 9:19 p.m. UTC | #2
On Thu, Feb 27, 2014 at 07:53:34AM +0100, Zhang Rui wrote:
> On Wed, 2014-02-05 at 17:43 -0800, Courtney Cavin wrote:
> > This driver is a generic method for using IIO ADC channels as thermal
> > sensors.
> > 
> > Signed-off-by: Courtney Cavin <courtney.cavin@sonymobile.com>
> 
> Eduardo,
> 
> what do you think of this patch?

Apparently not very much.  Oh well.

Sans further input, I will submit a new series with the following
changes:
	- Fix some return values in probe().
	- Change DT binding
		- Submit as seperate patch in series
		- Reword/rename to represent a thermistor
		- Remove specific references to IIO, where appropriate

-Courtney
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eduardo Valentin Aug. 28, 2014, 2:46 p.m. UTC | #3
On Fri, Mar 14, 2014 at 02:19:53PM -0700, Courtney Cavin wrote:
> On Thu, Feb 27, 2014 at 07:53:34AM +0100, Zhang Rui wrote:
> > On Wed, 2014-02-05 at 17:43 -0800, Courtney Cavin wrote:
> > > This driver is a generic method for using IIO ADC channels as thermal
> > > sensors.
> > > 
> > > Signed-off-by: Courtney Cavin <courtney.cavin@sonymobile.com>
> > 
> > Eduardo,
> > 
> > what do you think of this patch?
> 
> Apparently not very much.  Oh well.

Curtney,

Apolizes, this one fell into the cracks.


> 
> Sans further input, I will submit a new series with the following
> changes:
> 	- Fix some return values in probe().
> 	- Change DT binding
> 		- Submit as seperate patch in series
> 		- Reword/rename to represent a thermistor
> 		- Remove specific references to IIO, where appropriate


Do you already send your new version or the current one is still the
latest?

> 
> -Courtney
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eduardo Valentin Sept. 8, 2014, 11:43 a.m. UTC | #4
Hello,


Sorry for the waaay too long answer. In any case, I believe it is worth
giving answers to why this patch was not merged.

On Wed, Feb 05, 2014 at 05:43:27PM -0800, Courtney Cavin wrote:
> This driver is a generic method for using IIO ADC channels as thermal
> sensors.

In fact, this seams to be a way of getting DT descriptors, introduced in
this patches, and register them both in IIO layer and in thermal
framework, right?

My expectation would be: once it gets registered as IIO device, then it
would get registered as thermal zone too.

> 
> Signed-off-by: Courtney Cavin <courtney.cavin@sonymobile.com>
> ---
>  .../devicetree/bindings/thermal/iio-thermal.txt    |  46 +++++
>  drivers/thermal/Kconfig                            |  13 ++
>  drivers/thermal/Makefile                           |   1 +
>  drivers/thermal/iio_thermal.c                      | 207 +++++++++++++++++++++
>  4 files changed, 267 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/thermal/iio-thermal.txt
>  create mode 100644 drivers/thermal/iio_thermal.c
> 
> diff --git a/Documentation/devicetree/bindings/thermal/iio-thermal.txt b/Documentation/devicetree/bindings/thermal/iio-thermal.txt
> new file mode 100644
> index 0000000..3be11b6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/iio-thermal.txt
> @@ -0,0 +1,46 @@
> +Generic IIO channel thermal sensor bindings
> +
> +compatible:
> +	Usage: required
> +	Type: string
> +	Desc: compatible string, must be "iio-thermal"
> +
> +conversion-method:
> +	Usage: required
> +	Type: string
> +	Desc: How to convert IIO voltage values to temperature, one of:
> +		"interpolation" - interpolate between values in lookup table
> +		"scalar" - use values as multiplier and divisor
> +
> +conversion-values:
> +	Usage: required
> +	Type: u32 array, 2-tuples
> +	Desc: lookup table for conversion, for conversion-method:
> +		"interpolation" - 2-tuples of < uV mK >; micro-volts to
> +				  milli-kelvin; table must ascend
> +		"scalar" - single scalar 2-tuple as < M D >; where:
> +			   mK = uV * M / D
> +
> +io-channels:
> +	Usage: required
> +	Type: prop-encoded-array
> +	Desc: See bindings/iio/iio-bindings.txt; must be a voltage channel
> +
> +Example:
> +
> +vadc: some_vadc {
> +	compatible = "...";
> +	#io-channel-cells = <1>;
> +};
> +
> +iio-thermal {
> +	compatible = "iio-thermal";
> +	io-channels = <&vadc 0>;
> +	conversion-method = "interpolation";
> +	conversion-values = <
> +		  30000 398200
> +		 385000 318200
> +		1738000 233200
> +	>;
> +};

The problem I have with the above is the fact that it is an IIO
descriptor, that in turns gets reused to become a thermal zone. The
approach leaves the thermal zones with no thermal constraint described. 

What are the trips? and the cooling maps?

> +
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 35c0664..f83a8e8 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -114,6 +114,19 @@ config THERMAL_EMULATION
>  	  because userland can easily disable the thermal policy by simply
>  	  flooding this sysfs node with low temperature values.
>  
> +config IIO_THERMAL
> +	tristate "Temperature sensor driver for generic IIO channels"
> +	depends on IIO
> +	depends on THERMAL_OF
> +	help
> +	  Support for generic IIO channels, such as ADCs.  This driver allows
> +	  you to expose an IIO voltage channel as a thermal sensor.  This is
> +	  implemented as a thermal sensor, not a thermal zone, and thus
> +	  requires DT defined thermal infrastructure in order to be useful.
> +
> +	  If you aren't sure that you need this support, or haven't configured
> +	  a thermal infrastructure in device tree, you should say 'N' here.
> +
>  config IMX_THERMAL
>  	tristate "Temperature sensor driver for Freescale i.MX SoCs"
>  	depends on CPU_THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index 54e4ec9..0ee2c92 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -25,6 +25,7 @@ obj-y				+= samsung/
>  obj-$(CONFIG_DOVE_THERMAL)  	+= dove_thermal.o
>  obj-$(CONFIG_DB8500_THERMAL)	+= db8500_thermal.o
>  obj-$(CONFIG_ARMADA_THERMAL)	+= armada_thermal.o
> +obj-$(CONFIG_IIO_THERMAL)	+= iio_thermal.o
>  obj-$(CONFIG_IMX_THERMAL)	+= imx_thermal.o
>  obj-$(CONFIG_DB8500_CPUFREQ_COOLING)	+= db8500_cpufreq_cooling.o
>  obj-$(CONFIG_INTEL_POWERCLAMP)	+= intel_powerclamp.o
> diff --git a/drivers/thermal/iio_thermal.c b/drivers/thermal/iio_thermal.c
> new file mode 100644
> index 0000000..df21dbc
> --- /dev/null
> +++ b/drivers/thermal/iio_thermal.c
> @@ -0,0 +1,207 @@
> +/*
> + * An IIO channel based thermal sensor driver
> + *
> + * Copyright (C) 2014 Sony Mobile Communications, AB
> + *
> + * 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; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/consumer.h>
> +#include <linux/thermal.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +
> +#define MILLIKELVIN_0C 273150
> +
> +struct iio_thermal_conv {
> +	s32 *values;
> +	int ntuple;
> +	long (*convert)(const struct iio_thermal_conv *, int val);
> +};
> +
> +struct iio_thermal {
> +	struct thermal_zone_device *tz;
> +	struct iio_channel *chan;
> +	struct iio_thermal_conv conv;
> +};
> +
> +static int iio_bsearch(const struct iio_thermal_conv *conv, s32 input)
> +{
> +	int h = conv->ntuple - 1;
> +	int l = 0;
> +
> +	while (h - 1 > l) {
> +		int m = (l + h) & ~1;
> +		s32 v = conv->values[m];
> +		if (v < input)
> +			l = m >> 1;
> +		else
> +			h = m >> 1;
> +	}
> +
> +	return h;
> +}
> +
> +static long iio_thermal_interpolate(const struct iio_thermal_conv *conv, int v)
> +{
> +	int n;
> +
> +	n = iio_bsearch(conv, v);
> +	if (n == 0 || n == conv->ntuple) {
> +		BUG();
> +	} else {
> +		s32 *pts = &conv->values[(n-1)*2];
> +		s32 sx = pts[3] - pts[1];
> +		s32 sy = pts[2] - pts[0];
> +		return sx * (v - pts[0]) / sy + pts[1];
> +	}
> +}
> +
> +static long iio_thermal_scale(const struct iio_thermal_conv *conv, int v)
> +{
> +	return div_s64((s64)v * conv->values[0], conv->values[1]);
> +}
> +
> +static int iio_thermal_get_temp(void *pdata, long *value)
> +{
> +	struct iio_thermal *iio = pdata;
> +	long temp;
> +	int val;
> +	int rc;
> +
> +	rc = iio_read_channel_processed(iio->chan, &val);
> +	if (rc)
> +		return rc;
> +
> +	temp = iio->conv.convert(&iio->conv, val);
> +
> +	/* thermal core wants milli-celsius; we deal in milli-kelvin */
> +	temp = temp - MILLIKELVIN_0C;
> +
> +	/*
> +	 * Although it would appear that the temperature value here is a
> +	 * signed value for sensors, the underlying thermal zone core
> +	 * doesn't deal with negative temperature. Cut this value off at 0C.
> +	 */
> +	*value = (temp < 0) ? 0 : temp;
> +
> +	return 0;
> +}
> +
> +static int iio_thermal_probe(struct platform_device *pdev)
> +{
> +	struct iio_thermal *iio;
> +	const void *values;
> +	const char *type;
> +	int rc;
> +
> +	dev_info(&pdev->dev, "registering IIO thermal device\n");
> +	iio = devm_kzalloc(&pdev->dev, sizeof(*iio), GFP_KERNEL);
> +	if (!iio)
> +		return -ENOMEM;
> +
> +	values = of_get_property(pdev->dev.of_node,
> +			"conversion-values", &iio->conv.ntuple);
> +	if (values == NULL || (iio->conv.ntuple % (sizeof(u32) * 2)) != 0) {
> +		dev_err(&pdev->dev, "invalid/missing conversion values\n");
> +		return -EINVAL;
> +	}
> +	iio->conv.ntuple /= sizeof(u32) * 2;
> +	iio->conv.values = devm_kzalloc(&pdev->dev,
> +			sizeof(u32) * 2 * iio->conv.ntuple, GFP_KERNEL);
> +	if (!iio->conv.values)
> +		return -ENOMEM;
> +
> +	rc = of_property_read_u32_array(pdev->dev.of_node,
> +			"conversion-values", (u32 *)iio->conv.values,
> +			iio->conv.ntuple * 2);
> +	if (rc) {
> +		dev_err(&pdev->dev, "invalid/missing conversion values\n");
> +		return -EINVAL;
> +	}
> +
> +	rc = of_property_read_string(pdev->dev.of_node,
> +			"conversion-method", &type);
> +	if (rc) {
> +		dev_err(&pdev->dev, "invalid/missing conversion method\n");
> +		return rc;
> +	}
> +	if (!strcmp(type, "interpolation") && iio->conv.ntuple > 1) {
> +		if (iio->conv.values[0] > iio->conv.values[2]) {
> +			dev_err(&pdev->dev,
> +					"conversion values should ascend\n");
> +			return rc;
> +		}
> +		iio->conv.convert = iio_thermal_interpolate;
> +	} else if (!strcmp(type, "scalar") && iio->conv.ntuple == 1) {
> +		iio->conv.convert = iio_thermal_scale;
> +	} else {
> +		dev_err(&pdev->dev, "invalid conversion method for values\n");
> +		return rc;
> +	}
> +
> +	iio->chan = iio_channel_get(&pdev->dev, NULL);
> +	if (IS_ERR(iio->chan)) {
> +		dev_err(&pdev->dev, "invalid/missing iio channel\n");
> +		return PTR_ERR(iio->chan);
> +	}
> +	if (iio->chan->channel->type != IIO_VOLTAGE) {
> +		dev_err(&pdev->dev, "specified iio channel is not voltage\n");
> +		iio_channel_release(iio->chan);
> +		return -EINVAL;
> +	}
> +	platform_set_drvdata(pdev, iio);
> +
> +	iio->tz = thermal_zone_of_sensor_register(&pdev->dev, 0, iio,
> +			iio_thermal_get_temp, NULL);
> +	if (IS_ERR(iio->tz)) {
> +		dev_err(&pdev->dev, "failed to register thermal sensor\n");
> +		iio_channel_release(iio->chan);
> +		return PTR_ERR(iio->tz);
> +	}
> +	dev_info(&pdev->dev, "successfully registered IIO thermal sensor\n");
> +
> +	return 0;
> +}
> +
> +static int iio_thermal_remove(struct platform_device *pdev)
> +{
> +	struct iio_thermal *iio;
> +
> +	iio = platform_get_drvdata(pdev);
> +
> +	thermal_zone_of_sensor_unregister(&pdev->dev, iio->tz);
> +	iio_channel_release(iio->chan);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id iio_thermal_match[] = {
> +	{ .compatible = "iio-thermal", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, iio_thermal_match);
> +
> +static struct platform_driver iio_thermal = {
> +	.driver = {
> +		.name = "iio-thermal",
> +		.owner = THIS_MODULE,
> +		.of_match_table = iio_thermal_match,
> +	},
> +	.probe = iio_thermal_probe,
> +	.remove = iio_thermal_remove,
> +};
> +module_platform_driver(iio_thermal);
> +
> +MODULE_DESCRIPTION("Thermal driver for IIO ADCs");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.8.1.5

Please give scripts/checkpatch.pl a try on this patch.

> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/thermal/iio-thermal.txt b/Documentation/devicetree/bindings/thermal/iio-thermal.txt
new file mode 100644
index 0000000..3be11b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/iio-thermal.txt
@@ -0,0 +1,46 @@ 
+Generic IIO channel thermal sensor bindings
+
+compatible:
+	Usage: required
+	Type: string
+	Desc: compatible string, must be "iio-thermal"
+
+conversion-method:
+	Usage: required
+	Type: string
+	Desc: How to convert IIO voltage values to temperature, one of:
+		"interpolation" - interpolate between values in lookup table
+		"scalar" - use values as multiplier and divisor
+
+conversion-values:
+	Usage: required
+	Type: u32 array, 2-tuples
+	Desc: lookup table for conversion, for conversion-method:
+		"interpolation" - 2-tuples of < uV mK >; micro-volts to
+				  milli-kelvin; table must ascend
+		"scalar" - single scalar 2-tuple as < M D >; where:
+			   mK = uV * M / D
+
+io-channels:
+	Usage: required
+	Type: prop-encoded-array
+	Desc: See bindings/iio/iio-bindings.txt; must be a voltage channel
+
+Example:
+
+vadc: some_vadc {
+	compatible = "...";
+	#io-channel-cells = <1>;
+};
+
+iio-thermal {
+	compatible = "iio-thermal";
+	io-channels = <&vadc 0>;
+	conversion-method = "interpolation";
+	conversion-values = <
+		  30000 398200
+		 385000 318200
+		1738000 233200
+	>;
+};
+
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 35c0664..f83a8e8 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -114,6 +114,19 @@  config THERMAL_EMULATION
 	  because userland can easily disable the thermal policy by simply
 	  flooding this sysfs node with low temperature values.
 
+config IIO_THERMAL
+	tristate "Temperature sensor driver for generic IIO channels"
+	depends on IIO
+	depends on THERMAL_OF
+	help
+	  Support for generic IIO channels, such as ADCs.  This driver allows
+	  you to expose an IIO voltage channel as a thermal sensor.  This is
+	  implemented as a thermal sensor, not a thermal zone, and thus
+	  requires DT defined thermal infrastructure in order to be useful.
+
+	  If you aren't sure that you need this support, or haven't configured
+	  a thermal infrastructure in device tree, you should say 'N' here.
+
 config IMX_THERMAL
 	tristate "Temperature sensor driver for Freescale i.MX SoCs"
 	depends on CPU_THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 54e4ec9..0ee2c92 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -25,6 +25,7 @@  obj-y				+= samsung/
 obj-$(CONFIG_DOVE_THERMAL)  	+= dove_thermal.o
 obj-$(CONFIG_DB8500_THERMAL)	+= db8500_thermal.o
 obj-$(CONFIG_ARMADA_THERMAL)	+= armada_thermal.o
+obj-$(CONFIG_IIO_THERMAL)	+= iio_thermal.o
 obj-$(CONFIG_IMX_THERMAL)	+= imx_thermal.o
 obj-$(CONFIG_DB8500_CPUFREQ_COOLING)	+= db8500_cpufreq_cooling.o
 obj-$(CONFIG_INTEL_POWERCLAMP)	+= intel_powerclamp.o
diff --git a/drivers/thermal/iio_thermal.c b/drivers/thermal/iio_thermal.c
new file mode 100644
index 0000000..df21dbc
--- /dev/null
+++ b/drivers/thermal/iio_thermal.c
@@ -0,0 +1,207 @@ 
+/*
+ * An IIO channel based thermal sensor driver
+ *
+ * Copyright (C) 2014 Sony Mobile Communications, AB
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/consumer.h>
+#include <linux/thermal.h>
+#include <linux/err.h>
+#include <linux/of.h>
+
+#define MILLIKELVIN_0C 273150
+
+struct iio_thermal_conv {
+	s32 *values;
+	int ntuple;
+	long (*convert)(const struct iio_thermal_conv *, int val);
+};
+
+struct iio_thermal {
+	struct thermal_zone_device *tz;
+	struct iio_channel *chan;
+	struct iio_thermal_conv conv;
+};
+
+static int iio_bsearch(const struct iio_thermal_conv *conv, s32 input)
+{
+	int h = conv->ntuple - 1;
+	int l = 0;
+
+	while (h - 1 > l) {
+		int m = (l + h) & ~1;
+		s32 v = conv->values[m];
+		if (v < input)
+			l = m >> 1;
+		else
+			h = m >> 1;
+	}
+
+	return h;
+}
+
+static long iio_thermal_interpolate(const struct iio_thermal_conv *conv, int v)
+{
+	int n;
+
+	n = iio_bsearch(conv, v);
+	if (n == 0 || n == conv->ntuple) {
+		BUG();
+	} else {
+		s32 *pts = &conv->values[(n-1)*2];
+		s32 sx = pts[3] - pts[1];
+		s32 sy = pts[2] - pts[0];
+		return sx * (v - pts[0]) / sy + pts[1];
+	}
+}
+
+static long iio_thermal_scale(const struct iio_thermal_conv *conv, int v)
+{
+	return div_s64((s64)v * conv->values[0], conv->values[1]);
+}
+
+static int iio_thermal_get_temp(void *pdata, long *value)
+{
+	struct iio_thermal *iio = pdata;
+	long temp;
+	int val;
+	int rc;
+
+	rc = iio_read_channel_processed(iio->chan, &val);
+	if (rc)
+		return rc;
+
+	temp = iio->conv.convert(&iio->conv, val);
+
+	/* thermal core wants milli-celsius; we deal in milli-kelvin */
+	temp = temp - MILLIKELVIN_0C;
+
+	/*
+	 * Although it would appear that the temperature value here is a
+	 * signed value for sensors, the underlying thermal zone core
+	 * doesn't deal with negative temperature. Cut this value off at 0C.
+	 */
+	*value = (temp < 0) ? 0 : temp;
+
+	return 0;
+}
+
+static int iio_thermal_probe(struct platform_device *pdev)
+{
+	struct iio_thermal *iio;
+	const void *values;
+	const char *type;
+	int rc;
+
+	dev_info(&pdev->dev, "registering IIO thermal device\n");
+	iio = devm_kzalloc(&pdev->dev, sizeof(*iio), GFP_KERNEL);
+	if (!iio)
+		return -ENOMEM;
+
+	values = of_get_property(pdev->dev.of_node,
+			"conversion-values", &iio->conv.ntuple);
+	if (values == NULL || (iio->conv.ntuple % (sizeof(u32) * 2)) != 0) {
+		dev_err(&pdev->dev, "invalid/missing conversion values\n");
+		return -EINVAL;
+	}
+	iio->conv.ntuple /= sizeof(u32) * 2;
+	iio->conv.values = devm_kzalloc(&pdev->dev,
+			sizeof(u32) * 2 * iio->conv.ntuple, GFP_KERNEL);
+	if (!iio->conv.values)
+		return -ENOMEM;
+
+	rc = of_property_read_u32_array(pdev->dev.of_node,
+			"conversion-values", (u32 *)iio->conv.values,
+			iio->conv.ntuple * 2);
+	if (rc) {
+		dev_err(&pdev->dev, "invalid/missing conversion values\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_string(pdev->dev.of_node,
+			"conversion-method", &type);
+	if (rc) {
+		dev_err(&pdev->dev, "invalid/missing conversion method\n");
+		return rc;
+	}
+	if (!strcmp(type, "interpolation") && iio->conv.ntuple > 1) {
+		if (iio->conv.values[0] > iio->conv.values[2]) {
+			dev_err(&pdev->dev,
+					"conversion values should ascend\n");
+			return rc;
+		}
+		iio->conv.convert = iio_thermal_interpolate;
+	} else if (!strcmp(type, "scalar") && iio->conv.ntuple == 1) {
+		iio->conv.convert = iio_thermal_scale;
+	} else {
+		dev_err(&pdev->dev, "invalid conversion method for values\n");
+		return rc;
+	}
+
+	iio->chan = iio_channel_get(&pdev->dev, NULL);
+	if (IS_ERR(iio->chan)) {
+		dev_err(&pdev->dev, "invalid/missing iio channel\n");
+		return PTR_ERR(iio->chan);
+	}
+	if (iio->chan->channel->type != IIO_VOLTAGE) {
+		dev_err(&pdev->dev, "specified iio channel is not voltage\n");
+		iio_channel_release(iio->chan);
+		return -EINVAL;
+	}
+	platform_set_drvdata(pdev, iio);
+
+	iio->tz = thermal_zone_of_sensor_register(&pdev->dev, 0, iio,
+			iio_thermal_get_temp, NULL);
+	if (IS_ERR(iio->tz)) {
+		dev_err(&pdev->dev, "failed to register thermal sensor\n");
+		iio_channel_release(iio->chan);
+		return PTR_ERR(iio->tz);
+	}
+	dev_info(&pdev->dev, "successfully registered IIO thermal sensor\n");
+
+	return 0;
+}
+
+static int iio_thermal_remove(struct platform_device *pdev)
+{
+	struct iio_thermal *iio;
+
+	iio = platform_get_drvdata(pdev);
+
+	thermal_zone_of_sensor_unregister(&pdev->dev, iio->tz);
+	iio_channel_release(iio->chan);
+
+	return 0;
+}
+
+static const struct of_device_id iio_thermal_match[] = {
+	{ .compatible = "iio-thermal", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, iio_thermal_match);
+
+static struct platform_driver iio_thermal = {
+	.driver = {
+		.name = "iio-thermal",
+		.owner = THIS_MODULE,
+		.of_match_table = iio_thermal_match,
+	},
+	.probe = iio_thermal_probe,
+	.remove = iio_thermal_remove,
+};
+module_platform_driver(iio_thermal);
+
+MODULE_DESCRIPTION("Thermal driver for IIO ADCs");
+MODULE_LICENSE("GPL v2");