summaryrefslogtreecommitdiffstats
path: root/extras/recipes-kernel/linux/linux-omap-psp-2.6.32/0019-drivers-mfd-add-twl4030-madc-driver.patch
diff options
context:
space:
mode:
Diffstat (limited to 'extras/recipes-kernel/linux/linux-omap-psp-2.6.32/0019-drivers-mfd-add-twl4030-madc-driver.patch')
-rw-r--r--extras/recipes-kernel/linux/linux-omap-psp-2.6.32/0019-drivers-mfd-add-twl4030-madc-driver.patch601
1 files changed, 601 insertions, 0 deletions
diff --git a/extras/recipes-kernel/linux/linux-omap-psp-2.6.32/0019-drivers-mfd-add-twl4030-madc-driver.patch b/extras/recipes-kernel/linux/linux-omap-psp-2.6.32/0019-drivers-mfd-add-twl4030-madc-driver.patch
new file mode 100644
index 00000000..55c5e191
--- /dev/null
+++ b/extras/recipes-kernel/linux/linux-omap-psp-2.6.32/0019-drivers-mfd-add-twl4030-madc-driver.patch
@@ -0,0 +1,601 @@
1From b9270dc07b1b66cce33580bca6276c20f1bf68d2 Mon Sep 17 00:00:00 2001
2From: Steve Sakoman <steve@sakoman.com>
3Date: Wed, 19 Jan 2011 16:06:42 +0100
4Subject: [PATCH 19/45] drivers: mfd: add twl4030 madc driver
5
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
12
13diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
14index 306b346..6221146 100644
15--- a/drivers/mfd/Kconfig
16+++ b/drivers/mfd/Kconfig
17@@ -148,6 +148,27 @@ config TWL4030_CODEC
18 default n
19
20
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
45diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
46index 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
54
55 obj-$(CONFIG_TPS65910_CORE) += tps65910-core.o
56
57diff --git a/drivers/mfd/twl4030-madc.c b/drivers/mfd/twl4030-madc.c
58new file mode 100644
59index 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+ &regval, 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+ &regval, 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+ &regval, 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--
6001.6.6.1
601