aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaxman Dewangan <ldewangan@nvidia.com>2016-04-19 03:22:01 -0400
committerEduardo Valentin <edubezval@gmail.com>2016-05-17 10:28:31 -0400
commitb3aef78f76959b94e6df54f80040669a11cc4897 (patch)
treecaa7c6d3db2f0ff71e70697ecad0572f934313e9
parent9e389e383b30c5d63de67e7a0cfa39c527a98bbc (diff)
thermal: generic-adc: Add ADC based thermal sensor driver
In some of platform, thermal sensors like NCT thermistors are connected to the one of ADC channel. The temperature is read by reading the voltage across the sensor resistance via ADC. Lookup table for ADC read value to temperature is referred to get temperature. ADC is read via IIO framework. Add support for thermal sensor driver which read the voltage across sensor resistance from ADC through IIO framework. Acked-by: Jonathan Cameron <jic23@kernel.org> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com> Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
-rw-r--r--drivers/thermal/Kconfig10
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/thermal-generic-adc.c182
3 files changed, 193 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index ee02e6021c94..4166c10ba314 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -411,4 +411,14 @@ config QCOM_SPMI_TEMP_ALARM
411 real time die temperature if an ADC is present or an estimate of the 411 real time die temperature if an ADC is present or an estimate of the
412 temperature based upon the over temperature stage value. 412 temperature based upon the over temperature stage value.
413 413
414config GENERIC_ADC_THERMAL
415 tristate "Generic ADC based thermal sensor"
416 depends on IIO
417 help
418 This enabled a thermal sysfs driver for the temperature sensor
419 which is connected to the General Purpose ADC. The ADC channel
420 is read via IIO framework and the channel information is provided
421 to this driver. This driver reports the temperature by reading ADC
422 channel and converts it to temperature based on lookup table.
423
414endif 424endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 998134ff3b45..10b07c14f8a9 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -50,3 +50,4 @@ obj-$(CONFIG_ST_THERMAL) += st/
50obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ 50obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/
51obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o 51obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
52obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o 52obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
53obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
new file mode 100644
index 000000000000..73f55d6a1721
--- /dev/null
+++ b/drivers/thermal/thermal-generic-adc.c
@@ -0,0 +1,182 @@
1/*
2 * Generic ADC thermal driver
3 *
4 * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
5 *
6 * Author: Laxman Dewangan <ldewangan@nvidia.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 */
12#include <linux/iio/consumer.h>
13#include <linux/kernel.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/slab.h>
17#include <linux/thermal.h>
18
19struct gadc_thermal_info {
20 struct device *dev;
21 struct thermal_zone_device *tz_dev;
22 struct iio_channel *channel;
23 s32 *lookup_table;
24 int nlookup_table;
25};
26
27static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val)
28{
29 int temp, adc_hi, adc_lo;
30 int i;
31
32 for (i = 0; i < gti->nlookup_table; i++) {
33 if (val >= gti->lookup_table[2 * i + 1])
34 break;
35 }
36
37 if (i == 0) {
38 temp = gti->lookup_table[0];
39 } else if (i >= (gti->nlookup_table - 1)) {
40 temp = gti->lookup_table[2 * (gti->nlookup_table - 1)];
41 } else {
42 adc_hi = gti->lookup_table[2 * i - 1];
43 adc_lo = gti->lookup_table[2 * i + 1];
44 temp = gti->lookup_table[2 * i];
45 temp -= ((val - adc_lo) * 1000) / (adc_hi - adc_lo);
46 }
47
48 return temp;
49}
50
51static int gadc_thermal_get_temp(void *data, int *temp)
52{
53 struct gadc_thermal_info *gti = data;
54 int val;
55 int ret;
56
57 ret = iio_read_channel_processed(gti->channel, &val);
58 if (ret < 0) {
59 dev_err(gti->dev, "IIO channel read failed %d\n", ret);
60 return ret;
61 }
62 *temp = gadc_thermal_adc_to_temp(gti, val);
63
64 return 0;
65}
66
67static const struct thermal_zone_of_device_ops gadc_thermal_ops = {
68 .get_temp = gadc_thermal_get_temp,
69};
70
71static int gadc_thermal_read_linear_lookup_table(struct device *dev,
72 struct gadc_thermal_info *gti)
73{
74 struct device_node *np = dev->of_node;
75 int ntable;
76 int ret;
77
78 ntable = of_property_count_elems_of_size(np, "temperature-lookup-table",
79 sizeof(u32));
80 if (ntable < 0) {
81 dev_err(dev, "Lookup table is not provided\n");
82 return ntable;
83 }
84
85 if (ntable % 2) {
86 dev_err(dev, "Pair of temperature vs ADC read value missing\n");
87 return -EINVAL;
88 }
89
90 gti->lookup_table = devm_kzalloc(dev, sizeof(*gti->lookup_table) *
91 ntable, GFP_KERNEL);
92 if (!gti->lookup_table)
93 return -ENOMEM;
94
95 ret = of_property_read_u32_array(np, "temperature-lookup-table",
96 (u32 *)gti->lookup_table, ntable);
97 if (ret < 0) {
98 dev_err(dev, "Failed to read temperature lookup table: %d\n",
99 ret);
100 return ret;
101 }
102
103 gti->nlookup_table = ntable / 2;
104
105 return 0;
106}
107
108static int gadc_thermal_probe(struct platform_device *pdev)
109{
110 struct gadc_thermal_info *gti;
111 int ret;
112
113 if (!pdev->dev.of_node) {
114 dev_err(&pdev->dev, "Only DT based supported\n");
115 return -ENODEV;
116 }
117
118 gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL);
119 if (!gti)
120 return -ENOMEM;
121
122 ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti);
123 if (ret < 0)
124 return ret;
125
126 gti->dev = &pdev->dev;
127 platform_set_drvdata(pdev, gti);
128
129 gti->channel = iio_channel_get(&pdev->dev, "sensor-channel");
130 if (IS_ERR(gti->channel)) {
131 ret = PTR_ERR(gti->channel);
132 dev_err(&pdev->dev, "IIO channel not found: %d\n", ret);
133 return ret;
134 }
135
136 gti->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0,
137 gti, &gadc_thermal_ops);
138 if (IS_ERR(gti->tz_dev)) {
139 ret = PTR_ERR(gti->tz_dev);
140 dev_err(&pdev->dev, "Thermal zone sensor register failed: %d\n",
141 ret);
142 goto sensor_fail;
143 }
144
145 return 0;
146
147sensor_fail:
148 iio_channel_release(gti->channel);
149
150 return ret;
151}
152
153static int gadc_thermal_remove(struct platform_device *pdev)
154{
155 struct gadc_thermal_info *gti = platform_get_drvdata(pdev);
156
157 thermal_zone_of_sensor_unregister(&pdev->dev, gti->tz_dev);
158 iio_channel_release(gti->channel);
159
160 return 0;
161}
162
163static const struct of_device_id of_adc_thermal_match[] = {
164 { .compatible = "generic-adc-thermal", },
165 {},
166};
167MODULE_DEVICE_TABLE(of, of_adc_thermal_match);
168
169static struct platform_driver gadc_thermal_driver = {
170 .driver = {
171 .name = "generic-adc-thermal",
172 .of_match_table = of_adc_thermal_match,
173 },
174 .probe = gadc_thermal_probe,
175 .remove = gadc_thermal_remove,
176};
177
178module_platform_driver(gadc_thermal_driver);
179
180MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
181MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT");
182MODULE_LICENSE("GPL v2");