diff options
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.patch | 601 |
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 @@ | |||
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 | ||
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 | |||
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 | ||
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 | ||
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 | ||
54 | |||
55 | obj-$(CONFIG_TPS65910_CORE) += tps65910-core.o | ||
56 | |||
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 | ||
601 | |||