aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisael Lopez Cruz <misael.lopez@ti.com>2011-07-24 15:59:35 -0400
committerPaolo Pisati <paolo.pisati@canonical.com>2012-08-17 04:18:20 -0400
commitc5c90778bc01c24f7db90e828509e66bd1a4547c (patch)
tree3d18909712d6eaab900344af9479578dc5c671d9
parent6bce1fb3ff46fb0b764f9a3e34580702289e19e4 (diff)
Subject: [PATCH 043/104] 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>
-rw-r--r--arch/arm/plat-omap/include/plat/irqs.h12
-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.c602
-rw-r--r--drivers/mfd/twl6040-irq.c205
-rw-r--r--include/linux/i2c/twl.h1
-rw-r--r--include/linux/mfd/twl6040.h261
8 files changed, 1088 insertions, 5 deletions
diff --git a/arch/arm/plat-omap/include/plat/irqs.h b/arch/arm/plat-omap/include/plat/irqs.h
index 5a25098ea7e..2cfba5149ea 100644
--- a/arch/arm/plat-omap/include/plat/irqs.h
+++ b/arch/arm/plat-omap/include/plat/irqs.h
@@ -407,11 +407,19 @@
407#endif 407#endif
408#define TWL6030_IRQ_END (TWL6030_IRQ_BASE + TWL6030_BASE_NR_IRQS) 408#define TWL6030_IRQ_END (TWL6030_IRQ_BASE + TWL6030_BASE_NR_IRQS)
409 409
410#define TWL6040_CODEC_IRQ_BASE TWL6030_IRQ_END
411#ifdef CONFIG_TWL6040_CODEC
412#define TWL6040_CODEC_NR_IRQS 6
413#else
414#define TWL6040_CODEC_NR_IRQS 0
415#endif
416#define TWL6040_CODEC_IRQ_END (TWL6040_CODEC_IRQ_BASE + TWL6040_CODEC_NR_IRQS)
417
410/* Total number of interrupts depends on the enabled blocks above */ 418/* Total number of interrupts depends on the enabled blocks above */
411#if (TWL4030_GPIO_IRQ_END > TWL6030_IRQ_END) 419#if (TWL4030_GPIO_IRQ_END > TWL6040_CODEC_IRQ_END)
412#define TWL_IRQ_END TWL4030_GPIO_IRQ_END 420#define TWL_IRQ_END TWL4030_GPIO_IRQ_END
413#else 421#else
414#define TWL_IRQ_END TWL6030_IRQ_END 422#define TWL_IRQ_END TWL6040_CODEC_IRQ_END
415#endif 423#endif
416 424
417/* GPMC related */ 425/* GPMC related */
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 80f56fed626..37b83eb6d70 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 b977e09952e..22a280fcb70 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 0840752cd8c..a773d356971 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
@@ -825,10 +825,9 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
825 return PTR_ERR(child); 825 return PTR_ERR(child);
826 } 826 }
827 827
828 /* Phoenix codec driver is probed directly atm */
829 if (twl_has_codec() && pdata->audio && twl_class_is_6030()) { 828 if (twl_has_codec() && pdata->audio && twl_class_is_6030()) {
830 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; 829 sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;
831 child = add_child(sub_chip_id, "twl6040-codec", 830 child = add_child(sub_chip_id, "twl6040",
832 pdata->audio, sizeof(*pdata->audio), 831 pdata->audio, sizeof(*pdata->audio),
833 false, 0, 0); 832 false, 0, 0);
834 if (IS_ERR(child)) 833 if (IS_ERR(child))
diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c
new file mode 100644
index 00000000000..195613c7dcc
--- /dev/null
+++ b/drivers/mfd/twl6040-core.c
@@ -0,0 +1,602 @@
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_is_powered(struct twl6040 *twl6040)
308{
309 return twl6040->power_count;
310}
311EXPORT_SYMBOL(twl6040_is_powered);
312
313int twl6040_set_pll(struct twl6040 *twl6040, enum twl6040_pll_id id,
314 unsigned int freq_in, unsigned int freq_out)
315{
316 u8 hppllctl, lppllctl;
317 int ret = 0;
318
319 mutex_lock(&twl6040->mutex);
320
321 hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL);
322 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
323
324 switch (id) {
325 case TWL6040_LPPLL_ID:
326 /* low-power PLL divider */
327 switch (freq_out) {
328 case 17640000:
329 lppllctl |= TWL6040_LPLLFIN;
330 break;
331 case 19200000:
332 lppllctl &= ~TWL6040_LPLLFIN;
333 break;
334 default:
335 dev_err(&twl6040_dev->dev,
336 "freq_out %d not supported\n", freq_out);
337 ret = -EINVAL;
338 goto pll_out;
339 }
340 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
341
342 switch (freq_in) {
343 case 32768:
344 lppllctl |= TWL6040_LPLLENA;
345 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
346 lppllctl);
347 mdelay(5);
348 lppllctl &= ~TWL6040_HPLLSEL;
349 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
350 lppllctl);
351 hppllctl &= ~TWL6040_HPLLENA;
352 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
353 hppllctl);
354 break;
355 default:
356 dev_err(&twl6040_dev->dev,
357 "freq_in %d not supported\n", freq_in);
358 ret = -EINVAL;
359 goto pll_out;
360 }
361
362 twl6040->pll = TWL6040_LPPLL_ID;
363 break;
364 case TWL6040_HPPLL_ID:
365 /* high-performance PLL can provide only 19.2 MHz */
366 if (freq_out != 19200000) {
367 dev_err(&twl6040_dev->dev,
368 "freq_out %d not supported\n", freq_out);
369 ret = -EINVAL;
370 goto pll_out;
371 }
372
373 hppllctl &= ~TWL6040_MCLK_MSK;
374
375 switch (freq_in) {
376 case 12000000:
377 /* PLL enabled, active mode */
378 hppllctl |= TWL6040_MCLK_12000KHZ |
379 TWL6040_HPLLENA;
380 break;
381 case 19200000:
382 /*
383 * PLL disabled
384 * (enable PLL if MCLK jitter quality
385 * doesn't meet specification)
386 */
387 hppllctl |= TWL6040_MCLK_19200KHZ;
388 break;
389 case 26000000:
390 /* PLL enabled, active mode */
391 hppllctl |= TWL6040_MCLK_26000KHZ |
392 TWL6040_HPLLENA;
393 break;
394 case 38400000:
395 /* PLL enabled, active mode */
396 hppllctl |= TWL6040_MCLK_38400KHZ |
397 TWL6040_HPLLENA;
398 break;
399 default:
400 dev_err(&twl6040_dev->dev,
401 "freq_in %d not supported\n", freq_in);
402 ret = -EINVAL;
403 goto pll_out;
404 }
405
406 /* enable clock slicer to ensure input waveform is square */
407 hppllctl |= TWL6040_HPLLSQRENA;
408
409 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl);
410 usleep_range(500, 700);
411 lppllctl |= TWL6040_HPLLSEL;
412 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
413 lppllctl &= ~TWL6040_LPLLENA;
414 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
415
416 twl6040->pll = TWL6040_HPPLL_ID;
417 break;
418 default:
419 dev_err(&twl6040_dev->dev, "unknown pll id %d\n", id);
420 ret = -EINVAL;
421 goto pll_out;
422 }
423
424 twl6040->sysclk = freq_out;
425
426pll_out:
427 mutex_unlock(&twl6040->mutex);
428 return ret;
429}
430EXPORT_SYMBOL(twl6040_set_pll);
431
432enum twl6040_pll_id twl6040_get_pll(struct twl6040 *twl6040)
433{
434 return twl6040->pll;
435}
436EXPORT_SYMBOL(twl6040_get_pll);
437
438unsigned int twl6040_get_sysclk(struct twl6040 *twl6040)
439{
440 return twl6040->sysclk;
441}
442EXPORT_SYMBOL(twl6040_get_sysclk);
443
444static int __devinit twl6040_probe(struct platform_device *pdev)
445{
446 struct twl4030_audio_data *pdata = pdev->dev.platform_data;
447 struct twl6040 *twl6040;
448 struct mfd_cell *cell = NULL;
449 int ret, children = 0;
450
451 if (!pdata) {
452 dev_err(&pdev->dev, "Platform data is missing\n");
453 return -EINVAL;
454 }
455
456 twl6040 = kzalloc(sizeof(struct twl6040), GFP_KERNEL);
457 if (!twl6040)
458 return -ENOMEM;
459
460 platform_set_drvdata(pdev, twl6040);
461
462 twl6040_dev = pdev;
463 twl6040->dev = &pdev->dev;
464 twl6040->audpwron = pdata->audpwron_gpio;
465 twl6040->irq = pdata->naudint_irq;
466 twl6040->irq_base = pdata->irq_base;
467
468 mutex_init(&twl6040->mutex);
469 mutex_init(&twl6040->io_mutex);
470 init_completion(&twl6040->ready);
471
472 twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
473
474 if (gpio_is_valid(twl6040->audpwron)) {
475 ret = gpio_request(twl6040->audpwron, "audpwron");
476 if (ret)
477 goto gpio1_err;
478
479 ret = gpio_direction_output(twl6040->audpwron, 0);
480 if (ret)
481 goto gpio2_err;
482 }
483
484 /* ERRATA: Automatic power-up is not possible in ES1.0 */
485 if (twl6040_get_rev(twl6040) == TWL6040_REV_ES1_0)
486 twl6040->audpwron = -EINVAL;
487
488 if (twl6040->irq) {
489 /* codec interrupt */
490 ret = twl6040_irq_init(twl6040);
491 if (ret)
492 goto gpio2_err;
493
494 ret = twl6040_request_irq(twl6040, TWL6040_IRQ_READY,
495 twl6040_naudint_handler, 0,
496 "twl6040_irq_ready", twl6040);
497 if (ret) {
498 dev_err(twl6040->dev, "READY IRQ request failed: %d\n",
499 ret);
500 goto irq_err;
501 }
502 }
503
504 /* dual-access registers controlled by I2C only */
505 twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL);
506
507 if (pdata->codec) {
508 cell = &twl6040->cells[children];
509 cell->name = "twl6040-codec";
510 /* The codec expects the twl4030_audio_data as platform data */
511 cell->platform_data = pdata;
512 cell->pdata_size = sizeof(*pdata);
513 children++;
514 }
515
516 if (pdata->vibra) {
517 cell = &twl6040->cells[children];
518 cell->name = "twl6040-vibra";
519 cell->platform_data = pdata->vibra;
520 cell->pdata_size = sizeof(*pdata->vibra);
521 children++;
522 }
523
524 if (children) {
525 ret = mfd_add_devices(&pdev->dev, pdev->id, twl6040->cells,
526 children, NULL, 0);
527 if (ret)
528 goto mfd_err;
529 } else {
530 dev_err(&pdev->dev, "No platform data found for children\n");
531 ret = -ENODEV;
532 goto mfd_err;
533 }
534
535 return 0;
536
537mfd_err:
538 if (twl6040->irq)
539 twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
540irq_err:
541 if (twl6040->irq)
542 twl6040_irq_exit(twl6040);
543gpio2_err:
544 if (gpio_is_valid(twl6040->audpwron))
545 gpio_free(twl6040->audpwron);
546gpio1_err:
547 platform_set_drvdata(pdev, NULL);
548 kfree(twl6040);
549 twl6040_dev = NULL;
550 return ret;
551}
552
553static int __devexit twl6040_remove(struct platform_device *pdev)
554{
555 struct twl6040 *twl6040 = platform_get_drvdata(pdev);
556
557 if (twl6040_is_powered(twl6040))
558 twl6040_power(twl6040, 0);
559
560 if (gpio_is_valid(twl6040->audpwron))
561 gpio_free(twl6040->audpwron);
562
563 twl6040_free_irq(twl6040, TWL6040_IRQ_READY, twl6040);
564
565 if (twl6040->irq)
566 twl6040_irq_exit(twl6040);
567
568 mfd_remove_devices(&pdev->dev);
569 platform_set_drvdata(pdev, NULL);
570 kfree(twl6040);
571 twl6040_dev = NULL;
572
573 return 0;
574}
575
576static struct platform_driver twl6040_driver = {
577 .probe = twl6040_probe,
578 .remove = __devexit_p(twl6040_remove),
579 .driver = {
580 .owner = THIS_MODULE,
581 .name = "twl6040",
582 },
583};
584
585static int __devinit twl6040_init(void)
586{
587 return platform_driver_register(&twl6040_driver);
588}
589module_init(twl6040_init);
590
591static void __devexit twl6040_exit(void)
592{
593 platform_driver_unregister(&twl6040_driver);
594}
595
596module_exit(twl6040_exit);
597
598MODULE_DESCRIPTION("TWL6040 MFD");
599MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
600MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
601MODULE_LICENSE("GPL");
602MODULE_ALIAS("platform:twl6040");
diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c
new file mode 100644
index 00000000000..93805354152
--- /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);
diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h
index e0aba2b92fa..ea5baa26922 100644
--- a/include/linux/i2c/twl.h
+++ b/include/linux/i2c/twl.h
@@ -679,6 +679,7 @@ struct twl4030_audio_data {
679 /* twl6040 */ 679 /* twl6040 */
680 int audpwron_gpio; /* audio power-on gpio */ 680 int audpwron_gpio; /* audio power-on gpio */
681 int naudint_irq; /* audio interrupt */ 681 int naudint_irq; /* audio interrupt */
682 unsigned int irq_base;
682}; 683};
683 684
684struct twl4030_platform_data { 685struct twl4030_platform_data {
diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h
new file mode 100644
index 00000000000..d384a014b76
--- /dev/null
+++ b/include/linux/mfd/twl6040.h
@@ -0,0 +1,261 @@
1/*
2 * MFD driver for twl6040
3 *
4 * Authors: Jorge Eduardo Candelaria <jorge.candelaria@ti.com>
5 * Misael Lopez Cruz <misael.lopez@ti.com>
6 *
7 * Copyright: (C) 2011 Texas Instruments, Inc.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25#ifndef __TWL6040_CODEC_H__
26#define __TWL6040_CODEC_H__
27
28#include <linux/interrupt.h>
29#include <linux/mfd/core.h>
30
31#define TWL6040_REG_ASICID 0x01
32#define TWL6040_REG_ASICREV 0x02
33#define TWL6040_REG_INTID 0x03
34#define TWL6040_REG_INTMR 0x04
35#define TWL6040_REG_NCPCTL 0x05
36#define TWL6040_REG_LDOCTL 0x06
37#define TWL6040_REG_HPPLLCTL 0x07
38#define TWL6040_REG_LPPLLCTL 0x08
39#define TWL6040_REG_LPPLLDIV 0x09
40#define TWL6040_REG_AMICBCTL 0x0A
41#define TWL6040_REG_DMICBCTL 0x0B
42#define TWL6040_REG_MICLCTL 0x0C
43#define TWL6040_REG_MICRCTL 0x0D
44#define TWL6040_REG_MICGAIN 0x0E
45#define TWL6040_REG_LINEGAIN 0x0F
46#define TWL6040_REG_HSLCTL 0x10
47#define TWL6040_REG_HSRCTL 0x11
48#define TWL6040_REG_HSGAIN 0x12
49#define TWL6040_REG_EARCTL 0x13
50#define TWL6040_REG_HFLCTL 0x14
51#define TWL6040_REG_HFLGAIN 0x15
52#define TWL6040_REG_HFRCTL 0x16
53#define TWL6040_REG_HFRGAIN 0x17
54#define TWL6040_REG_VIBCTLL 0x18
55#define TWL6040_REG_VIBDATL 0x19
56#define TWL6040_REG_VIBCTLR 0x1A
57#define TWL6040_REG_VIBDATR 0x1B
58#define TWL6040_REG_HKCTL1 0x1C
59#define TWL6040_REG_HKCTL2 0x1D
60#define TWL6040_REG_GPOCTL 0x1E
61#define TWL6040_REG_ALB 0x1F
62#define TWL6040_REG_DLB 0x20
63#define TWL6040_REG_TRIM1 0x28
64#define TWL6040_REG_TRIM2 0x29
65#define TWL6040_REG_TRIM3 0x2A
66#define TWL6040_REG_HSOTRIM 0x2B
67#define TWL6040_REG_HFOTRIM 0x2C
68#define TWL6040_REG_ACCCTL 0x2D
69#define TWL6040_REG_STATUS 0x2E
70
71#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1)
72
73#define TWL6040_VIOREGNUM 18
74#define TWL6040_VDDREGNUM 21
75
76/* INTID (0x03) fields */
77
78#define TWL6040_THINT 0x01
79#define TWL6040_PLUGINT 0x02
80#define TWL6040_UNPLUGINT 0x04
81#define TWL6040_HOOKINT 0x08
82#define TWL6040_HFINT 0x10
83#define TWL6040_VIBINT 0x20
84#define TWL6040_READYINT 0x40
85
86/* INTMR (0x04) fields */
87
88#define TWL6040_THMSK 0x01
89#define TWL6040_PLUGMSK 0x02
90#define TWL6040_HOOKMSK 0x08
91#define TWL6040_HFMSK 0x10
92#define TWL6040_VIBMSK 0x20
93#define TWL6040_READYMSK 0x40
94#define TWL6040_ALLINT_MSK 0x7B
95
96/* NCPCTL (0x05) fields */
97
98#define TWL6040_NCPENA 0x01
99#define TWL6040_NCPOPEN 0x40
100
101/* LDOCTL (0x06) fields */
102
103#define TWL6040_LSLDOENA 0x01
104#define TWL6040_HSLDOENA 0x04
105#define TWL6040_REFENA 0x40
106#define TWL6040_OSCENA 0x80
107
108/* HPPLLCTL (0x07) fields */
109
110#define TWL6040_HPLLENA 0x01
111#define TWL6040_HPLLRST 0x02
112#define TWL6040_HPLLBP 0x04
113#define TWL6040_HPLLSQRENA 0x08
114#define TWL6040_MCLK_12000KHZ (0 << 5)
115#define TWL6040_MCLK_19200KHZ (1 << 5)
116#define TWL6040_MCLK_26000KHZ (2 << 5)
117#define TWL6040_MCLK_38400KHZ (3 << 5)
118#define TWL6040_MCLK_MSK 0x60
119
120/* LPPLLCTL (0x08) fields */
121
122#define TWL6040_LPLLENA 0x01
123#define TWL6040_LPLLRST 0x02
124#define TWL6040_LPLLSEL 0x04
125#define TWL6040_LPLLFIN 0x08
126#define TWL6040_HPLLSEL 0x10
127
128/* HSLCTL (0x10) fields */
129
130#define TWL6040_HSDACMODEL 0x02
131#define TWL6040_HSDRVMODEL 0x08
132
133/* HSRCTL (0x11) fields */
134
135#define TWL6040_HSDACMODER 0x02
136#define TWL6040_HSDRVMODER 0x08
137
138/* VIBCTLL (0x18) fields */
139
140#define TWL6040_VIBENAL 0x01
141#define TWL6040_VIBCTRLL 0x04
142#define TWL6040_VIBCTRLLP 0x08
143#define TWL6040_VIBCTRLLN 0x10
144
145/* VIBDATL (0x19) fields */
146
147#define TWL6040_VIBDAT_MAX 0x64
148
149/* VIBCTLR (0x1A) fields */
150
151#define TWL6040_VIBENAR 0x01
152#define TWL6040_VIBCTRLR 0x04
153#define TWL6040_VIBCTRLRP 0x08
154#define TWL6040_VIBCTRLRN 0x10
155
156/* GPOCTL (0x1E) fields */
157
158#define TWL6040_GPO1 0x01
159#define TWL6040_GPO2 0x02
160#define TWL6040_GPO3 0x03
161
162/* ACCCTL (0x2D) fields */
163
164#define TWL6040_I2CSEL 0x01
165#define TWL6040_RESETSPLIT 0x04
166#define TWL6040_INTCLRMODE 0x08
167
168#define TWL6040_SYSCLK_SEL_LPPLL 1
169#define TWL6040_SYSCLK_SEL_HPPLL 2
170
171/* STATUS (0x2E) fields */
172
173#define TWL6040_PLUGCOMP 0x02
174#define TWL6040_VIBLOCDET 0x10
175#define TWL6040_VIBROCDET 0x20
176#define TWL6040_TSHUTDET 0x40
177
178#define TWL6040_CELLS 2
179
180#define TWL6040_REV_ES1_0 0x00
181#define TWL6040_REV_ES1_1 0x01
182#define TWL6040_REV_ES1_2 0x02
183
184#define TWL6040_IRQ_TH 0
185#define TWL6040_IRQ_PLUG 1
186#define TWL6040_IRQ_HOOK 2
187#define TWL6040_IRQ_HF 3
188#define TWL6040_IRQ_VIB 4
189#define TWL6040_IRQ_READY 5
190
191enum twl6040_pll_id {
192 TWL6040_NOPLL_ID,
193 TWL6040_LPPLL_ID,
194 TWL6040_HPPLL_ID,
195};
196
197struct twl6040 {
198 struct device *dev;
199 struct mutex mutex;
200 struct mutex io_mutex;
201 struct mutex irq_mutex;
202 struct mfd_cell cells[TWL6040_CELLS];
203 struct completion ready;
204
205 int audpwron;
206 int power_count;
207 int rev;
208
209 enum twl6040_pll_id pll;
210 unsigned int sysclk;
211
212 unsigned int irq;
213 unsigned int irq_base;
214 u8 irq_masks_cur;
215 u8 irq_masks_cache;
216};
217
218static inline int twl6040_get_rev(struct twl6040 *twl6040)
219{
220 return twl6040->rev;
221}
222
223static inline int twl6040_request_irq(struct twl6040 *twl6040, int irq,
224 irq_handler_t handler,
225 unsigned long irqflags,
226 const char *name,
227 void *data)
228{
229 if (!twl6040->irq_base)
230 return -EINVAL;
231
232 return request_threaded_irq(twl6040->irq_base + irq, NULL, handler,
233 irqflags, name, data);
234}
235
236static inline void twl6040_free_irq(struct twl6040 *twl6040, int irq,
237 void *data)
238{
239 if (!twl6040->irq_base)
240 return;
241
242 free_irq(twl6040->irq_base + irq, data);
243}
244
245int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg);
246int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg,
247 u8 val);
248int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg,
249 u8 mask);
250int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg,
251 u8 mask);
252int twl6040_power(struct twl6040 *twl6040, int on);
253int twl6040_is_powered(struct twl6040 *twl6040);
254int twl6040_set_pll(struct twl6040 *twl6040, enum twl6040_pll_id id,
255 unsigned int freq_in, unsigned int freq_out);
256enum twl6040_pll_id twl6040_get_pll(struct twl6040 *twl6040);
257unsigned int twl6040_get_sysclk(struct twl6040 *twl6040);
258int twl6040_irq_init(struct twl6040 *twl6040);
259void twl6040_irq_exit(struct twl6040 *twl6040);
260
261#endif /* End of __TWL6040_CODEC_H__ */