diff options
Diffstat (limited to 'extras/recipes-kernel/linux/linux-omap/base/0018-MFD-add-twl4030-madc-driver.patch')
-rw-r--r-- | extras/recipes-kernel/linux/linux-omap/base/0018-MFD-add-twl4030-madc-driver.patch | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/extras/recipes-kernel/linux/linux-omap/base/0018-MFD-add-twl4030-madc-driver.patch b/extras/recipes-kernel/linux/linux-omap/base/0018-MFD-add-twl4030-madc-driver.patch new file mode 100644 index 00000000..a55136db --- /dev/null +++ b/extras/recipes-kernel/linux/linux-omap/base/0018-MFD-add-twl4030-madc-driver.patch | |||
@@ -0,0 +1,740 @@ | |||
1 | From 562dc52ebe3df1e5d23416e78306db7c568dc427 Mon Sep 17 00:00:00 2001 | ||
2 | From: Steve Sakoman <steve@sakoman.com> | ||
3 | Date: Thu, 17 Dec 2009 14:19:34 -0800 | ||
4 | Subject: [PATCH 18/28] MFD: add twl4030 madc driver | ||
5 | |||
6 | --- | ||
7 | drivers/mfd/Kconfig | 21 ++ | ||
8 | drivers/mfd/Makefile | 1 + | ||
9 | drivers/mfd/twl4030-madc.c | 537 ++++++++++++++++++++++++++++++++++++++ | ||
10 | include/linux/i2c/twl4030-madc.h | 130 +++++++++ | ||
11 | 4 files changed, 689 insertions(+), 0 deletions(-) | ||
12 | create mode 100644 drivers/mfd/twl4030-madc.c | ||
13 | create mode 100644 include/linux/i2c/twl4030-madc.h | ||
14 | |||
15 | diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig | ||
16 | index 3a1493b..26ca160 100644 | ||
17 | --- a/drivers/mfd/Kconfig | ||
18 | +++ b/drivers/mfd/Kconfig | ||
19 | @@ -186,6 +186,27 @@ config TWL4030_CODEC | ||
20 | select MFD_CORE | ||
21 | default n | ||
22 | |||
23 | +config TWL4030_MADC | ||
24 | + tristate "TWL4030 MADC Driver" | ||
25 | + depends on TWL4030_CORE | ||
26 | + help | ||
27 | + The TWL4030 Monitoring ADC driver enables the host | ||
28 | + processor to monitor analog signals using analog-to-digital | ||
29 | + conversions on the input source. TWL4030 MADC provides the | ||
30 | + following features: | ||
31 | + - Single 10-bit ADC with successive approximation register (SAR) conversion; | ||
32 | + - Analog multiplexer for 16 inputs; | ||
33 | + - Seven (of the 16) inputs are freely available; | ||
34 | + - Battery voltage monitoring; | ||
35 | + - Concurrent conversion request management; | ||
36 | + - Interrupt signal to Primary Interrupt Handler; | ||
37 | + - Averaging feature; | ||
38 | + - Selective enable/disable of the averaging feature. | ||
39 | + | ||
40 | + Say 'y' here to statically link this module into the kernel or 'm' | ||
41 | + to build it as a dinamically loadable module. The module will be | ||
42 | + called twl4030-madc.ko | ||
43 | + | ||
44 | config TWL6030_PWM | ||
45 | tristate "TWL6030 PWM (Pulse Width Modulator) Support" | ||
46 | depends on TWL4030_CORE | ||
47 | diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile | ||
48 | index f54b365..8c4ccb2 100644 | ||
49 | --- a/drivers/mfd/Makefile | ||
50 | +++ b/drivers/mfd/Makefile | ||
51 | @@ -39,6 +39,7 @@ obj-$(CONFIG_MENELAUS) += menelaus.o | ||
52 | obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o | ||
53 | obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o | ||
54 | obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o | ||
55 | +obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o | ||
56 | obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o | ||
57 | |||
58 | obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o | ||
59 | diff --git a/drivers/mfd/twl4030-madc.c b/drivers/mfd/twl4030-madc.c | ||
60 | new file mode 100644 | ||
61 | index 0000000..4adf880 | ||
62 | --- /dev/null | ||
63 | +++ b/drivers/mfd/twl4030-madc.c | ||
64 | @@ -0,0 +1,537 @@ | ||
65 | +/* | ||
66 | + * TWL4030 MADC module driver | ||
67 | + * | ||
68 | + * Copyright (C) 2008 Nokia Corporation | ||
69 | + * Mikko Ylinen <mikko.k.ylinen@nokia.com> | ||
70 | + * | ||
71 | + * This program is free software; you can redistribute it and/or | ||
72 | + * modify it under the terms of the GNU General Public License | ||
73 | + * version 2 as published by the Free Software Foundation. | ||
74 | + * | ||
75 | + * This program is distributed in the hope that it will be useful, but | ||
76 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
77 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
78 | + * General Public License for more details. | ||
79 | + * | ||
80 | + * You should have received a copy of the GNU General Public License | ||
81 | + * along with this program; if not, write to the Free Software | ||
82 | + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
83 | + * 02110-1301 USA | ||
84 | + * | ||
85 | + */ | ||
86 | + | ||
87 | +#include <linux/delay.h> | ||
88 | +#include <linux/fs.h> | ||
89 | +#include <linux/init.h> | ||
90 | +#include <linux/interrupt.h> | ||
91 | +#include <linux/kernel.h> | ||
92 | +#include <linux/miscdevice.h> | ||
93 | +#include <linux/module.h> | ||
94 | +#include <linux/platform_device.h> | ||
95 | +#include <linux/slab.h> | ||
96 | +#include <linux/types.h> | ||
97 | +#include <linux/i2c/twl.h> | ||
98 | +#include <linux/i2c/twl4030-madc.h> | ||
99 | + | ||
100 | +#include <asm/uaccess.h> | ||
101 | + | ||
102 | +#define TWL4030_MADC_PFX "twl4030-madc: " | ||
103 | + | ||
104 | +struct twl4030_madc_data { | ||
105 | + struct device *dev; | ||
106 | + struct mutex lock; | ||
107 | + struct work_struct ws; | ||
108 | + struct twl4030_madc_request requests[TWL4030_MADC_NUM_METHODS]; | ||
109 | + int imr; | ||
110 | + int isr; | ||
111 | +}; | ||
112 | + | ||
113 | +static struct twl4030_madc_data *the_madc; | ||
114 | + | ||
115 | +static | ||
116 | +const struct twl4030_madc_conversion_method twl4030_conversion_methods[] = { | ||
117 | + [TWL4030_MADC_RT] = { | ||
118 | + .sel = TWL4030_MADC_RTSELECT_LSB, | ||
119 | + .avg = TWL4030_MADC_RTAVERAGE_LSB, | ||
120 | + .rbase = TWL4030_MADC_RTCH0_LSB, | ||
121 | + }, | ||
122 | + [TWL4030_MADC_SW1] = { | ||
123 | + .sel = TWL4030_MADC_SW1SELECT_LSB, | ||
124 | + .avg = TWL4030_MADC_SW1AVERAGE_LSB, | ||
125 | + .rbase = TWL4030_MADC_GPCH0_LSB, | ||
126 | + .ctrl = TWL4030_MADC_CTRL_SW1, | ||
127 | + }, | ||
128 | + [TWL4030_MADC_SW2] = { | ||
129 | + .sel = TWL4030_MADC_SW2SELECT_LSB, | ||
130 | + .avg = TWL4030_MADC_SW2AVERAGE_LSB, | ||
131 | + .rbase = TWL4030_MADC_GPCH0_LSB, | ||
132 | + .ctrl = TWL4030_MADC_CTRL_SW2, | ||
133 | + }, | ||
134 | +}; | ||
135 | + | ||
136 | +static int twl4030_madc_read(struct twl4030_madc_data *madc, u8 reg) | ||
137 | +{ | ||
138 | + int ret; | ||
139 | + u8 val; | ||
140 | + | ||
141 | + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &val, reg); | ||
142 | + if (ret) { | ||
143 | + dev_dbg(madc->dev, "unable to read register 0x%X\n", reg); | ||
144 | + return ret; | ||
145 | + } | ||
146 | + | ||
147 | + return val; | ||
148 | +} | ||
149 | + | ||
150 | +static void twl4030_madc_write(struct twl4030_madc_data *madc, u8 reg, u8 val) | ||
151 | +{ | ||
152 | + int ret; | ||
153 | + | ||
154 | + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, val, reg); | ||
155 | + if (ret) | ||
156 | + dev_err(madc->dev, "unable to write register 0x%X\n", reg); | ||
157 | +} | ||
158 | + | ||
159 | +static int twl4030_madc_channel_raw_read(struct twl4030_madc_data *madc, u8 reg) | ||
160 | +{ | ||
161 | + u8 msb, lsb; | ||
162 | + | ||
163 | + /* For each ADC channel, we have MSB and LSB register pair. MSB address | ||
164 | + * is always LSB address+1. reg parameter is the addr of LSB register */ | ||
165 | + msb = twl4030_madc_read(madc, reg + 1); | ||
166 | + lsb = twl4030_madc_read(madc, reg); | ||
167 | + | ||
168 | + return (int)(((msb << 8) | lsb) >> 6); | ||
169 | +} | ||
170 | + | ||
171 | +static int twl4030_madc_read_channels(struct twl4030_madc_data *madc, | ||
172 | + u8 reg_base, u16 channels, int *buf) | ||
173 | +{ | ||
174 | + int count = 0; | ||
175 | + u8 reg, i; | ||
176 | + | ||
177 | + if (unlikely(!buf)) | ||
178 | + return 0; | ||
179 | + | ||
180 | + for (i = 0; i < TWL4030_MADC_MAX_CHANNELS; i++) { | ||
181 | + if (channels & (1<<i)) { | ||
182 | + reg = reg_base + 2*i; | ||
183 | + buf[i] = twl4030_madc_channel_raw_read(madc, reg); | ||
184 | + count++; | ||
185 | + } | ||
186 | + } | ||
187 | + return count; | ||
188 | +} | ||
189 | + | ||
190 | +static void twl4030_madc_enable_irq(struct twl4030_madc_data *madc, int id) | ||
191 | +{ | ||
192 | + u8 val; | ||
193 | + | ||
194 | + val = twl4030_madc_read(madc, madc->imr); | ||
195 | + val &= ~(1 << id); | ||
196 | + twl4030_madc_write(madc, madc->imr, val); | ||
197 | +} | ||
198 | + | ||
199 | +static void twl4030_madc_disable_irq(struct twl4030_madc_data *madc, int id) | ||
200 | +{ | ||
201 | + u8 val; | ||
202 | + | ||
203 | + val = twl4030_madc_read(madc, madc->imr); | ||
204 | + val |= (1 << id); | ||
205 | + twl4030_madc_write(madc, madc->imr, val); | ||
206 | +} | ||
207 | + | ||
208 | +static irqreturn_t twl4030_madc_irq_handler(int irq, void *_madc) | ||
209 | +{ | ||
210 | + struct twl4030_madc_data *madc = _madc; | ||
211 | + u8 isr_val, imr_val; | ||
212 | + int i; | ||
213 | + | ||
214 | +#ifdef CONFIG_LOCKDEP | ||
215 | + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which | ||
216 | + * we don't want and can't tolerate. Although it might be | ||
217 | + * friendlier not to borrow this thread context... | ||
218 | + */ | ||
219 | + local_irq_enable(); | ||
220 | +#endif | ||
221 | + | ||
222 | + /* Use COR to ack interrupts since we have no shared IRQs in ISRx */ | ||
223 | + isr_val = twl4030_madc_read(madc, madc->isr); | ||
224 | + imr_val = twl4030_madc_read(madc, madc->imr); | ||
225 | + | ||
226 | + isr_val &= ~imr_val; | ||
227 | + | ||
228 | + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { | ||
229 | + | ||
230 | + if (!(isr_val & (1<<i))) | ||
231 | + continue; | ||
232 | + | ||
233 | + twl4030_madc_disable_irq(madc, i); | ||
234 | + madc->requests[i].result_pending = 1; | ||
235 | + } | ||
236 | + | ||
237 | + schedule_work(&madc->ws); | ||
238 | + | ||
239 | + return IRQ_HANDLED; | ||
240 | +} | ||
241 | + | ||
242 | +static void twl4030_madc_work(struct work_struct *ws) | ||
243 | +{ | ||
244 | + const struct twl4030_madc_conversion_method *method; | ||
245 | + struct twl4030_madc_data *madc; | ||
246 | + struct twl4030_madc_request *r; | ||
247 | + int len, i; | ||
248 | + | ||
249 | + madc = container_of(ws, struct twl4030_madc_data, ws); | ||
250 | + mutex_lock(&madc->lock); | ||
251 | + | ||
252 | + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { | ||
253 | + | ||
254 | + r = &madc->requests[i]; | ||
255 | + | ||
256 | + /* No pending results for this method, move to next one */ | ||
257 | + if (!r->result_pending) | ||
258 | + continue; | ||
259 | + | ||
260 | + method = &twl4030_conversion_methods[r->method]; | ||
261 | + | ||
262 | + /* Read results */ | ||
263 | + len = twl4030_madc_read_channels(madc, method->rbase, | ||
264 | + r->channels, r->rbuf); | ||
265 | + | ||
266 | + /* Return results to caller */ | ||
267 | + if (r->func_cb != NULL) { | ||
268 | + r->func_cb(len, r->channels, r->rbuf); | ||
269 | + r->func_cb = NULL; | ||
270 | + } | ||
271 | + | ||
272 | + /* Free request */ | ||
273 | + r->result_pending = 0; | ||
274 | + r->active = 0; | ||
275 | + } | ||
276 | + | ||
277 | + mutex_unlock(&madc->lock); | ||
278 | +} | ||
279 | + | ||
280 | +static int twl4030_madc_set_irq(struct twl4030_madc_data *madc, | ||
281 | + struct twl4030_madc_request *req) | ||
282 | +{ | ||
283 | + struct twl4030_madc_request *p; | ||
284 | + | ||
285 | + p = &madc->requests[req->method]; | ||
286 | + | ||
287 | + memcpy(p, req, sizeof *req); | ||
288 | + | ||
289 | + twl4030_madc_enable_irq(madc, req->method); | ||
290 | + | ||
291 | + return 0; | ||
292 | +} | ||
293 | + | ||
294 | +static inline void twl4030_madc_start_conversion(struct twl4030_madc_data *madc, | ||
295 | + int conv_method) | ||
296 | +{ | ||
297 | + const struct twl4030_madc_conversion_method *method; | ||
298 | + | ||
299 | + method = &twl4030_conversion_methods[conv_method]; | ||
300 | + | ||
301 | + switch (conv_method) { | ||
302 | + case TWL4030_MADC_SW1: | ||
303 | + case TWL4030_MADC_SW2: | ||
304 | + twl4030_madc_write(madc, method->ctrl, TWL4030_MADC_SW_START); | ||
305 | + break; | ||
306 | + case TWL4030_MADC_RT: | ||
307 | + default: | ||
308 | + break; | ||
309 | + } | ||
310 | +} | ||
311 | + | ||
312 | +static int twl4030_madc_wait_conversion_ready( | ||
313 | + struct twl4030_madc_data *madc, | ||
314 | + unsigned int timeout_ms, u8 status_reg) | ||
315 | +{ | ||
316 | + unsigned long timeout; | ||
317 | + | ||
318 | + timeout = jiffies + msecs_to_jiffies(timeout_ms); | ||
319 | + do { | ||
320 | + u8 reg; | ||
321 | + | ||
322 | + reg = twl4030_madc_read(madc, status_reg); | ||
323 | + if (!(reg & TWL4030_MADC_BUSY) && (reg & TWL4030_MADC_EOC_SW)) | ||
324 | + return 0; | ||
325 | + } while (!time_after(jiffies, timeout)); | ||
326 | + | ||
327 | + return -EAGAIN; | ||
328 | +} | ||
329 | + | ||
330 | +int twl4030_madc_conversion(struct twl4030_madc_request *req) | ||
331 | +{ | ||
332 | + const struct twl4030_madc_conversion_method *method; | ||
333 | + u8 ch_msb, ch_lsb; | ||
334 | + int ret; | ||
335 | + | ||
336 | + if (unlikely(!req)) | ||
337 | + return -EINVAL; | ||
338 | + | ||
339 | + mutex_lock(&the_madc->lock); | ||
340 | + | ||
341 | + /* Do we have a conversion request ongoing */ | ||
342 | + if (the_madc->requests[req->method].active) { | ||
343 | + ret = -EBUSY; | ||
344 | + goto out; | ||
345 | + } | ||
346 | + | ||
347 | + ch_msb = (req->channels >> 8) & 0xff; | ||
348 | + ch_lsb = req->channels & 0xff; | ||
349 | + | ||
350 | + method = &twl4030_conversion_methods[req->method]; | ||
351 | + | ||
352 | + /* Select channels to be converted */ | ||
353 | + twl4030_madc_write(the_madc, method->sel + 1, ch_msb); | ||
354 | + twl4030_madc_write(the_madc, method->sel, ch_lsb); | ||
355 | + | ||
356 | + /* Select averaging for all channels if do_avg is set */ | ||
357 | + if (req->do_avg) { | ||
358 | + twl4030_madc_write(the_madc, method->avg + 1, ch_msb); | ||
359 | + twl4030_madc_write(the_madc, method->avg, ch_lsb); | ||
360 | + } | ||
361 | + | ||
362 | + if ((req->type == TWL4030_MADC_IRQ_ONESHOT) && (req->func_cb != NULL)) { | ||
363 | + twl4030_madc_set_irq(the_madc, req); | ||
364 | + twl4030_madc_start_conversion(the_madc, req->method); | ||
365 | + the_madc->requests[req->method].active = 1; | ||
366 | + ret = 0; | ||
367 | + goto out; | ||
368 | + } | ||
369 | + | ||
370 | + /* With RT method we should not be here anymore */ | ||
371 | + if (req->method == TWL4030_MADC_RT) { | ||
372 | + ret = -EINVAL; | ||
373 | + goto out; | ||
374 | + } | ||
375 | + | ||
376 | + twl4030_madc_start_conversion(the_madc, req->method); | ||
377 | + the_madc->requests[req->method].active = 1; | ||
378 | + | ||
379 | + /* Wait until conversion is ready (ctrl register returns EOC) */ | ||
380 | + ret = twl4030_madc_wait_conversion_ready(the_madc, 5, method->ctrl); | ||
381 | + if (ret) { | ||
382 | + dev_dbg(the_madc->dev, "conversion timeout!\n"); | ||
383 | + the_madc->requests[req->method].active = 0; | ||
384 | + goto out; | ||
385 | + } | ||
386 | + | ||
387 | + ret = twl4030_madc_read_channels(the_madc, method->rbase, req->channels, | ||
388 | + req->rbuf); | ||
389 | + | ||
390 | + the_madc->requests[req->method].active = 0; | ||
391 | + | ||
392 | +out: | ||
393 | + mutex_unlock(&the_madc->lock); | ||
394 | + | ||
395 | + return ret; | ||
396 | +} | ||
397 | +EXPORT_SYMBOL(twl4030_madc_conversion); | ||
398 | + | ||
399 | +static int twl4030_madc_set_current_generator(struct twl4030_madc_data *madc, | ||
400 | + int chan, int on) | ||
401 | +{ | ||
402 | + int ret; | ||
403 | + u8 regval; | ||
404 | + | ||
405 | + /* Current generator is only available for ADCIN0 and ADCIN1. NB: | ||
406 | + * ADCIN1 current generator only works when AC or VBUS is present */ | ||
407 | + if (chan > 1) | ||
408 | + return EINVAL; | ||
409 | + | ||
410 | + ret = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, | ||
411 | + ®val, TWL4030_BCI_BCICTL1); | ||
412 | + if (on) | ||
413 | + regval |= (chan) ? TWL4030_BCI_ITHEN : TWL4030_BCI_TYPEN; | ||
414 | + else | ||
415 | + regval &= (chan) ? ~TWL4030_BCI_ITHEN : ~TWL4030_BCI_TYPEN; | ||
416 | + ret = twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, | ||
417 | + regval, TWL4030_BCI_BCICTL1); | ||
418 | + | ||
419 | + return ret; | ||
420 | +} | ||
421 | + | ||
422 | +static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on) | ||
423 | +{ | ||
424 | + u8 regval; | ||
425 | + | ||
426 | + regval = twl4030_madc_read(madc, TWL4030_MADC_CTRL1); | ||
427 | + if (on) | ||
428 | + regval |= TWL4030_MADC_MADCON; | ||
429 | + else | ||
430 | + regval &= ~TWL4030_MADC_MADCON; | ||
431 | + twl4030_madc_write(madc, TWL4030_MADC_CTRL1, regval); | ||
432 | + | ||
433 | + return 0; | ||
434 | +} | ||
435 | + | ||
436 | +static long twl4030_madc_ioctl(struct file *filp, unsigned int cmd, | ||
437 | + unsigned long arg) | ||
438 | +{ | ||
439 | + struct twl4030_madc_user_parms par; | ||
440 | + int val, ret; | ||
441 | + | ||
442 | + ret = copy_from_user(&par, (void __user *) arg, sizeof(par)); | ||
443 | + if (ret) { | ||
444 | + dev_dbg(the_madc->dev, "copy_from_user: %d\n", ret); | ||
445 | + return -EACCES; | ||
446 | + } | ||
447 | + | ||
448 | + switch (cmd) { | ||
449 | + case TWL4030_MADC_IOCX_ADC_RAW_READ: { | ||
450 | + struct twl4030_madc_request req; | ||
451 | + if (par.channel >= TWL4030_MADC_MAX_CHANNELS) | ||
452 | + return -EINVAL; | ||
453 | + | ||
454 | + req.channels = (1 << par.channel); | ||
455 | + req.do_avg = par.average; | ||
456 | + req.method = TWL4030_MADC_SW1; | ||
457 | + req.func_cb = NULL; | ||
458 | + | ||
459 | + val = twl4030_madc_conversion(&req); | ||
460 | + if (val <= 0) { | ||
461 | + par.status = -1; | ||
462 | + } else { | ||
463 | + par.status = 0; | ||
464 | + par.result = (u16)req.rbuf[par.channel]; | ||
465 | + } | ||
466 | + break; | ||
467 | + } | ||
468 | + default: | ||
469 | + return -EINVAL; | ||
470 | + } | ||
471 | + | ||
472 | + ret = copy_to_user((void __user *) arg, &par, sizeof(par)); | ||
473 | + if (ret) { | ||
474 | + dev_dbg(the_madc->dev, "copy_to_user: %d\n", ret); | ||
475 | + return -EACCES; | ||
476 | + } | ||
477 | + | ||
478 | + return 0; | ||
479 | +} | ||
480 | + | ||
481 | +static struct file_operations twl4030_madc_fileops = { | ||
482 | + .owner = THIS_MODULE, | ||
483 | + .unlocked_ioctl = twl4030_madc_ioctl | ||
484 | +}; | ||
485 | + | ||
486 | +static struct miscdevice twl4030_madc_device = { | ||
487 | + .minor = MISC_DYNAMIC_MINOR, | ||
488 | + .name = "twl4030-madc", | ||
489 | + .fops = &twl4030_madc_fileops | ||
490 | +}; | ||
491 | + | ||
492 | +static int __init twl4030_madc_probe(struct platform_device *pdev) | ||
493 | +{ | ||
494 | + struct twl4030_madc_data *madc; | ||
495 | + struct twl4030_madc_platform_data *pdata = pdev->dev.platform_data; | ||
496 | + int ret; | ||
497 | + u8 regval; | ||
498 | + | ||
499 | + madc = kzalloc(sizeof *madc, GFP_KERNEL); | ||
500 | + if (!madc) | ||
501 | + return -ENOMEM; | ||
502 | + | ||
503 | + if (!pdata) { | ||
504 | + dev_dbg(&pdev->dev, "platform_data not available\n"); | ||
505 | + ret = -EINVAL; | ||
506 | + goto err_pdata; | ||
507 | + } | ||
508 | + | ||
509 | + madc->imr = (pdata->irq_line == 1) ? TWL4030_MADC_IMR1 : TWL4030_MADC_IMR2; | ||
510 | + madc->isr = (pdata->irq_line == 1) ? TWL4030_MADC_ISR1 : TWL4030_MADC_ISR2; | ||
511 | + | ||
512 | + ret = misc_register(&twl4030_madc_device); | ||
513 | + if (ret) { | ||
514 | + dev_dbg(&pdev->dev, "could not register misc_device\n"); | ||
515 | + goto err_misc; | ||
516 | + } | ||
517 | + twl4030_madc_set_power(madc, 1); | ||
518 | + twl4030_madc_set_current_generator(madc, 0, 1); | ||
519 | + | ||
520 | + /* Enable ADCIN3 through 6 */ | ||
521 | + ret = twl_i2c_read_u8(TWL4030_MODULE_USB, | ||
522 | + ®val, TWL4030_USB_CARKIT_ANA_CTRL); | ||
523 | + | ||
524 | + regval |= TWL4030_USB_SEL_MADC_MCPC; | ||
525 | + | ||
526 | + ret = twl_i2c_write_u8(TWL4030_MODULE_USB, | ||
527 | + regval, TWL4030_USB_CARKIT_ANA_CTRL); | ||
528 | + | ||
529 | + | ||
530 | + ret = twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, | ||
531 | + ®val, TWL4030_BCI_BCICTL1); | ||
532 | + | ||
533 | + regval |= TWL4030_BCI_MESBAT; | ||
534 | + | ||
535 | + ret = twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, | ||
536 | + regval, TWL4030_BCI_BCICTL1); | ||
537 | + | ||
538 | + ret = request_irq(platform_get_irq(pdev, 0), twl4030_madc_irq_handler, | ||
539 | + 0, "twl4030_madc", madc); | ||
540 | + if (ret) { | ||
541 | + dev_dbg(&pdev->dev, "could not request irq\n"); | ||
542 | + goto err_irq; | ||
543 | + } | ||
544 | + | ||
545 | + platform_set_drvdata(pdev, madc); | ||
546 | + mutex_init(&madc->lock); | ||
547 | + INIT_WORK(&madc->ws, twl4030_madc_work); | ||
548 | + | ||
549 | + the_madc = madc; | ||
550 | + | ||
551 | + return 0; | ||
552 | + | ||
553 | +err_irq: | ||
554 | + misc_deregister(&twl4030_madc_device); | ||
555 | + | ||
556 | +err_misc: | ||
557 | +err_pdata: | ||
558 | + kfree(madc); | ||
559 | + | ||
560 | + return ret; | ||
561 | +} | ||
562 | + | ||
563 | +static int __exit twl4030_madc_remove(struct platform_device *pdev) | ||
564 | +{ | ||
565 | + struct twl4030_madc_data *madc = platform_get_drvdata(pdev); | ||
566 | + | ||
567 | + twl4030_madc_set_power(madc, 0); | ||
568 | + twl4030_madc_set_current_generator(madc, 0, 0); | ||
569 | + free_irq(platform_get_irq(pdev, 0), madc); | ||
570 | + cancel_work_sync(&madc->ws); | ||
571 | + misc_deregister(&twl4030_madc_device); | ||
572 | + | ||
573 | + return 0; | ||
574 | +} | ||
575 | + | ||
576 | +static struct platform_driver twl4030_madc_driver = { | ||
577 | + .probe = twl4030_madc_probe, | ||
578 | + .remove = __exit_p(twl4030_madc_remove), | ||
579 | + .driver = { | ||
580 | + .name = "twl4030_madc", | ||
581 | + .owner = THIS_MODULE, | ||
582 | + }, | ||
583 | +}; | ||
584 | + | ||
585 | +static int __init twl4030_madc_init(void) | ||
586 | +{ | ||
587 | + return platform_driver_register(&twl4030_madc_driver); | ||
588 | +} | ||
589 | +module_init(twl4030_madc_init); | ||
590 | + | ||
591 | +static void __exit twl4030_madc_exit(void) | ||
592 | +{ | ||
593 | + platform_driver_unregister(&twl4030_madc_driver); | ||
594 | +} | ||
595 | +module_exit(twl4030_madc_exit); | ||
596 | + | ||
597 | +MODULE_ALIAS("platform:twl4030-madc"); | ||
598 | +MODULE_AUTHOR("Nokia Corporation"); | ||
599 | +MODULE_DESCRIPTION("twl4030 ADC driver"); | ||
600 | +MODULE_LICENSE("GPL"); | ||
601 | + | ||
602 | diff --git a/include/linux/i2c/twl4030-madc.h b/include/linux/i2c/twl4030-madc.h | ||
603 | new file mode 100644 | ||
604 | index 0000000..341a665 | ||
605 | --- /dev/null | ||
606 | +++ b/include/linux/i2c/twl4030-madc.h | ||
607 | @@ -0,0 +1,130 @@ | ||
608 | +/* | ||
609 | + * include/linux/i2c/twl4030-madc.h | ||
610 | + * | ||
611 | + * TWL4030 MADC module driver header | ||
612 | + * | ||
613 | + * Copyright (C) 2008 Nokia Corporation | ||
614 | + * Mikko Ylinen <mikko.k.ylinen@nokia.com> | ||
615 | + * | ||
616 | + * This program is free software; you can redistribute it and/or | ||
617 | + * modify it under the terms of the GNU General Public License | ||
618 | + * version 2 as published by the Free Software Foundation. | ||
619 | + * | ||
620 | + * This program is distributed in the hope that it will be useful, but | ||
621 | + * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
622 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
623 | + * General Public License for more details. | ||
624 | + * | ||
625 | + * You should have received a copy of the GNU General Public License | ||
626 | + * along with this program; if not, write to the Free Software | ||
627 | + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
628 | + * 02110-1301 USA | ||
629 | + * | ||
630 | + */ | ||
631 | + | ||
632 | +#ifndef _TWL4030_MADC_H | ||
633 | +#define _TWL4030_MADC_H | ||
634 | + | ||
635 | +struct twl4030_madc_conversion_method { | ||
636 | + u8 sel; | ||
637 | + u8 avg; | ||
638 | + u8 rbase; | ||
639 | + u8 ctrl; | ||
640 | +}; | ||
641 | + | ||
642 | +#define TWL4030_MADC_MAX_CHANNELS 16 | ||
643 | + | ||
644 | +struct twl4030_madc_request { | ||
645 | + u16 channels; | ||
646 | + u16 do_avg; | ||
647 | + u16 method; | ||
648 | + u16 type; | ||
649 | + int active; | ||
650 | + int result_pending; | ||
651 | + int rbuf[TWL4030_MADC_MAX_CHANNELS]; | ||
652 | + void (*func_cb)(int len, int channels, int *buf); | ||
653 | +}; | ||
654 | + | ||
655 | +enum conversion_methods { | ||
656 | + TWL4030_MADC_RT, | ||
657 | + TWL4030_MADC_SW1, | ||
658 | + TWL4030_MADC_SW2, | ||
659 | + TWL4030_MADC_NUM_METHODS | ||
660 | +}; | ||
661 | + | ||
662 | +enum sample_type { | ||
663 | + TWL4030_MADC_WAIT, | ||
664 | + TWL4030_MADC_IRQ_ONESHOT, | ||
665 | + TWL4030_MADC_IRQ_REARM | ||
666 | +}; | ||
667 | + | ||
668 | +#define TWL4030_MADC_CTRL1 0x00 | ||
669 | +#define TWL4030_MADC_CTRL2 0x01 | ||
670 | + | ||
671 | +#define TWL4030_MADC_RTSELECT_LSB 0x02 | ||
672 | +#define TWL4030_MADC_SW1SELECT_LSB 0x06 | ||
673 | +#define TWL4030_MADC_SW2SELECT_LSB 0x0A | ||
674 | + | ||
675 | +#define TWL4030_MADC_RTAVERAGE_LSB 0x04 | ||
676 | +#define TWL4030_MADC_SW1AVERAGE_LSB 0x08 | ||
677 | +#define TWL4030_MADC_SW2AVERAGE_LSB 0x0C | ||
678 | + | ||
679 | +#define TWL4030_MADC_CTRL_SW1 0x12 | ||
680 | +#define TWL4030_MADC_CTRL_SW2 0x13 | ||
681 | + | ||
682 | +#define TWL4030_MADC_RTCH0_LSB 0x17 | ||
683 | +#define TWL4030_MADC_GPCH0_LSB 0x37 | ||
684 | + | ||
685 | +#define TWL4030_MADC_MADCON (1<<0) /* MADC power on */ | ||
686 | +#define TWL4030_MADC_BUSY (1<<0) /* MADC busy */ | ||
687 | +#define TWL4030_MADC_EOC_SW (1<<1) /* MADC conversion completion */ | ||
688 | +#define TWL4030_MADC_SW_START (1<<5) /* MADC SWx start conversion */ | ||
689 | + | ||
690 | +#define TWL4030_MADC_ADCIN0 (1<<0) | ||
691 | +#define TWL4030_MADC_ADCIN1 (1<<1) | ||
692 | +#define TWL4030_MADC_ADCIN2 (1<<2) | ||
693 | +#define TWL4030_MADC_ADCIN3 (1<<3) | ||
694 | +#define TWL4030_MADC_ADCIN4 (1<<4) | ||
695 | +#define TWL4030_MADC_ADCIN5 (1<<5) | ||
696 | +#define TWL4030_MADC_ADCIN6 (1<<6) | ||
697 | +#define TWL4030_MADC_ADCIN7 (1<<7) | ||
698 | +#define TWL4030_MADC_ADCIN8 (1<<8) | ||
699 | +#define TWL4030_MADC_ADCIN9 (1<<9) | ||
700 | +#define TWL4030_MADC_ADCIN10 (1<<10) | ||
701 | +#define TWL4030_MADC_ADCIN11 (1<<11) | ||
702 | +#define TWL4030_MADC_ADCIN12 (1<<12) | ||
703 | +#define TWL4030_MADC_ADCIN13 (1<<13) | ||
704 | +#define TWL4030_MADC_ADCIN14 (1<<14) | ||
705 | +#define TWL4030_MADC_ADCIN15 (1<<15) | ||
706 | + | ||
707 | +/* Fixed channels */ | ||
708 | +#define TWL4030_MADC_BTEMP TWL4030_MADC_ADCIN1 | ||
709 | +#define TWL4030_MADC_VBUS TWL4030_MADC_ADCIN8 | ||
710 | +#define TWL4030_MADC_VBKB TWL4030_MADC_ADCIN9 | ||
711 | +#define TWL4030_MADC_ICHG TWL4030_MADC_ADCIN10 | ||
712 | +#define TWL4030_MADC_VCHG TWL4030_MADC_ADCIN11 | ||
713 | +#define TWL4030_MADC_VBAT TWL4030_MADC_ADCIN12 | ||
714 | + | ||
715 | +/* BCI related - XXX To be moved elsewhere */ | ||
716 | +#define TWL4030_BCI_BCICTL1 0x23 | ||
717 | +#define TWL4030_BCI_MESBAT (1<<1) | ||
718 | +#define TWL4030_BCI_TYPEN (1<<4) | ||
719 | +#define TWL4030_BCI_ITHEN (1<<3) | ||
720 | + | ||
721 | +/* USB related - XXX To be moved elsewhere */ | ||
722 | +#define TWL4030_USB_CARKIT_ANA_CTRL 0xBB | ||
723 | +#define TWL4030_USB_SEL_MADC_MCPC (1<<3) | ||
724 | + | ||
725 | +#define TWL4030_MADC_IOC_MAGIC '`' | ||
726 | +#define TWL4030_MADC_IOCX_ADC_RAW_READ _IO(TWL4030_MADC_IOC_MAGIC, 0) | ||
727 | + | ||
728 | +struct twl4030_madc_user_parms { | ||
729 | + int channel; | ||
730 | + int average; | ||
731 | + int status; | ||
732 | + u16 result; | ||
733 | +}; | ||
734 | + | ||
735 | +int twl4030_madc_conversion(struct twl4030_madc_request *conv); | ||
736 | + | ||
737 | +#endif | ||
738 | -- | ||
739 | 1.6.6.1 | ||
740 | |||