blob: a46d987b9c8b2d4bd184aca61aea0938178f26a5 [file] [log] [blame]
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -05001/*
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>
Peter Ujfalusi5af7df62012-05-02 16:54:42 +030030#include <linux/err.h>
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050031#include <linux/platform_device.h>
Peter Ujfalusi37e13ce2012-05-16 14:11:58 +030032#include <linux/of.h>
33#include <linux/of_irq.h>
34#include <linux/of_gpio.h>
35#include <linux/of_platform.h>
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050036#include <linux/gpio.h>
37#include <linux/delay.h>
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +030038#include <linux/i2c.h>
39#include <linux/regmap.h>
40#include <linux/err.h>
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050041#include <linux/mfd/core.h>
42#include <linux/mfd/twl6040.h>
Peter Ujfalusi5af7df62012-05-02 16:54:42 +030043#include <linux/regulator/consumer.h>
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050044
Peter Ujfalusi31b402e2011-10-12 11:57:54 +030045#define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1)
Peter Ujfalusi5af7df62012-05-02 16:54:42 +030046#define TWL6040_NUM_SUPPLIES (2)
Peter Ujfalusi31b402e2011-10-12 11:57:54 +030047
Samuel Ortizca2cad62012-05-23 16:23:21 +020048static bool twl6040_has_vibra(struct twl6040_platform_data *pdata,
49 struct device_node *node)
50{
51 if (pdata && pdata->vibra)
52 return true;
53
54#ifdef CONFIG_OF
55 if (of_find_node_by_name(node, "vibra"))
56 return true;
57#endif
58
59 return false;
60}
61
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050062int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
63{
64 int ret;
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +030065 unsigned int val;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050066
Peter Ujfalusi31b402e2011-10-12 11:57:54 +030067 /* Vibra control registers from cache */
68 if (unlikely(reg == TWL6040_REG_VIBCTLL ||
69 reg == TWL6040_REG_VIBCTLR)) {
70 val = twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)];
71 } else {
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +030072 ret = regmap_read(twl6040->regmap, reg, &val);
Axel Linc6000402012-07-11 10:06:34 +080073 if (ret < 0)
Peter Ujfalusi31b402e2011-10-12 11:57:54 +030074 return ret;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050075 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050076
77 return val;
78}
79EXPORT_SYMBOL(twl6040_reg_read);
80
81int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
82{
83 int ret;
84
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +030085 ret = regmap_write(twl6040->regmap, reg, val);
Peter Ujfalusi31b402e2011-10-12 11:57:54 +030086 /* Cache the vibra control registers */
87 if (reg == TWL6040_REG_VIBCTLL || reg == TWL6040_REG_VIBCTLR)
88 twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)] = val;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050089
90 return ret;
91}
92EXPORT_SYMBOL(twl6040_reg_write);
93
94int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
95{
Axel Linc6000402012-07-11 10:06:34 +080096 return regmap_update_bits(twl6040->regmap, reg, mask, mask);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -050097}
98EXPORT_SYMBOL(twl6040_set_bits);
99
100int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
101{
Axel Linc6000402012-07-11 10:06:34 +0800102 return regmap_update_bits(twl6040->regmap, reg, mask, 0);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500103}
104EXPORT_SYMBOL(twl6040_clear_bits);
105
106/* twl6040 codec manual power-up sequence */
107static int twl6040_power_up(struct twl6040 *twl6040)
108{
109 u8 ldoctl, ncpctl, lppllctl;
110 int ret;
111
112 /* enable high-side LDO, reference system and internal oscillator */
113 ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA;
114 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
115 if (ret)
116 return ret;
117 usleep_range(10000, 10500);
118
119 /* enable negative charge pump */
120 ncpctl = TWL6040_NCPENA;
121 ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
122 if (ret)
123 goto ncp_err;
124 usleep_range(1000, 1500);
125
126 /* enable low-side LDO */
127 ldoctl |= TWL6040_LSLDOENA;
128 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
129 if (ret)
130 goto lsldo_err;
131 usleep_range(1000, 1500);
132
133 /* enable low-power PLL */
134 lppllctl = TWL6040_LPLLENA;
135 ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
136 if (ret)
137 goto lppll_err;
138 usleep_range(5000, 5500);
139
140 /* disable internal oscillator */
141 ldoctl &= ~TWL6040_OSCENA;
142 ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
143 if (ret)
144 goto osc_err;
145
146 return 0;
147
148osc_err:
149 lppllctl &= ~TWL6040_LPLLENA;
150 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
151lppll_err:
152 ldoctl &= ~TWL6040_LSLDOENA;
153 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
154lsldo_err:
155 ncpctl &= ~TWL6040_NCPENA;
156 twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
157ncp_err:
158 ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
159 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
160
161 return ret;
162}
163
164/* twl6040 manual power-down sequence */
165static void twl6040_power_down(struct twl6040 *twl6040)
166{
167 u8 ncpctl, ldoctl, lppllctl;
168
169 ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL);
170 ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL);
171 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
172
173 /* enable internal oscillator */
174 ldoctl |= TWL6040_OSCENA;
175 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
176 usleep_range(1000, 1500);
177
178 /* disable low-power PLL */
179 lppllctl &= ~TWL6040_LPLLENA;
180 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl);
181
182 /* disable low-side LDO */
183 ldoctl &= ~TWL6040_LSLDOENA;
184 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
185
186 /* disable negative charge pump */
187 ncpctl &= ~TWL6040_NCPENA;
188 twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl);
189
190 /* disable high-side LDO, reference system and internal oscillator */
191 ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA);
192 twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl);
193}
194
195static irqreturn_t twl6040_naudint_handler(int irq, void *data)
196{
197 struct twl6040 *twl6040 = data;
198 u8 intid, status;
199
200 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
201
202 if (intid & TWL6040_READYINT)
203 complete(&twl6040->ready);
204
205 if (intid & TWL6040_THINT) {
206 status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS);
207 if (status & TWL6040_TSHUTDET) {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300208 dev_warn(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500209 "Thermal shutdown, powering-off");
210 twl6040_power(twl6040, 0);
211 } else {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300212 dev_warn(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500213 "Leaving thermal shutdown, powering-on");
214 twl6040_power(twl6040, 1);
215 }
216 }
217
218 return IRQ_HANDLED;
219}
220
Peter Ujfalusieae9a9c2012-10-11 13:55:29 +0200221static int twl6040_power_up_completion(struct twl6040 *twl6040)
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500222{
223 int time_left;
224 u8 intid;
225
226 time_left = wait_for_completion_timeout(&twl6040->ready,
227 msecs_to_jiffies(144));
228 if (!time_left) {
229 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
230 if (!(intid & TWL6040_READYINT)) {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300231 dev_err(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500232 "timeout waiting for READYINT\n");
233 return -ETIMEDOUT;
234 }
235 }
236
237 return 0;
238}
239
240int twl6040_power(struct twl6040 *twl6040, int on)
241{
242 int audpwron = twl6040->audpwron;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500243 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 */
Peter Ujfalusieae9a9c2012-10-11 13:55:29 +0200256 ret = twl6040_power_up_completion(twl6040);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500257 if (ret) {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300258 dev_err(twl6040->dev,
Peter Ujfalusia3241522012-10-11 13:55:28 +0200259 "automatic power-up failed\n");
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500260 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) {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300267 dev_err(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500268 "manual power-up failed\n");
269 twl6040->power_count = 0;
270 goto out;
271 }
272 }
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300273 /* Default PLL configuration after power up */
274 twl6040->pll = TWL6040_SYSCLK_SEL_LPPLL;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500275 twl6040->sysclk = 19200000;
Peter Ujfalusif8447d62012-01-14 20:58:43 +0100276 twl6040->mclk = 32768;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500277 } else {
278 /* already powered-down */
279 if (!twl6040->power_count) {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300280 dev_err(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500281 "device is already powered-off\n");
282 ret = -EPERM;
283 goto out;
284 }
285
286 if (--twl6040->power_count)
287 goto out;
288
289 if (gpio_is_valid(audpwron)) {
290 /* use AUDPWRON line */
291 gpio_set_value(audpwron, 0);
292
293 /* power-down sequence latency */
294 usleep_range(500, 700);
295 } else {
296 /* use manual power-down sequence */
297 twl6040_power_down(twl6040);
298 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500299 twl6040->sysclk = 0;
Peter Ujfalusif8447d62012-01-14 20:58:43 +0100300 twl6040->mclk = 0;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500301 }
302
303out:
304 mutex_unlock(&twl6040->mutex);
305 return ret;
306}
307EXPORT_SYMBOL(twl6040_power);
308
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300309int twl6040_set_pll(struct twl6040 *twl6040, int pll_id,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500310 unsigned int freq_in, unsigned int freq_out)
311{
312 u8 hppllctl, lppllctl;
313 int ret = 0;
314
315 mutex_lock(&twl6040->mutex);
316
317 hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL);
318 lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL);
319
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100320 /* Force full reconfiguration when switching between PLL */
321 if (pll_id != twl6040->pll) {
322 twl6040->sysclk = 0;
323 twl6040->mclk = 0;
324 }
325
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300326 switch (pll_id) {
327 case TWL6040_SYSCLK_SEL_LPPLL:
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500328 /* low-power PLL divider */
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100329 /* Change the sysclk configuration only if it has been canged */
330 if (twl6040->sysclk != freq_out) {
331 switch (freq_out) {
332 case 17640000:
333 lppllctl |= TWL6040_LPLLFIN;
334 break;
335 case 19200000:
336 lppllctl &= ~TWL6040_LPLLFIN;
337 break;
338 default:
339 dev_err(twl6040->dev,
340 "freq_out %d not supported\n",
341 freq_out);
342 ret = -EINVAL;
343 goto pll_out;
344 }
345 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
346 lppllctl);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500347 }
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100348
349 /* The PLL in use has not been change, we can exit */
350 if (twl6040->pll == pll_id)
351 break;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500352
353 switch (freq_in) {
354 case 32768:
355 lppllctl |= TWL6040_LPLLENA;
356 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
357 lppllctl);
358 mdelay(5);
359 lppllctl &= ~TWL6040_HPLLSEL;
360 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
361 lppllctl);
362 hppllctl &= ~TWL6040_HPLLENA;
363 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
364 hppllctl);
365 break;
366 default:
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300367 dev_err(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500368 "freq_in %d not supported\n", freq_in);
369 ret = -EINVAL;
370 goto pll_out;
371 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500372 break;
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300373 case TWL6040_SYSCLK_SEL_HPPLL:
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500374 /* high-performance PLL can provide only 19.2 MHz */
375 if (freq_out != 19200000) {
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300376 dev_err(twl6040->dev,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500377 "freq_out %d not supported\n", freq_out);
378 ret = -EINVAL;
379 goto pll_out;
380 }
381
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100382 if (twl6040->mclk != freq_in) {
383 hppllctl &= ~TWL6040_MCLK_MSK;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500384
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100385 switch (freq_in) {
386 case 12000000:
387 /* PLL enabled, active mode */
388 hppllctl |= TWL6040_MCLK_12000KHZ |
389 TWL6040_HPLLENA;
390 break;
391 case 19200000:
392 /*
393 * PLL disabled
394 * (enable PLL if MCLK jitter quality
395 * doesn't meet specification)
396 */
397 hppllctl |= TWL6040_MCLK_19200KHZ;
398 break;
399 case 26000000:
400 /* PLL enabled, active mode */
401 hppllctl |= TWL6040_MCLK_26000KHZ |
402 TWL6040_HPLLENA;
403 break;
404 case 38400000:
405 /* PLL enabled, active mode */
406 hppllctl |= TWL6040_MCLK_38400KHZ |
407 TWL6040_HPLLENA;
408 break;
409 default:
410 dev_err(twl6040->dev,
411 "freq_in %d not supported\n", freq_in);
412 ret = -EINVAL;
413 goto pll_out;
414 }
415
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500416 /*
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100417 * enable clock slicer to ensure input waveform is
418 * square
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500419 */
Peter Ujfalusi2bd05db2012-01-14 20:58:44 +0100420 hppllctl |= TWL6040_HPLLSQRENA;
421
422 twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL,
423 hppllctl);
424 usleep_range(500, 700);
425 lppllctl |= TWL6040_HPLLSEL;
426 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
427 lppllctl);
428 lppllctl &= ~TWL6040_LPLLENA;
429 twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL,
430 lppllctl);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500431 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500432 break;
433 default:
Peter Ujfalusi2d7c9572011-09-15 15:39:23 +0300434 dev_err(twl6040->dev, "unknown pll id %d\n", pll_id);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500435 ret = -EINVAL;
436 goto pll_out;
437 }
438
439 twl6040->sysclk = freq_out;
Peter Ujfalusif8447d62012-01-14 20:58:43 +0100440 twl6040->mclk = freq_in;
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300441 twl6040->pll = pll_id;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500442
443pll_out:
444 mutex_unlock(&twl6040->mutex);
445 return ret;
446}
447EXPORT_SYMBOL(twl6040_set_pll);
448
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300449int twl6040_get_pll(struct twl6040 *twl6040)
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500450{
Peter Ujfalusicfb7a332011-07-04 10:28:28 +0300451 if (twl6040->power_count)
452 return twl6040->pll;
453 else
454 return -ENODEV;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500455}
456EXPORT_SYMBOL(twl6040_get_pll);
457
458unsigned int twl6040_get_sysclk(struct twl6040 *twl6040)
459{
460 return twl6040->sysclk;
461}
462EXPORT_SYMBOL(twl6040_get_sysclk);
463
Peter Ujfalusi70601ec2011-10-12 11:57:55 +0300464/* Get the combined status of the vibra control register */
465int twl6040_get_vibralr_status(struct twl6040 *twl6040)
466{
467 u8 status;
468
469 status = twl6040->vibra_ctrl_cache[0] | twl6040->vibra_ctrl_cache[1];
470 status &= (TWL6040_VIBENA | TWL6040_VIBSEL);
471
472 return status;
473}
474EXPORT_SYMBOL(twl6040_get_vibralr_status);
475
Peter Ujfalusi0f962ae2011-07-05 11:40:33 +0300476static struct resource twl6040_vibra_rsrc[] = {
477 {
478 .flags = IORESOURCE_IRQ,
479 },
480};
481
482static struct resource twl6040_codec_rsrc[] = {
483 {
484 .flags = IORESOURCE_IRQ,
485 },
486};
487
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300488static bool twl6040_readable_reg(struct device *dev, unsigned int reg)
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500489{
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300490 /* Register 0 is not readable */
491 if (!reg)
492 return false;
493 return true;
494}
495
496static struct regmap_config twl6040_regmap_config = {
497 .reg_bits = 8,
498 .val_bits = 8,
499 .max_register = TWL6040_REG_STATUS, /* 0x2e */
500
501 .readable_reg = twl6040_readable_reg,
502};
503
504static int __devinit twl6040_probe(struct i2c_client *client,
505 const struct i2c_device_id *id)
506{
507 struct twl6040_platform_data *pdata = client->dev.platform_data;
Peter Ujfalusi37e13ce2012-05-16 14:11:58 +0300508 struct device_node *node = client->dev.of_node;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500509 struct twl6040 *twl6040;
510 struct mfd_cell *cell = NULL;
Peter Ujfalusi1f01d602012-05-16 14:11:57 +0300511 int irq, ret, children = 0;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500512
Peter Ujfalusi37e13ce2012-05-16 14:11:58 +0300513 if (!pdata && !node) {
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300514 dev_err(&client->dev, "Platform data is missing\n");
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500515 return -EINVAL;
516 }
517
Peter Ujfalusid20e1d22011-07-04 20:15:19 +0300518 /* In order to operate correctly we need valid interrupt config */
Peter Ujfalusi67124192012-05-16 14:11:56 +0300519 if (!client->irq) {
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300520 dev_err(&client->dev, "Invalid IRQ configuration\n");
Peter Ujfalusid20e1d22011-07-04 20:15:19 +0300521 return -EINVAL;
522 }
523
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300524 twl6040 = devm_kzalloc(&client->dev, sizeof(struct twl6040),
525 GFP_KERNEL);
526 if (!twl6040) {
527 ret = -ENOMEM;
528 goto err;
529 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500530
Axel Linbbf6adc2012-04-25 10:09:46 +0800531 twl6040->regmap = devm_regmap_init_i2c(client, &twl6040_regmap_config);
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300532 if (IS_ERR(twl6040->regmap)) {
533 ret = PTR_ERR(twl6040->regmap);
534 goto err;
535 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500536
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300537 i2c_set_clientdata(client, twl6040);
538
Peter Ujfalusi5af7df62012-05-02 16:54:42 +0300539 twl6040->supplies[0].supply = "vio";
540 twl6040->supplies[1].supply = "v2v1";
541 ret = regulator_bulk_get(&client->dev, TWL6040_NUM_SUPPLIES,
542 twl6040->supplies);
543 if (ret != 0) {
544 dev_err(&client->dev, "Failed to get supplies: %d\n", ret);
545 goto regulator_get_err;
546 }
547
548 ret = regulator_bulk_enable(TWL6040_NUM_SUPPLIES, twl6040->supplies);
549 if (ret != 0) {
550 dev_err(&client->dev, "Failed to enable supplies: %d\n", ret);
551 goto power_err;
552 }
553
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300554 twl6040->dev = &client->dev;
555 twl6040->irq = client->irq;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500556
557 mutex_init(&twl6040->mutex);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500558 init_completion(&twl6040->ready);
559
560 twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
561
Peter Ujfalusi77f63e02011-09-15 15:39:26 +0300562 /* ERRATA: Automatic power-up is not possible in ES1.0 */
Peter Ujfalusi37e13ce2012-05-16 14:11:58 +0300563 if (twl6040_get_revid(twl6040) > TWL6040_REV_ES1_0) {
564 if (pdata)
565 twl6040->audpwron = pdata->audpwron_gpio;
566 else
567 twl6040->audpwron = of_get_named_gpio(node,
568 "ti,audpwron-gpio", 0);
569 } else
Peter Ujfalusi77f63e02011-09-15 15:39:26 +0300570 twl6040->audpwron = -EINVAL;
571
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500572 if (gpio_is_valid(twl6040->audpwron)) {
Axel Linb04edb92011-12-01 09:55:07 +0800573 ret = gpio_request_one(twl6040->audpwron, GPIOF_OUT_INIT_LOW,
574 "audpwron");
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500575 if (ret)
Peter Ujfalusi5af7df62012-05-02 16:54:42 +0300576 goto gpio_err;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500577 }
578
Peter Ujfalusid20e1d22011-07-04 20:15:19 +0300579 /* codec interrupt */
580 ret = twl6040_irq_init(twl6040);
581 if (ret)
Peter Ujfalusi5af7df62012-05-02 16:54:42 +0300582 goto irq_init_err;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500583
Peter Ujfalusi1b7c4722011-07-04 20:16:23 +0300584 ret = request_threaded_irq(twl6040->irq_base + TWL6040_IRQ_READY,
Fengguang Wu09e09062012-09-19 09:32:38 +0800585 NULL, twl6040_naudint_handler, IRQF_ONESHOT,
Peter Ujfalusi1b7c4722011-07-04 20:16:23 +0300586 "twl6040_irq_ready", twl6040);
Peter Ujfalusid20e1d22011-07-04 20:15:19 +0300587 if (ret) {
588 dev_err(twl6040->dev, "READY IRQ request failed: %d\n",
589 ret);
590 goto irq_err;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500591 }
592
593 /* dual-access registers controlled by I2C only */
594 twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL);
595
Peter Ujfalusi1f01d602012-05-16 14:11:57 +0300596 /*
597 * The main functionality of twl6040 to provide audio on OMAP4+ systems.
598 * We can add the ASoC codec child whenever this driver has been loaded.
599 * The ASoC codec can work without pdata, pass the platform_data only if
600 * it has been provided.
601 */
602 irq = twl6040->irq_base + TWL6040_IRQ_PLUG;
603 cell = &twl6040->cells[children];
604 cell->name = "twl6040-codec";
605 twl6040_codec_rsrc[0].start = irq;
606 twl6040_codec_rsrc[0].end = irq;
607 cell->resources = twl6040_codec_rsrc;
608 cell->num_resources = ARRAY_SIZE(twl6040_codec_rsrc);
Peter Ujfalusi37e13ce2012-05-16 14:11:58 +0300609 if (pdata && pdata->codec) {
Peter Ujfalusi6c448632011-06-01 13:05:10 +0300610 cell->platform_data = pdata->codec;
611 cell->pdata_size = sizeof(*pdata->codec);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500612 }
Peter Ujfalusi1f01d602012-05-16 14:11:57 +0300613 children++;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500614
Samuel Ortizca2cad62012-05-23 16:23:21 +0200615 if (twl6040_has_vibra(pdata, node)) {
Peter Ujfalusi1f01d602012-05-16 14:11:57 +0300616 irq = twl6040->irq_base + TWL6040_IRQ_VIB;
Peter Ujfalusi0f962ae2011-07-05 11:40:33 +0300617
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500618 cell = &twl6040->cells[children];
619 cell->name = "twl6040-vibra";
Peter Ujfalusi0f962ae2011-07-05 11:40:33 +0300620 twl6040_vibra_rsrc[0].start = irq;
621 twl6040_vibra_rsrc[0].end = irq;
622 cell->resources = twl6040_vibra_rsrc;
623 cell->num_resources = ARRAY_SIZE(twl6040_vibra_rsrc);
624
Peter Ujfalusi37e13ce2012-05-16 14:11:58 +0300625 if (pdata && pdata->vibra) {
626 cell->platform_data = pdata->vibra;
627 cell->pdata_size = sizeof(*pdata->vibra);
628 }
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500629 children++;
630 }
631
Peter Ujfalusi5cbe7862012-08-16 15:13:14 +0300632 /*
633 * Enable the GPO driver in the following cases:
634 * DT booted kernel or legacy boot with valid gpo platform_data
635 */
636 if (!pdata || (pdata && pdata->gpo)) {
637 cell = &twl6040->cells[children];
638 cell->name = "twl6040-gpo";
639
640 if (pdata) {
641 cell->platform_data = pdata->gpo;
642 cell->pdata_size = sizeof(*pdata->gpo);
643 }
644 children++;
645 }
646
Peter Ujfalusi1f01d602012-05-16 14:11:57 +0300647 ret = mfd_add_devices(&client->dev, -1, twl6040->cells, children,
Mark Brown55692af2012-09-11 15:16:36 +0800648 NULL, 0, NULL);
Peter Ujfalusi1f01d602012-05-16 14:11:57 +0300649 if (ret)
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500650 goto mfd_err;
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500651
652 return 0;
653
654mfd_err:
Peter Ujfalusi1b7c4722011-07-04 20:16:23 +0300655 free_irq(twl6040->irq_base + TWL6040_IRQ_READY, twl6040);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500656irq_err:
Peter Ujfalusid20e1d22011-07-04 20:15:19 +0300657 twl6040_irq_exit(twl6040);
Peter Ujfalusi5af7df62012-05-02 16:54:42 +0300658irq_init_err:
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500659 if (gpio_is_valid(twl6040->audpwron))
660 gpio_free(twl6040->audpwron);
Peter Ujfalusi5af7df62012-05-02 16:54:42 +0300661gpio_err:
662 regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies);
663power_err:
664 regulator_bulk_free(TWL6040_NUM_SUPPLIES, twl6040->supplies);
665regulator_get_err:
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300666 i2c_set_clientdata(client, NULL);
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300667err:
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500668 return ret;
669}
670
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300671static int __devexit twl6040_remove(struct i2c_client *client)
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500672{
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300673 struct twl6040 *twl6040 = i2c_get_clientdata(client);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500674
675 if (twl6040->power_count)
676 twl6040_power(twl6040, 0);
677
678 if (gpio_is_valid(twl6040->audpwron))
679 gpio_free(twl6040->audpwron);
680
Peter Ujfalusi1b7c4722011-07-04 20:16:23 +0300681 free_irq(twl6040->irq_base + TWL6040_IRQ_READY, twl6040);
Peter Ujfalusid20e1d22011-07-04 20:15:19 +0300682 twl6040_irq_exit(twl6040);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500683
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300684 mfd_remove_devices(&client->dev);
685 i2c_set_clientdata(client, NULL);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500686
Peter Ujfalusi5af7df62012-05-02 16:54:42 +0300687 regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies);
688 regulator_bulk_free(TWL6040_NUM_SUPPLIES, twl6040->supplies);
689
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500690 return 0;
691}
692
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300693static const struct i2c_device_id twl6040_i2c_id[] = {
694 { "twl6040", 0, },
Peter Ujfalusi1fc74ae2012-07-16 11:49:44 +0200695 { "twl6041", 0, },
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300696 { },
697};
698MODULE_DEVICE_TABLE(i2c, twl6040_i2c_id);
699
700static struct i2c_driver twl6040_driver = {
701 .driver = {
702 .name = "twl6040",
703 .owner = THIS_MODULE,
704 },
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500705 .probe = twl6040_probe,
706 .remove = __devexit_p(twl6040_remove),
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300707 .id_table = twl6040_i2c_id,
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500708};
709
Peter Ujfalusi8eaeb932012-04-03 11:56:51 +0300710module_i2c_driver(twl6040_driver);
Misael Lopez Cruzf19b2822011-04-27 02:14:07 -0500711
712MODULE_DESCRIPTION("TWL6040 MFD");
713MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
714MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>");
715MODULE_LICENSE("GPL");
716MODULE_ALIAS("platform:twl6040");