aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
authorMisael Lopez Cruz <misael.lopez@ti.com>2011-04-27 03:14:07 -0400
committerPeter Ujfalusi <peter.ujfalusi@ti.com>2011-07-04 12:34:37 -0400
commitf19b2823f82499c60ec15d5fe8783193d77e3043 (patch)
tree75148aed2558ec27ca87cf94e7ec3f398cd77e96 /drivers/mfd
parent4ae6df5e1018796ce260be59b2c603bd0f9faa94 (diff)
mfd: twl6040: Add initial support
TWL6040 IC provides analog high-end audio codec functions for handset applications. It contains several audio analog inputs and outputs as well as vibrator support. It's connected to the host processor via PDM interface for audio data communication. The audio modules are controlled by internal registers that can be accessed by I2C and PDM interface. TWL6040 MFD will be registered as a child of TWL-CORE, and will have two children of its own: twl6040-codec and twl6040-vibra. This driver is based on TWL4030 and WM8350 MFD drivers. Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com> Signed-off-by: Peter Ujfalusi <peter.ujfalusi@ti.com> Acked-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig6
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/twl-core.c5
-rw-r--r--drivers/mfd/twl6040-core.c596
-rw-r--r--drivers/mfd/twl6040-irq.c205
5 files changed, 810 insertions, 3 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 3a6f76aac008..ac6b4ae757cb 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -233,6 +233,12 @@ config TWL6030_PWM
233 Say yes here if you want support for TWL6030 PWM. 233 Say yes here if you want support for TWL6030 PWM.
234 This is used to control charging LED brightness. 234 This is used to control charging LED brightness.
235 235
236config TWL6040_CORE
237 bool
238 depends on TWL4030_CORE && GENERIC_HARDIRQS
239 select MFD_CORE
240 default n
241
236config MFD_STMPE 242config MFD_STMPE
237 bool "Support STMicroelectronics STMPE" 243 bool "Support STMicroelectronics STMPE"
238 depends on I2C=y && GENERIC_HARDIRQS 244 depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4cf946537e39..41f3b61ee9a1 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
42obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o 42obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o
43obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o 43obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o
44obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o 44obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o
45obj-$(CONFIG_TWL6040_CORE) += twl6040-core.o twl6040-irq.o
45 46
46obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o 47obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
47 48
diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c
index f9d7880fd638..a2eddc70995c 100644
--- a/drivers/mfd/twl-core.c
+++ b/drivers/mfd/twl-core.c
@@ -110,7 +110,7 @@
110#endif 110#endif
111 111
112#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\ 112#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\
113 defined(CONFIG_SND_SOC_TWL6040) || defined(CONFIG_SND_SOC_TWL6040_MODULE) 113 defined(CONFIG_TWL6040_CORE) || defined(CONFIG_TWL6040_CORE_MODULE)
114#define twl_has_codec() true 114#define twl_has_codec() true
115#else 115#else
116#define twl_has_codec() false 116#define twl_has_codec() false
@@ -824,10 +824,9 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
824 return PTR_ERR(child); 824 return PTR_ERR(child);
825 } 825 }
826 826
827 /* Phoenix codec driver is probed directly atm */
828 if (twl_has_codec() && pdata->audio && twl_class_is_6030()) { 827 if (twl_has_codec() && pdata->audio && twl_class_is_6030()) {
829 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; 828 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
830 child = add_child(sub_chip_id, "twl6040-codec", 829 child = add_child(sub_chip_id, "twl6040",
831 pdata->audio, sizeof(*pdata->audio), 830 pdata->audio, sizeof(*pdata->audio),
832 false, 0, 0); 831 false, 0, 0);
833 if (IS_ERR(child)) 832 if (IS_ERR(child))
diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c
new file mode 100644
index 000000000000..57ae36bae807
--- /dev/null
+++ b/drivers/mfd/twl6040-core.c
@@ -0,0 +1,596 @@
1/*
2 * MFD driver for TWL6040 audio device
3 *
4 * Authors: Misael Lopez Cruz <misael.lopez@ti.com>
5 * Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
6 * Peter Ujfalusi <peter.ujfalusi@ti.com>
7 *
8 * Copyright: (C) 2011 Texas Instruments, Inc.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA
23 *
24 */
25
26#include <linux/module.h>
27#include <linux/types.h>
28#include <linux/slab.h>
29#include <linux/kernel.h>
30#include <linux/platform_device.h>
31#include <linux/gpio.h>
32#include <linux/delay.h>
33#include <linux/i2c/twl.h>
34#include <linux/mfd/core.h>
35#include <linux/mfd/twl6040.h>
36
37static struct platform_device *twl6040_dev;
38
39int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
40{
41 int ret;
42 u8 val = 0;
43
44 mutex_lock(&twl6040->io_mutex);
45 ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
46 if (ret < 0) {
47 mutex_unlock(&twl6040->io_mutex);
48 return ret;
49 }
50 mutex_unlock(&twl6040->io_mutex);
51
52 return val;
53}
54EXPORT_SYMBOL(twl6040_reg_read);
55
56int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
57{
58 int ret;
59
60 mutex_lock(&twl6040->io_mutex);
61 ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
62 mutex_unlock(&twl6040->io_mutex);
63
64 return ret;
65}
66EXPORT_SYMBOL(twl6040_reg_write);
67
68int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
69{
70 int ret;
71 u8 val;
72
73 mutex_lock(&twl6040->io_mutex);
74 ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
75 if (ret)
76 goto out;
77
78 val |= mask;
79 ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
80out:
81 mutex_unlock(&twl6040->io_mutex);
82 return ret;
83}
84EXPORT_SYMBOL(twl6040_set_bits);
85
86int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
87{
88 int ret;
89 u8 val;
90
91 mutex_lock(&twl6040->io_mutex);
92 ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg);
93 if (ret)
94 goto out;
95
96 val &= ~mask;
97 ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg);
98out:
99 mutex_unlock(&twl6040->io_mutex);
100 return ret;
101}
102EXPORT_SYMBOL(twl6040_clear_bits);
103
104/* twl6040 codec manual power-up sequence */
105static int twl6040_power_up(struct twl6040 *twl6040)
106{
107 u8 ldoctl, ncpctl, lppllctl;
108 int ret;
109
110 /* enable high-side LDO, reference system and internal oscillator */
111 ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA;
112 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
113 if (ret)
114 return ret;
115 usleep_range(10000, 10500);
116
117 /* enable negative charge pump */
118 ncpctl = TWL6040_NCPENA;
119 ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
120 if (ret)
121 goto ncp_err;
122 usleep_range(1000, 1500);
123
124 /* enable low-side LDO */
125 ldoctl |= TWL6040_LSLDOENA;
126 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
127 if (ret)
128 goto lsldo_err;
129 usleep_range(1000, 1500);
130
131 /* enable low-power PLL */
132 lppllctl = TWL6040_LPLLENA;
133 ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
134 if (ret)
135 goto lppll_err;
136 usleep_range(5000, 5500);
137
138 /* disable internal oscillator */
139 ldoctl &= ~TWL6040_OSCENA;
140 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
141 if (ret)
142 goto osc_err;
143
144 return 0;
145
146osc_err:
147 lppllctl &= ~TWL6040_LPLLENA;
148 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
149lppll_err:
150 ldoctl &= ~TWL6040_LSLDOENA;
151 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
152lsldo_err:
153 ncpctl &= ~TWL6040_NCPENA;
154 twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
155ncp_err:
156 ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
157 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
158
159 return ret;
160}
161
162/* twl6040 manual power-down sequence */
163static void twl6040_power_down(struct twl6040 *twl6040)
164{
165 u8 ncpctl, ldoctl, lppllctl;
166
167 ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
168 ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
169 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
170
171 /* enable internal oscillator */
172 ldoctl |= TWL6040_OSCENA;
173 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
174 usleep_range(1000, 1500);
175
176 /* disable low-power PLL */
177 lppllctl &= ~TWL6040_LPLLENA;
178 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
179
180 /* disable low-side LDO */
181 ldoctl &= ~TWL6040_LSLDOENA;
182 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
183
184 /* disable negative charge pump */
185 ncpctl &= ~TWL6040_NCPENA;
186 twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
187
188 /* disable high-side LDO, reference system and internal oscillator */
189 ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
190 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
191}
192
193static irqreturn_t twl6040_naudint_handler(int irq, void *data)
194{
195 struct twl6040 *twl6040 = data;
196 u8 intid, status;
197
198 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
199
200 if (intid & TWL6040_READYINT)
201 complete(&twl6040->ready);
202
203 if (intid & TWL6040_THINT) {
204 status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
205 if (status & TWL6040_TSHUTDET) {
206 dev_warn(&twl6040_dev->dev,
207 "Thermal shutdown, powering-off");
208 twl6040_power(twl6040, 0);
209 } else {
210 dev_warn(&twl6040_dev->dev,
211 "Leaving thermal shutdown, powering-on");
212 twl6040_power(twl6040, 1);
213 }
214 }
215
216 return IRQ_HANDLED;
217}
218
219static int twl6040_power_up_completion(struct twl6040 *twl6040,
220 int naudint)
221{
222 int time_left;
223 u8 intid;
224
225 time_left = wait_for_completion_timeout(&twl6040->ready,
226 msecs_to_jiffies(144));
227 if (!time_left) {
228 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
229 if (!(intid & TWL6040_READYINT)) {
230 dev_err(&twl6040_dev->dev,
231 "timeout waiting for READYINT\n");
232 return -ETIMEDOUT;
233 }
234 }
235
236 return 0;
237}
238
239int twl6040_power(struct twl6040 *twl6040, int on)
240{
241 int audpwron = twl6040->audpwron;
242 int naudint = twl6040->irq;
243 int ret = 0;
244
245 mutex_lock(&twl6040->mutex);
246
247 if (on) {
248 /* already powered-up */
249 if (twl6040->power_count++)
250 goto out;
251
252 if (gpio_is_valid(audpwron)) {
253 /* use AUDPWRON line */
254 gpio_set_value(audpwron, 1);
255 /* wait for power-up completion */
256 ret = twl6040_power_up_completion(twl6040, naudint);
257 if (ret) {
258 dev_err(&twl6040_dev->dev,
259 "automatic power-down failed\n");
260 twl6040->power_count = 0;
261 goto out;
262 }
263 } else {
264 /* use manual power-up sequence */
265 ret = twl6040_power_up(twl6040);
266 if (ret) {
267 dev_err(&twl6040_dev->dev,
268 "manual power-up failed\n");
269 twl6040->power_count = 0;
270 goto out;
271 }
272 }
273 twl6040->pll = TWL6040_LPPLL_ID;
274 twl6040->sysclk = 19200000;
275 } else {
276 /* already powered-down */
277 if (!twl6040->power_count) {
278 dev_err(&twl6040_dev->dev,
279 "device is already powered-off\n");
280 ret = -EPERM;
281 goto out;
282 }
283
284 if (--twl6040->power_count)
285 goto out;
286
287 if (gpio_is_valid(audpwron)) {
288 /* use AUDPWRON line */
289 gpio_set_value(audpwron, 0);
290
291 /* power-down sequence latency */
292 usleep_range(500, 700);
293 } else {
294 /* use manual power-down sequence */
295 twl6040_power_down(twl6040);
296 }
297 twl6040->pll = TWL6040_NOPLL_ID;
298 twl6040->sysclk = 0;
299 }
300
301out:
302 mutex_unlock(&twl6040->mutex);
303 return ret;
304}
305EXPORT_SYMBOL(twl6040_power);
306
307int twl6040_set_pll(struct twl6040 *twl6040, enum twl6040_pll_id id,
308 unsigned int freq_in, unsigned int freq_out)
309{
310 u8 hppllctl, lppllctl;
311 int ret = 0;
312
313 mutex_lock(&twl6040->mutex);
314
315 hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL);
316 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
317
318 switch (id) {
319 case TWL6040_LPPLL_ID:
320 /* low-power PLL divider */
321 switch (freq_out) {
322 case 17640000:
323 lppllctl |= TWL6040_LPLLFIN;
324 break;
325 case 19200000:
326 lppllctl &= ~TWL6040_LPLLFIN;
327 break;
328 default:
329 dev_err(&twl6040_dev->dev,
330 "freq_out %d not supported\n", freq_out);
331 ret = -EINVAL;
332 goto pll_out;
333 }
334 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
335
336 switch (freq_in) {
337 case 32768:
338 lppllctl |= TWL6040_LPLLENA;
339 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
340 lppllctl);
341 mdelay(5);
342 lppllctl &= ~TWL6040_HPLLSEL;
343 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
344 lppllctl);
345 hppllctl &= ~TWL6040_HPLLENA;
346 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
347 hppllctl);
348 break;
349 default:
350 dev_err(&twl6040_dev->dev,
351 "freq_in %d not supported\n", freq_in);
352 ret = -EINVAL;
353 goto pll_out;
354 }
355
356 twl6040->pll = TWL6040_LPPLL_ID;
357 break;
358 case TWL6040_HPPLL_ID:
359 /* high-performance PLL can provide only 19.2 MHz */
360 if (freq_out != 19200000) {
361 dev_err(&twl6040_dev->dev,
362 "freq_out %d not supported\n", freq_out);
363 ret = -EINVAL;
364 goto pll_out;
365 }
366
367 hppllctl &= ~TWL6040_MCLK_MSK;
368
369 switch (freq_in) {
370 case 12000000:
371 /* PLL enabled, active mode */
372 hppllctl |= TWL6040_MCLK_12000KHZ |
373 TWL6040_HPLLENA;
374 break;
375 case 19200000:
376 /*
377 * PLL disabled
378 * (enable PLL if MCLK jitter quality
379 * doesn't meet specification)
380 */
381 hppllctl |= TWL6040_MCLK_19200KHZ;
382 break;
383 case 26000000:
384 /* PLL enabled, active mode */
385 hppllctl |= TWL6040_MCLK_26000KHZ |
386 TWL6040_HPLLENA;
387 break;
388 case 38400000:
389 /* PLL enabled, active mode */
390 hppllctl |= TWL6040_MCLK_38400KHZ |
391 TWL6040_HPLLENA;
392 break;
393 default:
394 dev_err(&twl6040_dev->dev,
395 "freq_in %d not supported\n", freq_in);
396 ret = -EINVAL;
397 goto pll_out;
398 }
399
400 /* enable clock slicer to ensure input waveform is square */
401 hppllctl |= TWL6040_HPLLSQRENA;
402
403 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl);
404 usleep_range(500, 700);
405 lppllctl |= TWL6040_HPLLSEL;
406 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
407 lppllctl &= ~TWL6040_LPLLENA;
408 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
409
410 twl6040->pll = TWL6040_HPPLL_ID;
411 break;
412 default:
413 dev_err(&twl6040_dev->dev, "unknown pll id %d\n", id);
414 ret = -EINVAL;
415 goto pll_out;
416 }
417
418 twl6040->sysclk = freq_out;
419
420pll_out:
421 mutex_unlock(&twl6040->mutex);
422 return ret;
423}
424EXPORT_SYMBOL(twl6040_set_pll);
425
426enum twl6040_pll_id twl6040_get_pll(struct twl6040 *twl6040)
427{
428 return twl6040->pll;
429}
430EXPORT_SYMBOL(twl6040_get_pll);
431
432unsigned int twl6040_get_sysclk(struct twl6040 *twl6040)
433{
434 return twl6040->sysclk;
435}
436EXPORT_SYMBOL(twl6040_get_sysclk);
437
438static int __devinit twl6040_probe(struct platform_device *pdev)
439{
440 struct twl4030_audio_data *pdata = pdev->dev.platform_data;
441 struct twl6040 *twl6040;
442 struct mfd_cell *cell = NULL;
443 int ret, children = 0;
444
445 if (!pdata) {
446 dev_err(&pdev->dev, "Platform data is missing\n");
447 return -EINVAL;
448 }
449
450 twl6040 = kzalloc(sizeof(struct twl6040), GFP_KERNEL);
451 if (!twl6040)
452 return -ENOMEM;
453
454 platform_set_drvdata(pdev, twl6040);
455
456 twl6040_dev = pdev;
457 twl6040->dev = &pdev->dev;
458 twl6040->audpwron = pdata->audpwron_gpio;
459 twl6040->irq = pdata->naudint_irq;
460 twl6040->irq_base = pdata->irq_base;
461
462 mutex_init(&twl6040->mutex);
463 mutex_init(&twl6040->io_mutex);
464 init_completion(&twl6040->ready);
465
466 twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
467
468 if (gpio_is_valid(twl6040->audpwron)) {
469 ret = gpio_request(twl6040->audpwron, "audpwron");
470 if (ret)
471 goto gpio1_err;
472
473 ret = gpio_direction_output(twl6040->audpwron, 0);
474 if (ret)
475 goto gpio2_err;
476 }
477
478 /* ERRATA: Automatic power-up is not possible in ES1.0 */
479 if (twl6040->rev == TWL6040_REV_ES1_0)
480 twl6040->audpwron = -EINVAL;
481
482 if (twl6040->irq) {
483 /* codec interrupt */
484 ret = twl6040_irq_init(twl6040);
485 if (ret)
486 goto gpio2_err;
487
488 ret = twl6040_request_irq(twl6040, TWL6040_IRQ_READY,
489 twl6040_naudint_handler, 0,
490 "twl6040_irq_ready", twl6040);
491 if (ret) {
492 dev_err(twl6040->dev, "READY IRQ request failed: %d\n",
493 ret);
494 goto irq_err;
495 }
496 }
497
498 /* dual-access registers controlled by I2C only */
499 twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL);
500
501 if (pdata->codec) {
502 cell = &twl6040->cells[children];
503 cell->name = "twl6040-codec";
504 /* The codec expects the twl4030_audio_data as platform data */
505 cell->platform_data = pdata;
506 cell->pdata_size = sizeof(*pdata);
507 children++;
508 }
509
510 if (pdata->vibra) {
511 cell = &twl6040->cells[children];
512 cell->name = "twl6040-vibra";
513 cell->platform_data = pdata->vibra;
514 cell->pdata_size = sizeof(*pdata->vibra);
515 children++;
516 }
517
518 if (children) {
519 ret = mfd_add_devices(&pdev->dev, pdev->id, twl6040->cells,
520 children, NULL, 0);
521 if (ret)
522 goto mfd_err;
523 } else {
524 dev_err(&pdev->dev, "No platform data found for children\n");
525 ret = -ENODEV;
526 goto mfd_err;
527 }
528
529 return 0;
530
531mfd_err:
532 if (twl6040->irq)
533 twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
534irq_err:
535 if (twl6040->irq)
536 twl6040_irq_exit(twl6040);
537gpio2_err:
538 if (gpio_is_valid(twl6040->audpwron))
539 gpio_free(twl6040->audpwron);
540gpio1_err:
541 platform_set_drvdata(pdev, NULL);
542 kfree(twl6040);
543 twl6040_dev = NULL;
544 return ret;
545}
546
547static int __devexit twl6040_remove(struct platform_device *pdev)
548{
549 struct twl6040 *twl6040 = platform_get_drvdata(pdev);
550
551 if (twl6040->power_count)
552 twl6040_power(twl6040, 0);
553
554 if (gpio_is_valid(twl6040->audpwron))
555 gpio_free(twl6040->audpwron);
556
557 twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
558
559 if (twl6040->irq)
560 twl6040_irq_exit(twl6040);
561
562 mfd_remove_devices(&pdev->dev);
563 platform_set_drvdata(pdev, NULL);
564 kfree(twl6040);
565 twl6040_dev = NULL;
566
567 return 0;
568}
569
570static struct platform_driver twl6040_driver = {
571 .probe = twl6040_probe,
572 .remove = __devexit_p(twl6040_remove),
573 .driver = {
574 .owner = THIS_MODULE,
575 .name = "twl6040",
576 },
577};
578
579static int __devinit twl6040_init(void)
580{
581 return platform_driver_register(&twl6040_driver);
582}
583module_init(twl6040_init);
584
585static void __devexit twl6040_exit(void)
586{
587 platform_driver_unregister(&twl6040_driver);
588}
589
590module_exit(twl6040_exit);
591
592MODULE_DESCRIPTION("TWL6040 MFD");
593MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
594MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
595MODULE_LICENSE("GPL");
596MODULE_ALIAS("platform:twl6040");
diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c
new file mode 100644
index 000000000000..938053541520
--- /dev/null
+++ b/drivers/mfd/twl6040-irq.c
@@ -0,0 +1,205 @@
1/*
2 * Interrupt controller support for TWL6040
3 *
4 * Author: Misael Lopez Cruz <misael.lopez@ti.com>
5 *
6 * Copyright: (C) 2011 Texas Instruments, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA
21 *
22 */
23
24#include <linux/kernel.h>
25#include <linux/module.h>
26#include <linux/irq.h>
27#include <linux/interrupt.h>
28#include <linux/mfd/core.h>
29#include <linux/mfd/twl6040.h>
30
31struct twl6040_irq_data {
32 int mask;
33 int status;
34};
35
36static struct twl6040_irq_data twl6040_irqs[] = {
37 {
38 .mask = TWL6040_THMSK,
39 .status = TWL6040_THINT,
40 },
41 {
42 .mask = TWL6040_PLUGMSK,
43 .status = TWL6040_PLUGINT | TWL6040_UNPLUGINT,
44 },
45 {
46 .mask = TWL6040_HOOKMSK,
47 .status = TWL6040_HOOKINT,
48 },
49 {
50 .mask = TWL6040_HFMSK,
51 .status = TWL6040_HFINT,
52 },
53 {
54 .mask = TWL6040_VIBMSK,
55 .status = TWL6040_VIBINT,
56 },
57 {
58 .mask = TWL6040_READYMSK,
59 .status = TWL6040_READYINT,
60 },
61};
62
63static inline
64struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040,
65 int irq)
66{
67 return &twl6040_irqs[irq - twl6040->irq_base];
68}
69
70static void twl6040_irq_lock(struct irq_data *data)
71{
72 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
73
74 mutex_lock(&twl6040->irq_mutex);
75}
76
77static void twl6040_irq_sync_unlock(struct irq_data *data)
78{
79 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
80
81 /* write back to hardware any change in irq mask */
82 if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) {
83 twl6040->irq_masks_cache = twl6040->irq_masks_cur;
84 twl6040_reg_write(twl6040, TWL6040_REG_INTMR,
85 twl6040->irq_masks_cur);
86 }
87
88 mutex_unlock(&twl6040->irq_mutex);
89}
90
91static void twl6040_irq_enable(struct irq_data *data)
92{
93 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
94 struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
95 data->irq);
96
97 twl6040->irq_masks_cur &= ~irq_data->mask;
98}
99
100static void twl6040_irq_disable(struct irq_data *data)
101{
102 struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data);
103 struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040,
104 data->irq);
105
106 twl6040->irq_masks_cur |= irq_data->mask;
107}
108
109static struct irq_chip twl6040_irq_chip = {
110 .name = "twl6040",
111 .irq_bus_lock = twl6040_irq_lock,
112 .irq_bus_sync_unlock = twl6040_irq_sync_unlock,
113 .irq_enable = twl6040_irq_enable,
114 .irq_disable = twl6040_irq_disable,
115};
116
117static irqreturn_t twl6040_irq_thread(int irq, void *data)
118{
119 struct twl6040 *twl6040 = data;
120 u8 intid;
121 int i;
122
123 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
124
125 /* apply masking and report (backwards to handle READYINT first) */
126 for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) {
127 if (twl6040->irq_masks_cur & twl6040_irqs[i].mask)
128 intid &= ~twl6040_irqs[i].status;
129 if (intid & twl6040_irqs[i].status)
130 handle_nested_irq(twl6040->irq_base + i);
131 }
132
133 /* ack unmasked irqs */
134 twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid);
135
136 return IRQ_HANDLED;
137}
138
139int twl6040_irq_init(struct twl6040 *twl6040)
140{
141 int cur_irq, ret;
142 u8 val;
143
144 mutex_init(&twl6040->irq_mutex);
145
146 /* mask the individual interrupt sources */
147 twl6040->irq_masks_cur = TWL6040_ALLINT_MSK;
148 twl6040->irq_masks_cache = TWL6040_ALLINT_MSK;
149 twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK);
150
151 if (!twl6040->irq) {
152 dev_warn(twl6040->dev,
153 "no interrupt specified, no interrupts\n");
154 twl6040->irq_base = 0;
155 return 0;
156 }
157
158 if (!twl6040->irq_base) {
159 dev_err(twl6040->dev,
160 "no interrupt base specified, no interrupts\n");
161 return 0;
162 }
163
164 /* Register them with genirq */
165 for (cur_irq = twl6040->irq_base;
166 cur_irq < twl6040->irq_base + ARRAY_SIZE(twl6040_irqs);
167 cur_irq++) {
168 irq_set_chip_data(cur_irq, twl6040);
169 irq_set_chip_and_handler(cur_irq, &twl6040_irq_chip,
170 handle_level_irq);
171 irq_set_nested_thread(cur_irq, 1);
172
173 /* ARM needs us to explicitly flag the IRQ as valid
174 * and will set them noprobe when we do so. */
175#ifdef CONFIG_ARM
176 set_irq_flags(cur_irq, IRQF_VALID);
177#else
178 irq_set_noprobe(cur_irq);
179#endif
180 }
181
182 ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread,
183 IRQF_ONESHOT, "twl6040", twl6040);
184 if (ret) {
185 dev_err(twl6040->dev, "failed to request IRQ %d: %d\n",
186 twl6040->irq, ret);
187 return ret;
188 }
189
190 /* reset interrupts */
191 val = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
192
193 /* interrupts cleared on write */
194 twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE);
195
196 return 0;
197}
198EXPORT_SYMBOL(twl6040_irq_init);
199
200void twl6040_irq_exit(struct twl6040 *twl6040)
201{
202 if (twl6040->irq)
203 free_irq(twl6040->irq, twl6040);
204}
205EXPORT_SYMBOL(twl6040_irq_exit);