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; | |
e5970223 AJ |
39 | struct mutex hwmon_lock; |
40 | struct mutex irq_lock; | |
41 | struct completion done; | |
42 | }; | |
43 | ||
44 | static const char * const input_names[] = { | |
45 | [DA9055_ADC_VSYS] = "VSYS", | |
46 | [DA9055_ADC_ADCIN1] = "ADC IN1", | |
47 | [DA9055_ADC_ADCIN2] = "ADC IN2", | |
48 | [DA9055_ADC_ADCIN3] = "ADC IN3", | |
49 | [DA9055_ADC_TJUNC] = "CHIP TEMP", | |
50 | }; | |
51 | ||
52 | static const u8 chan_mux[DA9055_ADC_TJUNC + 1] = { | |
53 | [DA9055_ADC_VSYS] = DA9055_ADC_MUX_VSYS, | |
54 | [DA9055_ADC_ADCIN1] = DA9055_ADC_MUX_ADCIN1, | |
55 | [DA9055_ADC_ADCIN2] = DA9055_ADC_MUX_ADCIN2, | |
44f751ce | 56 | [DA9055_ADC_ADCIN3] = DA9055_ADC_MUX_ADCIN3, |
e5970223 AJ |
57 | [DA9055_ADC_TJUNC] = DA9055_ADC_MUX_T_SENSE, |
58 | }; | |
59 | ||
60 | static int da9055_adc_manual_read(struct da9055_hwmon *hwmon, | |
61 | unsigned char channel) | |
62 | { | |
63 | int ret; | |
64 | unsigned short calc_data; | |
65 | unsigned short data; | |
66 | unsigned char mux_sel; | |
67 | struct da9055 *da9055 = hwmon->da9055; | |
68 | ||
69 | if (channel > DA9055_ADC_TJUNC) | |
70 | return -EINVAL; | |
71 | ||
72 | mutex_lock(&hwmon->irq_lock); | |
73 | ||
74 | /* Selects desired MUX for manual conversion */ | |
75 | mux_sel = chan_mux[channel] | DA9055_ADC_MAN_CONV; | |
76 | ||
77 | ret = da9055_reg_write(da9055, DA9055_REG_ADC_MAN, mux_sel); | |
78 | if (ret < 0) | |
79 | goto err; | |
80 | ||
81 | /* Wait for an interrupt */ | |
82 | if (!wait_for_completion_timeout(&hwmon->done, | |
83 | msecs_to_jiffies(500))) { | |
84 | dev_err(da9055->dev, | |
85 | "timeout waiting for ADC conversion interrupt\n"); | |
86 | ret = -ETIMEDOUT; | |
87 | goto err; | |
88 | } | |
89 | ||
90 | ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_H); | |
91 | if (ret < 0) | |
92 | goto err; | |
93 | ||
94 | calc_data = (unsigned short)ret; | |
95 | data = calc_data << 2; | |
96 | ||
97 | ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_L); | |
98 | if (ret < 0) | |
99 | goto err; | |
100 | ||
101 | calc_data = (unsigned short)(ret & DA9055_ADC_LSB_MASK); | |
102 | data |= calc_data; | |
103 | ||
104 | ret = data; | |
105 | ||
106 | err: | |
107 | mutex_unlock(&hwmon->irq_lock); | |
108 | return ret; | |
109 | } | |
110 | ||
111 | static irqreturn_t da9055_auxadc_irq(int irq, void *irq_data) | |
112 | { | |
113 | struct da9055_hwmon *hwmon = irq_data; | |
114 | ||
115 | complete(&hwmon->done); | |
116 | ||
117 | return IRQ_HANDLED; | |
118 | } | |
119 | ||
120 | /* Conversion function for VSYS and ADCINx */ | |
088ce2ac | 121 | static inline int volt_reg_to_mv(int value, int channel) |
e5970223 AJ |
122 | { |
123 | if (channel == DA9055_ADC_VSYS) | |
124 | return DIV_ROUND_CLOSEST(value * 1000, DA9055_VSYS_DIV) + 2500; | |
125 | else | |
126 | return DIV_ROUND_CLOSEST(value * 1000, DA9055_ADCIN_DIV); | |
127 | } | |
128 | ||
129 | static int da9055_enable_auto_mode(struct da9055 *da9055, int channel) | |
130 | { | |
131 | ||
132 | return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, | |
133 | 1 << channel); | |
134 | ||
135 | } | |
136 | ||
137 | static int da9055_disable_auto_mode(struct da9055 *da9055, int channel) | |
138 | { | |
139 | ||
140 | return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, 0); | |
141 | } | |
142 | ||
143 | static ssize_t da9055_read_auto_ch(struct device *dev, | |
144 | struct device_attribute *devattr, char *buf) | |
145 | { | |
146 | struct da9055_hwmon *hwmon = dev_get_drvdata(dev); | |
147 | int ret, adc; | |
148 | int channel = to_sensor_dev_attr(devattr)->index; | |
149 | ||
150 | mutex_lock(&hwmon->hwmon_lock); | |
151 | ||
152 | ret = da9055_enable_auto_mode(hwmon->da9055, channel); | |
153 | if (ret < 0) | |
154 | goto hwmon_err; | |
155 | ||
156 | usleep_range(10000, 10500); | |
157 | ||
158 | adc = da9055_reg_read(hwmon->da9055, DA9055_REG_VSYS_RES + channel); | |
159 | if (adc < 0) { | |
160 | ret = adc; | |
161 | goto hwmon_err_release; | |
162 | } | |
163 | ||
164 | ret = da9055_disable_auto_mode(hwmon->da9055, channel); | |
165 | if (ret < 0) | |
166 | goto hwmon_err; | |
167 | ||
168 | mutex_unlock(&hwmon->hwmon_lock); | |
169 | ||
088ce2ac | 170 | return sprintf(buf, "%d\n", volt_reg_to_mv(adc, channel)); |
e5970223 AJ |
171 | |
172 | hwmon_err_release: | |
173 | da9055_disable_auto_mode(hwmon->da9055, channel); | |
174 | hwmon_err: | |
175 | mutex_unlock(&hwmon->hwmon_lock); | |
176 | return ret; | |
177 | } | |
178 | ||
179 | static ssize_t da9055_read_tjunc(struct device *dev, | |
180 | struct device_attribute *devattr, char *buf) | |
181 | { | |
182 | struct da9055_hwmon *hwmon = dev_get_drvdata(dev); | |
183 | int tjunc; | |
184 | int toffset; | |
185 | ||
186 | tjunc = da9055_adc_manual_read(hwmon, DA9055_ADC_TJUNC); | |
187 | if (tjunc < 0) | |
188 | return tjunc; | |
189 | ||
190 | toffset = da9055_reg_read(hwmon->da9055, DA9055_REG_T_OFFSET); | |
191 | if (toffset < 0) | |
192 | return toffset; | |
193 | ||
194 | /* | |
195 | * Degrees celsius = -0.4084 * (ADC_RES - T_OFFSET) + 307.6332 | |
196 | * T_OFFSET is a trim value used to improve accuracy of the result | |
197 | */ | |
198 | return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(-4084 * (tjunc - toffset) | |
199 | + 3076332, 10000)); | |
200 | } | |
201 | ||
e5970223 AJ |
202 | static ssize_t show_label(struct device *dev, |
203 | struct device_attribute *devattr, char *buf) | |
204 | { | |
205 | return sprintf(buf, "%s\n", | |
206 | input_names[to_sensor_dev_attr(devattr)->index]); | |
207 | } | |
208 | ||
209 | static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, da9055_read_auto_ch, NULL, | |
210 | DA9055_ADC_VSYS); | |
211 | static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_label, NULL, | |
212 | DA9055_ADC_VSYS); | |
213 | static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, da9055_read_auto_ch, NULL, | |
214 | DA9055_ADC_ADCIN1); | |
215 | static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_label, NULL, | |
216 | DA9055_ADC_ADCIN1); | |
217 | static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, da9055_read_auto_ch, NULL, | |
218 | DA9055_ADC_ADCIN2); | |
219 | static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_label, NULL, | |
220 | DA9055_ADC_ADCIN2); | |
221 | static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, da9055_read_auto_ch, NULL, | |
222 | DA9055_ADC_ADCIN3); | |
223 | static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, | |
224 | DA9055_ADC_ADCIN3); | |
225 | ||
226 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, da9055_read_tjunc, NULL, | |
227 | DA9055_ADC_TJUNC); | |
228 | static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, | |
229 | DA9055_ADC_TJUNC); | |
230 | ||
e7d275e7 | 231 | static struct attribute *da9055_attrs[] = { |
e5970223 AJ |
232 | &sensor_dev_attr_in0_input.dev_attr.attr, |
233 | &sensor_dev_attr_in0_label.dev_attr.attr, | |
234 | &sensor_dev_attr_in1_input.dev_attr.attr, | |
235 | &sensor_dev_attr_in1_label.dev_attr.attr, | |
236 | &sensor_dev_attr_in2_input.dev_attr.attr, | |
237 | &sensor_dev_attr_in2_label.dev_attr.attr, | |
238 | &sensor_dev_attr_in3_input.dev_attr.attr, | |
239 | &sensor_dev_attr_in3_label.dev_attr.attr, | |
240 | ||
241 | &sensor_dev_attr_temp1_input.dev_attr.attr, | |
242 | &sensor_dev_attr_temp1_label.dev_attr.attr, | |
243 | NULL | |
244 | }; | |
245 | ||
e7d275e7 | 246 | ATTRIBUTE_GROUPS(da9055); |
e5970223 AJ |
247 | |
248 | static int da9055_hwmon_probe(struct platform_device *pdev) | |
249 | { | |
e7d275e7 | 250 | struct device *dev = &pdev->dev; |
e5970223 | 251 | struct da9055_hwmon *hwmon; |
e7d275e7 | 252 | struct device *hwmon_dev; |
e5970223 AJ |
253 | int hwmon_irq, ret; |
254 | ||
e7d275e7 | 255 | hwmon = devm_kzalloc(dev, sizeof(struct da9055_hwmon), GFP_KERNEL); |
e5970223 AJ |
256 | if (!hwmon) |
257 | return -ENOMEM; | |
258 | ||
259 | mutex_init(&hwmon->hwmon_lock); | |
260 | mutex_init(&hwmon->irq_lock); | |
261 | ||
262 | init_completion(&hwmon->done); | |
263 | hwmon->da9055 = dev_get_drvdata(pdev->dev.parent); | |
264 | ||
e5970223 AJ |
265 | hwmon_irq = platform_get_irq_byname(pdev, "HWMON"); |
266 | if (hwmon_irq < 0) | |
267 | return hwmon_irq; | |
268 | ||
e5970223 AJ |
269 | ret = devm_request_threaded_irq(&pdev->dev, hwmon_irq, |
270 | NULL, da9055_auxadc_irq, | |
271 | IRQF_TRIGGER_HIGH | IRQF_ONESHOT, | |
272 | "adc-irq", hwmon); | |
273 | if (ret != 0) { | |
274 | dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", | |
275 | ret); | |
276 | return ret; | |
277 | } | |
278 | ||
e7d275e7 AL |
279 | hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", |
280 | hwmon, | |
281 | da9055_groups); | |
282 | return PTR_ERR_OR_ZERO(hwmon_dev); | |
e5970223 AJ |
283 | } |
284 | ||
285 | static struct platform_driver da9055_hwmon_driver = { | |
286 | .probe = da9055_hwmon_probe, | |
e5970223 AJ |
287 | .driver = { |
288 | .name = "da9055-hwmon", | |
e5970223 AJ |
289 | }, |
290 | }; | |
291 | ||
292 | module_platform_driver(da9055_hwmon_driver); | |
293 | ||
294 | MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); | |
295 | MODULE_DESCRIPTION("DA9055 HWMON driver"); | |
296 | MODULE_LICENSE("GPL"); | |
297 | MODULE_ALIAS("platform:da9055-hwmon"); |