Commit | Line | Data |
---|---|---|
e5970223 AJ |
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, | |
44f751ce | 57 | [DA9055_ADC_ADCIN3] = DA9055_ADC_MUX_ADCIN3, |
e5970223 AJ |
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 */ | |
088ce2ac | 122 | static inline int volt_reg_to_mv(int value, int channel) |
e5970223 AJ |
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 | ||
088ce2ac | 171 | return sprintf(buf, "%d\n", volt_reg_to_mv(adc, channel)); |
e5970223 AJ |
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"); |