aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAshish Jangam <ashish.jangam@kpitcummins.com>2012-10-08 09:26:47 -0400
committerGuenter Roeck <linux@roeck-us.net>2012-12-05 13:55:55 -0500
commite597022353631194d93d142b6e14a9f673660de6 (patch)
tree67969abd05519f49425d11a07070e82ee9a7574e
parent04a87a0fbeab7aaebb52b4a079905f732f56d4a7 (diff)
hwmon: DA9055 HWMON driver
This is the HWMON patch for DA9055 PMIC and has got dependency on the DA9055 MFD core. This patch monitors the DA9055 PMIC's ADC channels vddout, junction temperature and auxiliary channels. This patch is functionally tested on Samsung SMDKV6410. Signed-off-by: David Dajun Chen <dchen@diasemi.com> Signed-off-by: Ashish Jangam <ashish.jangam@kpitcummins.com> [Guenter Roeck: Dropped __devinit, __devexit, __devexit_p] Signed-off-by: Guenter Roeck <linux@roeck-us.net>
-rw-r--r--Documentation/hwmon/da905547
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/da9055-hwmon.c336
4 files changed, 394 insertions, 0 deletions
diff --git a/Documentation/hwmon/da9055 b/Documentation/hwmon/da9055
new file mode 100644
index 000000000000..855c3f536e00
--- /dev/null
+++ b/Documentation/hwmon/da9055
@@ -0,0 +1,47 @@
1Supported chips:
2 * Dialog Semiconductors DA9055 PMIC
3 Prefix: 'da9055'
4 Datasheet: Datasheet is not publicly available.
5
6Authors: David Dajun Chen <dchen@diasemi.com>
7
8Description
9-----------
10
11The DA9055 provides an Analogue to Digital Converter (ADC) with 10 bits
12resolution and track and hold circuitry combined with an analogue input
13multiplexer. The analogue input multiplexer will allow conversion of up to 5
14different inputs. The track and hold circuit ensures stable input voltages at
15the input of the ADC during the conversion.
16
17The ADC is used to measure the following inputs:
18Channel 0: VDDOUT - measurement of the system voltage
19Channel 1: ADC_IN1 - high impedance input (0 - 2.5V)
20Channel 2: ADC_IN2 - high impedance input (0 - 2.5V)
21Channel 3: ADC_IN3 - high impedance input (0 - 2.5V)
22Channel 4: Internal Tjunc. - sense (internal temp. sensor)
23
24By using sysfs attributes we can measure the system voltage VDDOUT,
25chip junction temperature and auxiliary channels voltages.
26
27Voltage Monitoring
28------------------
29
30Voltages are sampled in a AUTO mode it can be manually sampled too and results
31are stored in a 10 bit ADC.
32
33The system voltage is calculated as:
34 Milli volt = ((ADC value * 1000) / 85) + 2500
35
36The voltages on ADC channels 1, 2 and 3 are calculated as:
37 Milli volt = (ADC value * 1000) / 102
38
39Temperature Monitoring
40----------------------
41
42Temperatures are sampled by a 10 bit ADC. Junction temperatures
43are monitored by the ADC channels.
44
45The junction temperature is calculated:
46 Degrees celsius = -0.4084 * (ADC_RES - T_OFFSET) + 307.6332
47The junction temperature attribute is supported by the driver.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 42f21ad56276..4800d4c2a7b7 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -334,6 +334,16 @@ config SENSORS_DA9052_ADC
334 This driver can also be built as module. If so, the module 334 This driver can also be built as module. If so, the module
335 will be called da9052-hwmon. 335 will be called da9052-hwmon.
336 336
337config SENSORS_DA9055
338 tristate "Dialog Semiconductor DA9055 ADC"
339 depends on MFD_DA9055
340 help
341 If you say yes here you get support for ADC on the Dialog
342 Semiconductor DA9055 PMIC.
343
344 This driver can also be built as a module. If so, the module
345 will be called da9055-hwmon.
346
337config SENSORS_I5K_AMB 347config SENSORS_I5K_AMB
338 tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets" 348 tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
339 depends on PCI 349 depends on PCI
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 8d5fcb5e8e9f..a930f0997d25 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
44obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o 44obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
45obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o 45obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
46obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o 46obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
47obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
47obj-$(CONFIG_SENSORS_DME1737) += dme1737.o 48obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
48obj-$(CONFIG_SENSORS_DS620) += ds620.o 49obj-$(CONFIG_SENSORS_DS620) += ds620.o
49obj-$(CONFIG_SENSORS_DS1621) += ds1621.o 50obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c
new file mode 100644
index 000000000000..d32edaea86d5
--- /dev/null
+++ b/drivers/hwmon/da9055-hwmon.c
@@ -0,0 +1,336 @@
1/*
2 * HWMON Driver for Dialog DA9055
3 *
4 * Copyright(c) 2012 Dialog Semiconductor Ltd.
5 *
6 * Author: David Dajun Chen <dchen@diasemi.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 *
13 */
14
15#include <linux/delay.h>
16#include <linux/err.h>
17#include <linux/hwmon.h>
18#include <linux/hwmon-sysfs.h>
19#include <linux/init.h>
20#include <linux/kernel.h>
21#include <linux/module.h>
22#include <linux/platform_device.h>
23#include <linux/completion.h>
24
25#include <linux/mfd/da9055/core.h>
26#include <linux/mfd/da9055/reg.h>
27
28#define DA9055_ADCIN_DIV 102
29#define DA9055_VSYS_DIV 85
30
31#define DA9055_ADC_VSYS 0
32#define DA9055_ADC_ADCIN1 1
33#define DA9055_ADC_ADCIN2 2
34#define DA9055_ADC_ADCIN3 3
35#define DA9055_ADC_TJUNC 4
36
37struct da9055_hwmon {
38 struct da9055 *da9055;
39 struct device *class_device;
40 struct mutex hwmon_lock;
41 struct mutex irq_lock;
42 struct completion done;
43};
44
45static const char * const input_names[] = {
46 [DA9055_ADC_VSYS] = "VSYS",
47 [DA9055_ADC_ADCIN1] = "ADC IN1",
48 [DA9055_ADC_ADCIN2] = "ADC IN2",
49 [DA9055_ADC_ADCIN3] = "ADC IN3",
50 [DA9055_ADC_TJUNC] = "CHIP TEMP",
51};
52
53static const u8 chan_mux[DA9055_ADC_TJUNC + 1] = {
54 [DA9055_ADC_VSYS] = DA9055_ADC_MUX_VSYS,
55 [DA9055_ADC_ADCIN1] = DA9055_ADC_MUX_ADCIN1,
56 [DA9055_ADC_ADCIN2] = DA9055_ADC_MUX_ADCIN2,
57 [DA9055_ADC_ADCIN3] = DA9055_ADC_MUX_ADCIN1,
58 [DA9055_ADC_TJUNC] = DA9055_ADC_MUX_T_SENSE,
59};
60
61static int da9055_adc_manual_read(struct da9055_hwmon *hwmon,
62 unsigned char channel)
63{
64 int ret;
65 unsigned short calc_data;
66 unsigned short data;
67 unsigned char mux_sel;
68 struct da9055 *da9055 = hwmon->da9055;
69
70 if (channel > DA9055_ADC_TJUNC)
71 return -EINVAL;
72
73 mutex_lock(&hwmon->irq_lock);
74
75 /* Selects desired MUX for manual conversion */
76 mux_sel = chan_mux[channel] | DA9055_ADC_MAN_CONV;
77
78 ret = da9055_reg_write(da9055, DA9055_REG_ADC_MAN, mux_sel);
79 if (ret < 0)
80 goto err;
81
82 /* Wait for an interrupt */
83 if (!wait_for_completion_timeout(&hwmon->done,
84 msecs_to_jiffies(500))) {
85 dev_err(da9055->dev,
86 "timeout waiting for ADC conversion interrupt\n");
87 ret = -ETIMEDOUT;
88 goto err;
89 }
90
91 ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_H);
92 if (ret < 0)
93 goto err;
94
95 calc_data = (unsigned short)ret;
96 data = calc_data << 2;
97
98 ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_L);
99 if (ret < 0)
100 goto err;
101
102 calc_data = (unsigned short)(ret & DA9055_ADC_LSB_MASK);
103 data |= calc_data;
104
105 ret = data;
106
107err:
108 mutex_unlock(&hwmon->irq_lock);
109 return ret;
110}
111
112static irqreturn_t da9055_auxadc_irq(int irq, void *irq_data)
113{
114 struct da9055_hwmon *hwmon = irq_data;
115
116 complete(&hwmon->done);
117
118 return IRQ_HANDLED;
119}
120
121/* Conversion function for VSYS and ADCINx */
122static inline int volt_reg_to_mV(int value, int channel)
123{
124 if (channel == DA9055_ADC_VSYS)
125 return DIV_ROUND_CLOSEST(value * 1000, DA9055_VSYS_DIV) + 2500;
126 else
127 return DIV_ROUND_CLOSEST(value * 1000, DA9055_ADCIN_DIV);
128}
129
130static int da9055_enable_auto_mode(struct da9055 *da9055, int channel)
131{
132
133 return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel,
134 1 << channel);
135
136}
137
138static int da9055_disable_auto_mode(struct da9055 *da9055, int channel)
139{
140
141 return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, 0);
142}
143
144static ssize_t da9055_read_auto_ch(struct device *dev,
145 struct device_attribute *devattr, char *buf)
146{
147 struct da9055_hwmon *hwmon = dev_get_drvdata(dev);
148 int ret, adc;
149 int channel = to_sensor_dev_attr(devattr)->index;
150
151 mutex_lock(&hwmon->hwmon_lock);
152
153 ret = da9055_enable_auto_mode(hwmon->da9055, channel);
154 if (ret < 0)
155 goto hwmon_err;
156
157 usleep_range(10000, 10500);
158
159 adc = da9055_reg_read(hwmon->da9055, DA9055_REG_VSYS_RES + channel);
160 if (adc < 0) {
161 ret = adc;
162 goto hwmon_err_release;
163 }
164
165 ret = da9055_disable_auto_mode(hwmon->da9055, channel);
166 if (ret < 0)
167 goto hwmon_err;
168
169 mutex_unlock(&hwmon->hwmon_lock);
170
171 return sprintf(buf, "%d\n", volt_reg_to_mV(adc, channel));
172
173hwmon_err_release:
174 da9055_disable_auto_mode(hwmon->da9055, channel);
175hwmon_err:
176 mutex_unlock(&hwmon->hwmon_lock);
177 return ret;
178}
179
180static ssize_t da9055_read_tjunc(struct device *dev,
181 struct device_attribute *devattr, char *buf)
182{
183 struct da9055_hwmon *hwmon = dev_get_drvdata(dev);
184 int tjunc;
185 int toffset;
186
187 tjunc = da9055_adc_manual_read(hwmon, DA9055_ADC_TJUNC);
188 if (tjunc < 0)
189 return tjunc;
190
191 toffset = da9055_reg_read(hwmon->da9055, DA9055_REG_T_OFFSET);
192 if (toffset < 0)
193 return toffset;
194
195 /*
196 * Degrees celsius = -0.4084 * (ADC_RES - T_OFFSET) + 307.6332
197 * T_OFFSET is a trim value used to improve accuracy of the result
198 */
199 return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(-4084 * (tjunc - toffset)
200 + 3076332, 10000));
201}
202
203static ssize_t da9055_hwmon_show_name(struct device *dev,
204 struct device_attribute *devattr,
205 char *buf)
206{
207 return sprintf(buf, "da9055-hwmon\n");
208}
209
210static ssize_t show_label(struct device *dev,
211 struct device_attribute *devattr, char *buf)
212{
213 return sprintf(buf, "%s\n",
214 input_names[to_sensor_dev_attr(devattr)->index]);
215}
216
217static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, da9055_read_auto_ch, NULL,
218 DA9055_ADC_VSYS);
219static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_label, NULL,
220 DA9055_ADC_VSYS);
221static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, da9055_read_auto_ch, NULL,
222 DA9055_ADC_ADCIN1);
223static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_label, NULL,
224 DA9055_ADC_ADCIN1);
225static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, da9055_read_auto_ch, NULL,
226 DA9055_ADC_ADCIN2);
227static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_label, NULL,
228 DA9055_ADC_ADCIN2);
229static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, da9055_read_auto_ch, NULL,
230 DA9055_ADC_ADCIN3);
231static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL,
232 DA9055_ADC_ADCIN3);
233
234static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, da9055_read_tjunc, NULL,
235 DA9055_ADC_TJUNC);
236static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL,
237 DA9055_ADC_TJUNC);
238
239static DEVICE_ATTR(name, S_IRUGO, da9055_hwmon_show_name, NULL);
240
241static struct attribute *da9055_attr[] = {
242 &dev_attr_name.attr,
243 &sensor_dev_attr_in0_input.dev_attr.attr,
244 &sensor_dev_attr_in0_label.dev_attr.attr,
245 &sensor_dev_attr_in1_input.dev_attr.attr,
246 &sensor_dev_attr_in1_label.dev_attr.attr,
247 &sensor_dev_attr_in2_input.dev_attr.attr,
248 &sensor_dev_attr_in2_label.dev_attr.attr,
249 &sensor_dev_attr_in3_input.dev_attr.attr,
250 &sensor_dev_attr_in3_label.dev_attr.attr,
251
252 &sensor_dev_attr_temp1_input.dev_attr.attr,
253 &sensor_dev_attr_temp1_label.dev_attr.attr,
254 NULL
255};
256
257static const struct attribute_group da9055_attr_group = {.attrs = da9055_attr};
258
259static int da9055_hwmon_probe(struct platform_device *pdev)
260{
261 struct da9055_hwmon *hwmon;
262 int hwmon_irq, ret;
263
264 hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da9055_hwmon),
265 GFP_KERNEL);
266 if (!hwmon)
267 return -ENOMEM;
268
269 mutex_init(&hwmon->hwmon_lock);
270 mutex_init(&hwmon->irq_lock);
271
272 init_completion(&hwmon->done);
273 hwmon->da9055 = dev_get_drvdata(pdev->dev.parent);
274
275 platform_set_drvdata(pdev, hwmon);
276
277 hwmon_irq = platform_get_irq_byname(pdev, "HWMON");
278 if (hwmon_irq < 0)
279 return hwmon_irq;
280
281 hwmon_irq = regmap_irq_get_virq(hwmon->da9055->irq_data, hwmon_irq);
282 if (hwmon_irq < 0)
283 return hwmon_irq;
284
285 ret = devm_request_threaded_irq(&pdev->dev, hwmon_irq,
286 NULL, da9055_auxadc_irq,
287 IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
288 "adc-irq", hwmon);
289 if (ret != 0) {
290 dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n",
291 ret);
292 return ret;
293 }
294
295 ret = sysfs_create_group(&pdev->dev.kobj, &da9055_attr_group);
296 if (ret)
297 return ret;
298
299 hwmon->class_device = hwmon_device_register(&pdev->dev);
300 if (IS_ERR(hwmon->class_device)) {
301 ret = PTR_ERR(hwmon->class_device);
302 goto err;
303 }
304
305 return 0;
306
307err:
308 sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group);
309 return ret;
310}
311
312static int da9055_hwmon_remove(struct platform_device *pdev)
313{
314 struct da9055_hwmon *hwmon = platform_get_drvdata(pdev);
315
316 sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group);
317 hwmon_device_unregister(hwmon->class_device);
318
319 return 0;
320}
321
322static struct platform_driver da9055_hwmon_driver = {
323 .probe = da9055_hwmon_probe,
324 .remove = da9055_hwmon_remove,
325 .driver = {
326 .name = "da9055-hwmon",
327 .owner = THIS_MODULE,
328 },
329};
330
331module_platform_driver(da9055_hwmon_driver);
332
333MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
334MODULE_DESCRIPTION("DA9055 HWMON driver");
335MODULE_LICENSE("GPL");
336MODULE_ALIAS("platform:da9055-hwmon");