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