diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/da9055-hwmon.c | 336 |
3 files changed, 347 insertions, 0 deletions
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 | ||
337 | config 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 | |||
337 | config SENSORS_I5K_AMB | 347 | config 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 | |||
44 | obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o | 44 | obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o |
45 | obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o | 45 | obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o |
46 | obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o | 46 | obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o |
47 | obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o | ||
47 | obj-$(CONFIG_SENSORS_DME1737) += dme1737.o | 48 | obj-$(CONFIG_SENSORS_DME1737) += dme1737.o |
48 | obj-$(CONFIG_SENSORS_DS620) += ds620.o | 49 | obj-$(CONFIG_SENSORS_DS620) += ds620.o |
49 | obj-$(CONFIG_SENSORS_DS1621) += ds1621.o | 50 | obj-$(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 | |||
37 | struct 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 | |||
45 | static 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 | |||
53 | static 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 | |||
61 | static 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 | |||
107 | err: | ||
108 | mutex_unlock(&hwmon->irq_lock); | ||
109 | return ret; | ||
110 | } | ||
111 | |||
112 | static 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 */ | ||
122 | static 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 | |||
130 | static 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 | |||
138 | static 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 | |||
144 | static 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 | |||
173 | hwmon_err_release: | ||
174 | da9055_disable_auto_mode(hwmon->da9055, channel); | ||
175 | hwmon_err: | ||
176 | mutex_unlock(&hwmon->hwmon_lock); | ||
177 | return ret; | ||
178 | } | ||
179 | |||
180 | static 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 | |||
203 | static 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 | |||
210 | static 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 | |||
217 | static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, da9055_read_auto_ch, NULL, | ||
218 | DA9055_ADC_VSYS); | ||
219 | static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_label, NULL, | ||
220 | DA9055_ADC_VSYS); | ||
221 | static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, da9055_read_auto_ch, NULL, | ||
222 | DA9055_ADC_ADCIN1); | ||
223 | static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_label, NULL, | ||
224 | DA9055_ADC_ADCIN1); | ||
225 | static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, da9055_read_auto_ch, NULL, | ||
226 | DA9055_ADC_ADCIN2); | ||
227 | static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_label, NULL, | ||
228 | DA9055_ADC_ADCIN2); | ||
229 | static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, da9055_read_auto_ch, NULL, | ||
230 | DA9055_ADC_ADCIN3); | ||
231 | static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, | ||
232 | DA9055_ADC_ADCIN3); | ||
233 | |||
234 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, da9055_read_tjunc, NULL, | ||
235 | DA9055_ADC_TJUNC); | ||
236 | static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, | ||
237 | DA9055_ADC_TJUNC); | ||
238 | |||
239 | static DEVICE_ATTR(name, S_IRUGO, da9055_hwmon_show_name, NULL); | ||
240 | |||
241 | static 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 | |||
257 | static const struct attribute_group da9055_attr_group = {.attrs = da9055_attr}; | ||
258 | |||
259 | static 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 | |||
307 | err: | ||
308 | sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group); | ||
309 | return ret; | ||
310 | } | ||
311 | |||
312 | static 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 | |||
322 | static 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 | |||
331 | module_platform_driver(da9055_hwmon_driver); | ||
332 | |||
333 | MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); | ||
334 | MODULE_DESCRIPTION("DA9055 HWMON driver"); | ||
335 | MODULE_LICENSE("GPL"); | ||
336 | MODULE_ALIAS("platform:da9055-hwmon"); | ||