blob: 96354660c3437b093bddacbc66ab862ef1881c70 [file] [log] [blame]
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001/*
2 * ALSA SoC TWL6040 codec driver
3 *
4 * Author: Misael Lopez Cruz <x0052729@ti.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * version 2 as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA
19 *
20 */
21
22#include <linux/module.h>
23#include <linux/moduleparam.h>
24#include <linux/init.h>
25#include <linux/delay.h>
26#include <linux/pm.h>
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000027#include <linux/platform_device.h>
Stephen Rothwell68b40cc2010-03-29 17:55:51 +110028#include <linux/slab.h>
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000029#include <linux/i2c/twl.h>
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -050030#include <linux/mfd/twl6040.h>
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000031
32#include <sound/core.h>
33#include <sound/pcm.h>
34#include <sound/pcm_params.h>
35#include <sound/soc.h>
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000036#include <sound/initval.h>
37#include <sound/tlv.h>
38
39#include "twl6040.h"
40
Olaya, Margarita60ea4ce2010-12-10 21:05:58 -060041#define TWL6040_RATES SNDRV_PCM_RATE_8000_96000
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -060042#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
43
44#define TWL6040_OUTHS_0dB 0x00
45#define TWL6040_OUTHS_M30dB 0x0F
46#define TWL6040_OUTHF_0dB 0x03
47#define TWL6040_OUTHF_M52dB 0x1D
48
49#define TWL6040_RAMP_NONE 0
50#define TWL6040_RAMP_UP 1
51#define TWL6040_RAMP_DOWN 2
52
53#define TWL6040_HSL_VOL_MASK 0x0F
54#define TWL6040_HSL_VOL_SHIFT 0
55#define TWL6040_HSR_VOL_MASK 0xF0
56#define TWL6040_HSR_VOL_SHIFT 4
57#define TWL6040_HF_VOL_MASK 0x1F
58#define TWL6040_HF_VOL_SHIFT 0
59
Peter Ujfalusid17bf312011-09-22 11:05:48 +030060/* Shadow register used by the driver */
61#define TWL6040_REG_SW_SHADOW 0x2F
62#define TWL6040_CACHEREGNUM (TWL6040_REG_SW_SHADOW + 1)
63
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -060064struct twl6040_output {
65 u16 active;
66 u16 left_vol;
67 u16 right_vol;
68 u16 left_step;
69 u16 right_step;
70 unsigned int step_delay;
71 u16 ramp;
72 u16 mute;
73 struct completion ramp_done;
74};
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000075
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -060076struct twl6040_jack_data {
77 struct snd_soc_jack *jack;
78 int report;
79};
80
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000081/* codec private data */
82struct twl6040_data {
Peter Ujfalusi2a433b92011-07-04 19:52:26 +030083 int plug_irq;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000084 int codec_powered;
85 int pll;
86 int non_lp;
Peter Ujfalusiaf958c72011-06-27 17:03:14 +030087 int pll_power_mode;
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -060088 int hs_power_mode;
89 int hs_power_mode_locked;
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -050090 unsigned int clk_in;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +000091 unsigned int sysclk;
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -060092 u16 hs_left_step;
93 u16 hs_right_step;
94 u16 hf_left_step;
95 u16 hf_right_step;
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -060096 struct twl6040_jack_data hs_jack;
97 struct snd_soc_codec *codec;
98 struct workqueue_struct *workqueue;
99 struct delayed_work delayed_work;
100 struct mutex mutex;
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600101 struct twl6040_output headset;
102 struct twl6040_output handsfree;
103 struct workqueue_struct *hf_workqueue;
104 struct workqueue_struct *hs_workqueue;
105 struct delayed_work hs_delayed_work;
106 struct delayed_work hf_delayed_work;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000107};
108
109/*
110 * twl6040 register cache & default register settings
111 */
112static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = {
Peter Ujfalusi4548dc32011-09-22 11:05:46 +0300113 0x00, /* not used 0x00 */
114 0x4B, /* REG_ASICID 0x01 (ro) */
115 0x00, /* REG_ASICREV 0x02 (ro) */
116 0x00, /* REG_INTID 0x03 */
117 0x00, /* REG_INTMR 0x04 */
118 0x00, /* REG_NCPCTRL 0x05 */
119 0x00, /* REG_LDOCTL 0x06 */
120 0x60, /* REG_HPPLLCTL 0x07 */
121 0x00, /* REG_LPPLLCTL 0x08 */
122 0x4A, /* REG_LPPLLDIV 0x09 */
123 0x00, /* REG_AMICBCTL 0x0A */
124 0x00, /* REG_DMICBCTL 0x0B */
125 0x00, /* REG_MICLCTL 0x0C */
126 0x00, /* REG_MICRCTL 0x0D */
127 0x00, /* REG_MICGAIN 0x0E */
128 0x1B, /* REG_LINEGAIN 0x0F */
129 0x00, /* REG_HSLCTL 0x10 */
130 0x00, /* REG_HSRCTL 0x11 */
131 0x00, /* REG_HSGAIN 0x12 */
132 0x00, /* REG_EARCTL 0x13 */
133 0x00, /* REG_HFLCTL 0x14 */
134 0x00, /* REG_HFLGAIN 0x15 */
135 0x00, /* REG_HFRCTL 0x16 */
136 0x00, /* REG_HFRGAIN 0x17 */
137 0x00, /* REG_VIBCTLL 0x18 */
138 0x00, /* REG_VIBDATL 0x19 */
139 0x00, /* REG_VIBCTLR 0x1A */
140 0x00, /* REG_VIBDATR 0x1B */
141 0x00, /* REG_HKCTL1 0x1C */
142 0x00, /* REG_HKCTL2 0x1D */
143 0x00, /* REG_GPOCTL 0x1E */
144 0x00, /* REG_ALB 0x1F */
145 0x00, /* REG_DLB 0x20 */
146 0x00, /* not used 0x21 */
147 0x00, /* not used 0x22 */
148 0x00, /* not used 0x23 */
149 0x00, /* not used 0x24 */
150 0x00, /* not used 0x25 */
151 0x00, /* not used 0x26 */
152 0x00, /* not used 0x27 */
153 0x00, /* REG_TRIM1 0x28 */
154 0x00, /* REG_TRIM2 0x29 */
155 0x00, /* REG_TRIM3 0x2A */
156 0x00, /* REG_HSOTRIM 0x2B */
157 0x00, /* REG_HFOTRIM 0x2C */
158 0x09, /* REG_ACCCTL 0x2D */
159 0x00, /* REG_STATUS 0x2E (ro) */
Peter Ujfalusid17bf312011-09-22 11:05:48 +0300160
161 0x00, /* REG_SW_SHADOW 0x2F - Shadow, non HW register */
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000162};
163
Peter Ujfalusia52762e2011-09-15 15:39:27 +0300164/* List of registers to be restored after power up */
165static const int twl6040_restore_list[] = {
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000166 TWL6040_REG_MICLCTL,
167 TWL6040_REG_MICRCTL,
168 TWL6040_REG_MICGAIN,
169 TWL6040_REG_LINEGAIN,
170 TWL6040_REG_HSLCTL,
171 TWL6040_REG_HSRCTL,
172 TWL6040_REG_HSGAIN,
173 TWL6040_REG_EARCTL,
174 TWL6040_REG_HFLCTL,
175 TWL6040_REG_HFLGAIN,
176 TWL6040_REG_HFRCTL,
177 TWL6040_REG_HFRGAIN,
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000178};
179
Peter Ujfalusiaf958c72011-06-27 17:03:14 +0300180/* set of rates for each pll: low-power and high-performance */
181static unsigned int lp_rates[] = {
182 8000,
183 11250,
184 16000,
185 22500,
186 32000,
187 44100,
188 48000,
189 88200,
190 96000,
191};
192
Peter Ujfalusiaf958c72011-06-27 17:03:14 +0300193static unsigned int hp_rates[] = {
194 8000,
195 16000,
196 32000,
197 48000,
198 96000,
199};
200
Peter Ujfalusif53c3462011-06-29 13:28:18 +0300201static struct snd_pcm_hw_constraint_list sysclk_constraints[] = {
202 { .count = ARRAY_SIZE(lp_rates), .list = lp_rates, },
203 { .count = ARRAY_SIZE(hp_rates), .list = hp_rates, },
Peter Ujfalusiaf958c72011-06-27 17:03:14 +0300204};
205
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000206/*
207 * read twl6040 register cache
208 */
209static inline unsigned int twl6040_read_reg_cache(struct snd_soc_codec *codec,
210 unsigned int reg)
211{
212 u8 *cache = codec->reg_cache;
213
214 if (reg >= TWL6040_CACHEREGNUM)
215 return -EIO;
216
217 return cache[reg];
218}
219
220/*
221 * write twl6040 register cache
222 */
223static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec,
224 u8 reg, u8 value)
225{
226 u8 *cache = codec->reg_cache;
227
228 if (reg >= TWL6040_CACHEREGNUM)
229 return;
230 cache[reg] = value;
231}
232
233/*
234 * read from twl6040 hardware register
235 */
236static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
237 unsigned int reg)
238{
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -0500239 struct twl6040 *twl6040 = codec->control_data;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000240 u8 value;
241
242 if (reg >= TWL6040_CACHEREGNUM)
243 return -EIO;
244
Peter Ujfalusid17bf312011-09-22 11:05:48 +0300245 if (likely(reg < TWL6040_REG_SW_SHADOW)) {
246 value = twl6040_reg_read(twl6040, reg);
247 twl6040_write_reg_cache(codec, reg, value);
248 } else {
249 value = twl6040_read_reg_cache(codec, reg);
250 }
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000251
252 return value;
253}
254
255/*
256 * write to the twl6040 register space
257 */
258static int twl6040_write(struct snd_soc_codec *codec,
259 unsigned int reg, unsigned int value)
260{
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -0500261 struct twl6040 *twl6040 = codec->control_data;
262
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000263 if (reg >= TWL6040_CACHEREGNUM)
264 return -EIO;
265
266 twl6040_write_reg_cache(codec, reg, value);
Peter Ujfalusid17bf312011-09-22 11:05:48 +0300267 if (likely(reg < TWL6040_REG_SW_SHADOW))
268 return twl6040_reg_write(twl6040, reg, value);
269 else
270 return 0;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000271}
272
Peter Ujfalusia52762e2011-09-15 15:39:27 +0300273static void twl6040_init_chip(struct snd_soc_codec *codec)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000274{
Peter Ujfalusia52762e2011-09-15 15:39:27 +0300275 struct twl6040 *twl6040 = codec->control_data;
276 u8 val;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000277
Peter Ujfalusia52762e2011-09-15 15:39:27 +0300278 val = twl6040_get_revid(twl6040);
279 twl6040_write_reg_cache(codec, TWL6040_REG_ASICREV, val);
280
Peter Ujfalusi2c27ff42011-09-15 15:39:28 +0300281 /* Change chip defaults */
282 /* No imput selected for microphone amplifiers */
283 twl6040_write_reg_cache(codec, TWL6040_REG_MICLCTL, 0x18);
284 twl6040_write_reg_cache(codec, TWL6040_REG_MICRCTL, 0x18);
Peter Ujfalusi3acef682011-09-22 11:05:45 +0300285
286 /*
287 * We need to lower the default gain values, so the ramp code
288 * can work correctly for the first playback.
289 * This reduces the pop noise heard at the first playback.
290 */
291 twl6040_write_reg_cache(codec, TWL6040_REG_HSGAIN, 0xff);
292 twl6040_write_reg_cache(codec, TWL6040_REG_EARCTL, 0x1e);
293 twl6040_write_reg_cache(codec, TWL6040_REG_HFLGAIN, 0x1d);
294 twl6040_write_reg_cache(codec, TWL6040_REG_HFRGAIN, 0x1d);
295 twl6040_write_reg_cache(codec, TWL6040_REG_LINEGAIN, 0);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000296}
297
Peter Ujfalusia52762e2011-09-15 15:39:27 +0300298static void twl6040_restore_regs(struct snd_soc_codec *codec)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000299{
300 u8 *cache = codec->reg_cache;
301 int reg, i;
302
Peter Ujfalusia52762e2011-09-15 15:39:27 +0300303 for (i = 0; i < ARRAY_SIZE(twl6040_restore_list); i++) {
304 reg = twl6040_restore_list[i];
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000305 twl6040_write(codec, reg, cache[reg]);
306 }
307}
308
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600309/*
310 * Ramp HS PGA volume to minimise pops at stream startup and shutdown.
311 */
312static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec,
313 unsigned int left_step, unsigned int right_step)
314{
315
316 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
317 struct twl6040_output *headset = &priv->headset;
318 int left_complete = 0, right_complete = 0;
319 u8 reg, val;
320
321 /* left channel */
322 left_step = (left_step > 0xF) ? 0xF : left_step;
323 reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN);
324 val = (~reg & TWL6040_HSL_VOL_MASK);
325
326 if (headset->ramp == TWL6040_RAMP_UP) {
327 /* ramp step up */
328 if (val < headset->left_vol) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600329 if (val + left_step > headset->left_vol)
330 val = headset->left_vol;
331 else
332 val += left_step;
333
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600334 reg &= ~TWL6040_HSL_VOL_MASK;
335 twl6040_write(codec, TWL6040_REG_HSGAIN,
336 (reg | (~val & TWL6040_HSL_VOL_MASK)));
337 } else {
338 left_complete = 1;
339 }
340 } else if (headset->ramp == TWL6040_RAMP_DOWN) {
341 /* ramp step down */
342 if (val > 0x0) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600343 if ((int)val - (int)left_step < 0)
344 val = 0;
345 else
346 val -= left_step;
347
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600348 reg &= ~TWL6040_HSL_VOL_MASK;
349 twl6040_write(codec, TWL6040_REG_HSGAIN, reg |
350 (~val & TWL6040_HSL_VOL_MASK));
351 } else {
352 left_complete = 1;
353 }
354 }
355
356 /* right channel */
357 right_step = (right_step > 0xF) ? 0xF : right_step;
358 reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN);
359 val = (~reg & TWL6040_HSR_VOL_MASK) >> TWL6040_HSR_VOL_SHIFT;
360
361 if (headset->ramp == TWL6040_RAMP_UP) {
362 /* ramp step up */
363 if (val < headset->right_vol) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600364 if (val + right_step > headset->right_vol)
365 val = headset->right_vol;
366 else
367 val += right_step;
368
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600369 reg &= ~TWL6040_HSR_VOL_MASK;
370 twl6040_write(codec, TWL6040_REG_HSGAIN,
371 (reg | (~val << TWL6040_HSR_VOL_SHIFT)));
372 } else {
373 right_complete = 1;
374 }
375 } else if (headset->ramp == TWL6040_RAMP_DOWN) {
376 /* ramp step down */
377 if (val > 0x0) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600378 if ((int)val - (int)right_step < 0)
379 val = 0;
380 else
381 val -= right_step;
382
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600383 reg &= ~TWL6040_HSR_VOL_MASK;
384 twl6040_write(codec, TWL6040_REG_HSGAIN,
385 reg | (~val << TWL6040_HSR_VOL_SHIFT));
386 } else {
387 right_complete = 1;
388 }
389 }
390
391 return left_complete & right_complete;
392}
393
394/*
395 * Ramp HF PGA volume to minimise pops at stream startup and shutdown.
396 */
397static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec,
398 unsigned int left_step, unsigned int right_step)
399{
400 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
401 struct twl6040_output *handsfree = &priv->handsfree;
402 int left_complete = 0, right_complete = 0;
403 u16 reg, val;
404
405 /* left channel */
406 left_step = (left_step > 0x1D) ? 0x1D : left_step;
407 reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN);
408 reg = 0x1D - reg;
409 val = (reg & TWL6040_HF_VOL_MASK);
410 if (handsfree->ramp == TWL6040_RAMP_UP) {
411 /* ramp step up */
412 if (val < handsfree->left_vol) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600413 if (val + left_step > handsfree->left_vol)
414 val = handsfree->left_vol;
415 else
416 val += left_step;
417
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600418 reg &= ~TWL6040_HF_VOL_MASK;
419 twl6040_write(codec, TWL6040_REG_HFLGAIN,
420 reg | (0x1D - val));
421 } else {
422 left_complete = 1;
423 }
424 } else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
425 /* ramp step down */
426 if (val > 0) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600427 if ((int)val - (int)left_step < 0)
428 val = 0;
429 else
430 val -= left_step;
431
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600432 reg &= ~TWL6040_HF_VOL_MASK;
433 twl6040_write(codec, TWL6040_REG_HFLGAIN,
434 reg | (0x1D - val));
435 } else {
436 left_complete = 1;
437 }
438 }
439
440 /* right channel */
441 right_step = (right_step > 0x1D) ? 0x1D : right_step;
442 reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN);
443 reg = 0x1D - reg;
444 val = (reg & TWL6040_HF_VOL_MASK);
445 if (handsfree->ramp == TWL6040_RAMP_UP) {
446 /* ramp step up */
447 if (val < handsfree->right_vol) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600448 if (val + right_step > handsfree->right_vol)
449 val = handsfree->right_vol;
450 else
451 val += right_step;
452
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600453 reg &= ~TWL6040_HF_VOL_MASK;
454 twl6040_write(codec, TWL6040_REG_HFRGAIN,
455 reg | (0x1D - val));
456 } else {
457 right_complete = 1;
458 }
459 } else if (handsfree->ramp == TWL6040_RAMP_DOWN) {
460 /* ramp step down */
461 if (val > 0) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600462 if ((int)val - (int)right_step < 0)
463 val = 0;
464 else
465 val -= right_step;
466
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600467 reg &= ~TWL6040_HF_VOL_MASK;
468 twl6040_write(codec, TWL6040_REG_HFRGAIN,
469 reg | (0x1D - val));
470 }
471 }
472
473 return left_complete & right_complete;
474}
475
476/*
477 * This work ramps both output PGAs at stream start/stop time to
478 * minimise pop associated with DAPM power switching.
479 */
480static void twl6040_pga_hs_work(struct work_struct *work)
481{
482 struct twl6040_data *priv =
483 container_of(work, struct twl6040_data, hs_delayed_work.work);
484 struct snd_soc_codec *codec = priv->codec;
485 struct twl6040_output *headset = &priv->headset;
486 unsigned int delay = headset->step_delay;
487 int i, headset_complete;
488
489 /* do we need to ramp at all ? */
490 if (headset->ramp == TWL6040_RAMP_NONE)
491 return;
492
493 /* HS PGA volumes have 4 bits of resolution to ramp */
494 for (i = 0; i <= 16; i++) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600495 headset_complete = twl6040_hs_ramp_step(codec,
496 headset->left_step,
497 headset->right_step);
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600498
499 /* ramp finished ? */
500 if (headset_complete)
501 break;
502
503 /*
504 * TODO: tune: delay is longer over 0dB
505 * as increases are larger.
506 */
507 if (i >= 8)
508 schedule_timeout_interruptible(msecs_to_jiffies(delay +
509 (delay >> 1)));
510 else
511 schedule_timeout_interruptible(msecs_to_jiffies(delay));
512 }
513
514 if (headset->ramp == TWL6040_RAMP_DOWN) {
515 headset->active = 0;
516 complete(&headset->ramp_done);
517 } else {
518 headset->active = 1;
519 }
520 headset->ramp = TWL6040_RAMP_NONE;
521}
522
523static void twl6040_pga_hf_work(struct work_struct *work)
524{
525 struct twl6040_data *priv =
526 container_of(work, struct twl6040_data, hf_delayed_work.work);
527 struct snd_soc_codec *codec = priv->codec;
528 struct twl6040_output *handsfree = &priv->handsfree;
529 unsigned int delay = handsfree->step_delay;
530 int i, handsfree_complete;
531
532 /* do we need to ramp at all ? */
533 if (handsfree->ramp == TWL6040_RAMP_NONE)
534 return;
535
536 /* HF PGA volumes have 5 bits of resolution to ramp */
537 for (i = 0; i <= 32; i++) {
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600538 handsfree_complete = twl6040_hf_ramp_step(codec,
539 handsfree->left_step,
540 handsfree->right_step);
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600541
542 /* ramp finished ? */
543 if (handsfree_complete)
544 break;
545
546 /*
547 * TODO: tune: delay is longer over 0dB
548 * as increases are larger.
549 */
550 if (i >= 16)
551 schedule_timeout_interruptible(msecs_to_jiffies(delay +
552 (delay >> 1)));
553 else
554 schedule_timeout_interruptible(msecs_to_jiffies(delay));
555 }
556
557
558 if (handsfree->ramp == TWL6040_RAMP_DOWN) {
559 handsfree->active = 0;
560 complete(&handsfree->ramp_done);
561 } else
562 handsfree->active = 1;
563 handsfree->ramp = TWL6040_RAMP_NONE;
564}
565
566static int pga_event(struct snd_soc_dapm_widget *w,
567 struct snd_kcontrol *kcontrol, int event)
568{
569 struct snd_soc_codec *codec = w->codec;
570 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
571 struct twl6040_output *out;
572 struct delayed_work *work;
573 struct workqueue_struct *queue;
574
575 switch (w->shift) {
576 case 2:
577 case 3:
578 out = &priv->headset;
579 work = &priv->hs_delayed_work;
580 queue = priv->hs_workqueue;
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600581 out->left_step = priv->hs_left_step;
582 out->right_step = priv->hs_right_step;
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600583 out->step_delay = 5; /* 5 ms between volume ramp steps */
584 break;
585 case 4:
586 out = &priv->handsfree;
587 work = &priv->hf_delayed_work;
588 queue = priv->hf_workqueue;
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -0600589 out->left_step = priv->hf_left_step;
590 out->right_step = priv->hf_right_step;
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600591 out->step_delay = 5; /* 5 ms between volume ramp steps */
592 if (SND_SOC_DAPM_EVENT_ON(event))
593 priv->non_lp++;
594 else
595 priv->non_lp--;
596 break;
597 default:
598 return -1;
599 }
600
601 switch (event) {
602 case SND_SOC_DAPM_POST_PMU:
603 if (out->active)
604 break;
605
606 /* don't use volume ramp for power-up */
607 out->left_step = out->left_vol;
608 out->right_step = out->right_vol;
609
610 if (!delayed_work_pending(work)) {
611 out->ramp = TWL6040_RAMP_UP;
612 queue_delayed_work(queue, work,
613 msecs_to_jiffies(1));
614 }
615 break;
616
617 case SND_SOC_DAPM_PRE_PMD:
618 if (!out->active)
619 break;
620
621 if (!delayed_work_pending(work)) {
622 /* use volume ramp for power-down */
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600623 out->ramp = TWL6040_RAMP_DOWN;
624 INIT_COMPLETION(out->ramp_done);
625
626 queue_delayed_work(queue, work,
627 msecs_to_jiffies(1));
628
629 wait_for_completion_timeout(&out->ramp_done,
630 msecs_to_jiffies(2000));
631 }
632 break;
633 }
634
635 return 0;
636}
637
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000638/* set headset dac and driver power mode */
639static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
640{
641 int hslctl, hsrctl;
642 int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL;
643
644 hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL);
645 hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL);
646
647 if (high_perf) {
648 hslctl &= ~mask;
649 hsrctl &= ~mask;
650 } else {
651 hslctl |= mask;
652 hsrctl |= mask;
653 }
654
655 twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl);
656 twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl);
657
658 return 0;
659}
660
Jorge Eduardo Candelaria0fad4ed2010-07-15 11:38:01 -0500661static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w,
662 struct snd_kcontrol *kcontrol, int event)
663{
664 msleep(1);
665 return 0;
666}
667
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000668static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
669 struct snd_kcontrol *kcontrol, int event)
670{
671 struct snd_soc_codec *codec = w->codec;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +0200672 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600673 int ret = 0;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000674
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600675 if (SND_SOC_DAPM_EVENT_ON(event)) {
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000676 priv->non_lp++;
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600677 if (!strcmp(w->name, "Earphone Driver")) {
678 /* Earphone doesn't support low power mode */
679 priv->hs_power_mode_locked = 1;
680 ret = headset_power_mode(codec, 1);
681 }
682 } else {
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000683 priv->non_lp--;
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600684 if (!strcmp(w->name, "Earphone Driver")) {
685 priv->hs_power_mode_locked = 0;
686 ret = headset_power_mode(codec, priv->hs_power_mode);
687 }
688 }
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000689
Jorge Eduardo Candelaria0fad4ed2010-07-15 11:38:01 -0500690 msleep(1);
691
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600692 return ret;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000693}
694
Mark Brown64ed9832011-01-20 21:43:44 +0000695static void twl6040_hs_jack_report(struct snd_soc_codec *codec,
696 struct snd_soc_jack *jack, int report)
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -0600697{
698 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
699 int status;
700
701 mutex_lock(&priv->mutex);
702
703 /* Sync status */
704 status = twl6040_read_reg_volatile(codec, TWL6040_REG_STATUS);
705 if (status & TWL6040_PLUGCOMP)
706 snd_soc_jack_report(jack, report, report);
707 else
708 snd_soc_jack_report(jack, 0, report);
709
710 mutex_unlock(&priv->mutex);
711}
712
713void twl6040_hs_jack_detect(struct snd_soc_codec *codec,
714 struct snd_soc_jack *jack, int report)
715{
716 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
717 struct twl6040_jack_data *hs_jack = &priv->hs_jack;
718
719 hs_jack->jack = jack;
720 hs_jack->report = report;
721
722 twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report);
723}
724EXPORT_SYMBOL_GPL(twl6040_hs_jack_detect);
725
726static void twl6040_accessory_work(struct work_struct *work)
727{
728 struct twl6040_data *priv = container_of(work,
729 struct twl6040_data, delayed_work.work);
730 struct snd_soc_codec *codec = priv->codec;
731 struct twl6040_jack_data *hs_jack = &priv->hs_jack;
732
733 twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report);
734}
735
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000736/* audio interrupt handler */
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -0500737static irqreturn_t twl6040_audio_handler(int irq, void *data)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000738{
739 struct snd_soc_codec *codec = data;
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -0500740 struct twl6040 *twl6040 = codec->control_data;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +0200741 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000742 u8 intid;
743
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -0500744 intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID);
Olaya, Margaritacf370a52010-12-10 21:05:30 -0600745
746 if ((intid & TWL6040_PLUGINT) || (intid & TWL6040_UNPLUGINT))
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -0600747 queue_delayed_work(priv->workqueue, &priv->delayed_work,
748 msecs_to_jiffies(200));
Olaya, Margaritacf370a52010-12-10 21:05:30 -0600749
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000750 return IRQ_HANDLED;
751}
752
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -0600753static int twl6040_put_volsw(struct snd_kcontrol *kcontrol,
754 struct snd_ctl_elem_value *ucontrol)
755{
756 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
757 struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
758 struct twl6040_output *out = NULL;
759 struct soc_mixer_control *mc =
760 (struct soc_mixer_control *)kcontrol->private_value;
761 int ret;
762 unsigned int reg = mc->reg;
763
764 /* For HS and HF we shadow the values and only actually write
765 * them out when active in order to ensure the amplifier comes on
766 * as quietly as possible. */
767 switch (reg) {
768 case TWL6040_REG_HSGAIN:
769 out = &twl6040_priv->headset;
770 break;
771 default:
772 break;
773 }
774
775 if (out) {
776 out->left_vol = ucontrol->value.integer.value[0];
777 out->right_vol = ucontrol->value.integer.value[1];
778 if (!out->active)
779 return 1;
780 }
781
782 ret = snd_soc_put_volsw(kcontrol, ucontrol);
783 if (ret < 0)
784 return ret;
785
786 return 1;
787}
788
789static int twl6040_get_volsw(struct snd_kcontrol *kcontrol,
790 struct snd_ctl_elem_value *ucontrol)
791{
792 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
793 struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
794 struct twl6040_output *out = &twl6040_priv->headset;
795 struct soc_mixer_control *mc =
796 (struct soc_mixer_control *)kcontrol->private_value;
797 unsigned int reg = mc->reg;
798
799 switch (reg) {
800 case TWL6040_REG_HSGAIN:
801 out = &twl6040_priv->headset;
802 ucontrol->value.integer.value[0] = out->left_vol;
803 ucontrol->value.integer.value[1] = out->right_vol;
804 return 0;
805
806 default:
807 break;
808 }
809
810 return snd_soc_get_volsw(kcontrol, ucontrol);
811}
812
813static int twl6040_put_volsw_2r_vu(struct snd_kcontrol *kcontrol,
814 struct snd_ctl_elem_value *ucontrol)
815{
816 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
817 struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
818 struct twl6040_output *out = NULL;
819 struct soc_mixer_control *mc =
820 (struct soc_mixer_control *)kcontrol->private_value;
821 int ret;
822 unsigned int reg = mc->reg;
823
824 /* For HS and HF we shadow the values and only actually write
825 * them out when active in order to ensure the amplifier comes on
826 * as quietly as possible. */
827 switch (reg) {
828 case TWL6040_REG_HFLGAIN:
829 case TWL6040_REG_HFRGAIN:
830 out = &twl6040_priv->handsfree;
831 break;
832 default:
833 break;
834 }
835
836 if (out) {
837 out->left_vol = ucontrol->value.integer.value[0];
838 out->right_vol = ucontrol->value.integer.value[1];
839 if (!out->active)
840 return 1;
841 }
842
843 ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
844 if (ret < 0)
845 return ret;
846
847 return 1;
848}
849
850static int twl6040_get_volsw_2r(struct snd_kcontrol *kcontrol,
851 struct snd_ctl_elem_value *ucontrol)
852{
853 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
854 struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec);
855 struct twl6040_output *out = &twl6040_priv->handsfree;
856 struct soc_mixer_control *mc =
857 (struct soc_mixer_control *)kcontrol->private_value;
858 unsigned int reg = mc->reg;
859
860 /* If these are cached registers use the cache */
861 switch (reg) {
862 case TWL6040_REG_HFLGAIN:
863 case TWL6040_REG_HFRGAIN:
864 out = &twl6040_priv->handsfree;
865 ucontrol->value.integer.value[0] = out->left_vol;
866 ucontrol->value.integer.value[1] = out->right_vol;
867 return 0;
868
869 default:
870 break;
871 }
872
873 return snd_soc_get_volsw_2r(kcontrol, ucontrol);
874}
875
876/* double control with volume update */
877#define SOC_TWL6040_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax,\
878 xinvert, tlv_array)\
879{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
880 .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
881 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
882 .tlv.p = (tlv_array), \
883 .info = snd_soc_info_volsw, .get = twl6040_get_volsw, \
884 .put = twl6040_put_volsw, \
885 .private_value = (unsigned long)&(struct soc_mixer_control) \
886 {.reg = xreg, .shift = shift_left, .rshift = shift_right,\
887 .max = xmax, .platform_max = xmax, .invert = xinvert} }
888
889/* double control with volume update */
890#define SOC_TWL6040_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax,\
891 xinvert, tlv_array)\
892{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
893 .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
894 SNDRV_CTL_ELEM_ACCESS_READWRITE | \
895 SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
896 .tlv.p = (tlv_array), \
897 .info = snd_soc_info_volsw_2r, \
898 .get = twl6040_get_volsw_2r, .put = twl6040_put_volsw_2r_vu, \
899 .private_value = (unsigned long)&(struct soc_mixer_control) \
900 {.reg = reg_left, .rreg = reg_right, .shift = xshift, \
901 .rshift = xshift, .max = xmax, .invert = xinvert}, }
902
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000903/*
904 * MICATT volume control:
905 * from -6 to 0 dB in 6 dB steps
906 */
907static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0);
908
909/*
910 * MICGAIN volume control:
Ricardo Neri2763f452011-05-01 15:35:55 +0100911 * from 6 to 30 dB in 6 dB steps
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000912 */
Ricardo Neri2763f452011-05-01 15:35:55 +0100913static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000914
915/*
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600916 * AFMGAIN volume control:
Liam Girdwood1f71a3b2011-03-28 19:23:23 +0100917 * from -18 to 24 dB in 6 dB steps
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600918 */
Liam Girdwood1f71a3b2011-03-28 19:23:23 +0100919static DECLARE_TLV_DB_SCALE(afm_amp_tlv, -1800, 600, 0);
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600920
921/*
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000922 * HSGAIN volume control:
923 * from -30 to 0 dB in 2 dB steps
924 */
925static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0);
926
927/*
928 * HFGAIN volume control:
929 * from -52 to 6 dB in 2 dB steps
930 */
931static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0);
932
Jorge Eduardo Candelaria871a05a2010-05-18 12:44:18 -0500933/*
934 * EPGAIN volume control:
935 * from -24 to 6 dB in 2 dB steps
936 */
937static DECLARE_TLV_DB_SCALE(ep_tlv, -2400, 200, 0);
938
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000939/* Left analog microphone selection */
940static const char *twl6040_amicl_texts[] =
941 {"Headset Mic", "Main Mic", "Aux/FM Left", "Off"};
942
943/* Right analog microphone selection */
944static const char *twl6040_amicr_texts[] =
945 {"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"};
946
947static const struct soc_enum twl6040_enum[] = {
Francois Mazardcb973d72010-12-10 21:06:03 -0600948 SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 4, twl6040_amicl_texts),
949 SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 4, twl6040_amicr_texts),
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000950};
951
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600952static const char *twl6040_hs_texts[] = {
953 "Off", "HS DAC", "Line-In amp"
954};
955
956static const struct soc_enum twl6040_hs_enum[] = {
957 SOC_ENUM_SINGLE(TWL6040_REG_HSLCTL, 5, ARRAY_SIZE(twl6040_hs_texts),
958 twl6040_hs_texts),
959 SOC_ENUM_SINGLE(TWL6040_REG_HSRCTL, 5, ARRAY_SIZE(twl6040_hs_texts),
960 twl6040_hs_texts),
961};
962
963static const char *twl6040_hf_texts[] = {
964 "Off", "HF DAC", "Line-In amp"
965};
966
967static const struct soc_enum twl6040_hf_enum[] = {
968 SOC_ENUM_SINGLE(TWL6040_REG_HFLCTL, 2, ARRAY_SIZE(twl6040_hf_texts),
969 twl6040_hf_texts),
970 SOC_ENUM_SINGLE(TWL6040_REG_HFRCTL, 2, ARRAY_SIZE(twl6040_hf_texts),
971 twl6040_hf_texts),
972};
973
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000974static const struct snd_kcontrol_new amicl_control =
975 SOC_DAPM_ENUM("Route", twl6040_enum[0]);
976
977static const struct snd_kcontrol_new amicr_control =
978 SOC_DAPM_ENUM("Route", twl6040_enum[1]);
979
980/* Headset DAC playback switches */
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600981static const struct snd_kcontrol_new hsl_mux_controls =
982 SOC_DAPM_ENUM("Route", twl6040_hs_enum[0]);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000983
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600984static const struct snd_kcontrol_new hsr_mux_controls =
985 SOC_DAPM_ENUM("Route", twl6040_hs_enum[1]);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000986
987/* Handsfree DAC playback switches */
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600988static const struct snd_kcontrol_new hfl_mux_controls =
989 SOC_DAPM_ENUM("Route", twl6040_hf_enum[0]);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000990
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -0600991static const struct snd_kcontrol_new hfr_mux_controls =
992 SOC_DAPM_ENUM("Route", twl6040_hf_enum[1]);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +0000993
Jorge Eduardo Candelaria871a05a2010-05-18 12:44:18 -0500994static const struct snd_kcontrol_new ep_driver_switch_controls =
995 SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
996
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600997/* Headset power mode */
Peter Ujfalusi7cca6062011-06-27 13:33:14 +0300998static const char *twl6040_power_mode_texts[] = {
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -0600999 "Low-Power", "High-Perfomance",
1000};
1001
Peter Ujfalusi7cca6062011-06-27 13:33:14 +03001002static const struct soc_enum twl6040_power_mode_enum =
1003 SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl6040_power_mode_texts),
1004 twl6040_power_mode_texts);
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -06001005
1006static int twl6040_headset_power_get_enum(struct snd_kcontrol *kcontrol,
1007 struct snd_ctl_elem_value *ucontrol)
1008{
1009 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
1010 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
1011
1012 ucontrol->value.enumerated.item[0] = priv->hs_power_mode;
1013
1014 return 0;
1015}
1016
1017static int twl6040_headset_power_put_enum(struct snd_kcontrol *kcontrol,
1018 struct snd_ctl_elem_value *ucontrol)
1019{
1020 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
1021 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
1022 int high_perf = ucontrol->value.enumerated.item[0];
1023 int ret = 0;
1024
1025 if (!priv->hs_power_mode_locked)
1026 ret = headset_power_mode(codec, high_perf);
1027
1028 if (!ret)
1029 priv->hs_power_mode = high_perf;
1030
1031 return ret;
1032}
1033
Peter Ujfalusiaf958c72011-06-27 17:03:14 +03001034static int twl6040_pll_get_enum(struct snd_kcontrol *kcontrol,
1035 struct snd_ctl_elem_value *ucontrol)
1036{
1037 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
1038 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
1039
1040 ucontrol->value.enumerated.item[0] = priv->pll_power_mode;
1041
1042 return 0;
1043}
1044
1045static int twl6040_pll_put_enum(struct snd_kcontrol *kcontrol,
1046 struct snd_ctl_elem_value *ucontrol)
1047{
1048 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
1049 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
1050
1051 priv->pll_power_mode = ucontrol->value.enumerated.item[0];
Peter Ujfalusiaf958c72011-06-27 17:03:14 +03001052
1053 return 0;
1054}
1055
1056int twl6040_get_clk_id(struct snd_soc_codec *codec)
1057{
1058 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
1059
Peter Ujfalusiff593ca2011-07-04 10:35:23 +03001060 return priv->pll_power_mode;
Peter Ujfalusiaf958c72011-06-27 17:03:14 +03001061}
1062EXPORT_SYMBOL_GPL(twl6040_get_clk_id);
1063
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001064static const struct snd_kcontrol_new twl6040_snd_controls[] = {
1065 /* Capture gains */
1066 SOC_DOUBLE_TLV("Capture Preamplifier Volume",
1067 TWL6040_REG_MICGAIN, 6, 7, 1, 1, mic_preamp_tlv),
1068 SOC_DOUBLE_TLV("Capture Volume",
1069 TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv),
1070
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001071 /* AFM gains */
1072 SOC_DOUBLE_TLV("Aux FM Volume",
Liam Girdwood1f71a3b2011-03-28 19:23:23 +01001073 TWL6040_REG_LINEGAIN, 0, 3, 7, 0, afm_amp_tlv),
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001074
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001075 /* Playback gains */
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001076 SOC_TWL6040_DOUBLE_TLV("Headset Playback Volume",
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001077 TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv),
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001078 SOC_TWL6040_DOUBLE_R_TLV("Handsfree Playback Volume",
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001079 TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
Jorge Eduardo Candelaria871a05a2010-05-18 12:44:18 -05001080 SOC_SINGLE_TLV("Earphone Playback Volume",
1081 TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv),
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -06001082
Peter Ujfalusi7cca6062011-06-27 13:33:14 +03001083 SOC_ENUM_EXT("Headset Power Mode", twl6040_power_mode_enum,
Misael Lopez Cruz6bba63b2011-02-11 17:51:05 -06001084 twl6040_headset_power_get_enum,
1085 twl6040_headset_power_put_enum),
Peter Ujfalusiaf958c72011-06-27 17:03:14 +03001086
1087 SOC_ENUM_EXT("PLL Selection", twl6040_power_mode_enum,
1088 twl6040_pll_get_enum, twl6040_pll_put_enum),
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001089};
1090
1091static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
1092 /* Inputs */
1093 SND_SOC_DAPM_INPUT("MAINMIC"),
1094 SND_SOC_DAPM_INPUT("HSMIC"),
1095 SND_SOC_DAPM_INPUT("SUBMIC"),
1096 SND_SOC_DAPM_INPUT("AFML"),
1097 SND_SOC_DAPM_INPUT("AFMR"),
1098
1099 /* Outputs */
1100 SND_SOC_DAPM_OUTPUT("HSOL"),
1101 SND_SOC_DAPM_OUTPUT("HSOR"),
1102 SND_SOC_DAPM_OUTPUT("HFL"),
1103 SND_SOC_DAPM_OUTPUT("HFR"),
Jorge Eduardo Candelaria871a05a2010-05-18 12:44:18 -05001104 SND_SOC_DAPM_OUTPUT("EP"),
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001105
1106 /* Analog input muxes for the capture amplifiers */
1107 SND_SOC_DAPM_MUX("Analog Left Capture Route",
1108 SND_SOC_NOPM, 0, 0, &amicl_control),
1109 SND_SOC_DAPM_MUX("Analog Right Capture Route",
1110 SND_SOC_NOPM, 0, 0, &amicr_control),
1111
1112 /* Analog capture PGAs */
1113 SND_SOC_DAPM_PGA("MicAmpL",
1114 TWL6040_REG_MICLCTL, 0, 0, NULL, 0),
1115 SND_SOC_DAPM_PGA("MicAmpR",
1116 TWL6040_REG_MICRCTL, 0, 0, NULL, 0),
1117
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001118 /* Auxiliary FM PGAs */
1119 SND_SOC_DAPM_PGA("AFMAmpL",
1120 TWL6040_REG_MICLCTL, 1, 0, NULL, 0),
1121 SND_SOC_DAPM_PGA("AFMAmpR",
1122 TWL6040_REG_MICRCTL, 1, 0, NULL, 0),
1123
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001124 /* ADCs */
1125 SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture",
1126 TWL6040_REG_MICLCTL, 2, 0),
1127 SND_SOC_DAPM_ADC("ADC Right", "Right Front Capture",
1128 TWL6040_REG_MICRCTL, 2, 0),
1129
1130 /* Microphone bias */
1131 SND_SOC_DAPM_MICBIAS("Headset Mic Bias",
1132 TWL6040_REG_AMICBCTL, 0, 0),
1133 SND_SOC_DAPM_MICBIAS("Main Mic Bias",
1134 TWL6040_REG_AMICBCTL, 4, 0),
1135 SND_SOC_DAPM_MICBIAS("Digital Mic1 Bias",
1136 TWL6040_REG_DMICBCTL, 0, 0),
1137 SND_SOC_DAPM_MICBIAS("Digital Mic2 Bias",
1138 TWL6040_REG_DMICBCTL, 4, 0),
1139
1140 /* DACs */
Jorge Eduardo Candelaria0fad4ed2010-07-15 11:38:01 -05001141 SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback",
1142 TWL6040_REG_HSLCTL, 0, 0,
1143 twl6040_hs_dac_event,
1144 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
1145 SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback",
1146 TWL6040_REG_HSRCTL, 0, 0,
1147 twl6040_hs_dac_event,
1148 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001149 SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback",
1150 TWL6040_REG_HFLCTL, 0, 0,
1151 twl6040_power_mode_event,
1152 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
1153 SND_SOC_DAPM_DAC_E("HFDAC Right", "Handsfree Playback",
1154 TWL6040_REG_HFRCTL, 0, 0,
1155 twl6040_power_mode_event,
1156 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
1157
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001158 SND_SOC_DAPM_MUX("HF Left Playback",
1159 SND_SOC_NOPM, 0, 0, &hfl_mux_controls),
1160 SND_SOC_DAPM_MUX("HF Right Playback",
1161 SND_SOC_NOPM, 0, 0, &hfr_mux_controls),
1162 /* Analog playback Muxes */
1163 SND_SOC_DAPM_MUX("HS Left Playback",
1164 SND_SOC_NOPM, 0, 0, &hsl_mux_controls),
1165 SND_SOC_DAPM_MUX("HS Right Playback",
1166 SND_SOC_NOPM, 0, 0, &hsr_mux_controls),
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001167
Jorge Eduardo Candelaria0fad4ed2010-07-15 11:38:01 -05001168 /* Analog playback drivers */
Olaya, Margaritaf769bdf2010-12-20 10:39:20 -06001169 SND_SOC_DAPM_OUT_DRV_E("Handsfree Left Driver",
Jorge Eduardo Candelaria0fad4ed2010-07-15 11:38:01 -05001170 TWL6040_REG_HFLCTL, 4, 0, NULL, 0,
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001171 pga_event,
1172 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
Olaya, Margaritaf769bdf2010-12-20 10:39:20 -06001173 SND_SOC_DAPM_OUT_DRV_E("Handsfree Right Driver",
Jorge Eduardo Candelaria0fad4ed2010-07-15 11:38:01 -05001174 TWL6040_REG_HFRCTL, 4, 0, NULL, 0,
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001175 pga_event,
1176 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
Olaya, Margaritaf769bdf2010-12-20 10:39:20 -06001177 SND_SOC_DAPM_OUT_DRV_E("Headset Left Driver",
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001178 TWL6040_REG_HSLCTL, 2, 0, NULL, 0,
1179 pga_event,
1180 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
Olaya, Margaritaf769bdf2010-12-20 10:39:20 -06001181 SND_SOC_DAPM_OUT_DRV_E("Headset Right Driver",
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001182 TWL6040_REG_HSRCTL, 2, 0, NULL, 0,
1183 pga_event,
1184 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
Jorge Eduardo Candelaria871a05a2010-05-18 12:44:18 -05001185 SND_SOC_DAPM_SWITCH_E("Earphone Driver",
1186 SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls,
1187 twl6040_power_mode_event,
1188 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001189
1190 /* Analog playback PGAs */
1191 SND_SOC_DAPM_PGA("HFDAC Left PGA",
1192 TWL6040_REG_HFLCTL, 1, 0, NULL, 0),
1193 SND_SOC_DAPM_PGA("HFDAC Right PGA",
1194 TWL6040_REG_HFRCTL, 1, 0, NULL, 0),
1195
1196};
1197
1198static const struct snd_soc_dapm_route intercon[] = {
1199 /* Capture path */
1200 {"Analog Left Capture Route", "Headset Mic", "HSMIC"},
1201 {"Analog Left Capture Route", "Main Mic", "MAINMIC"},
1202 {"Analog Left Capture Route", "Aux/FM Left", "AFML"},
1203
1204 {"Analog Right Capture Route", "Headset Mic", "HSMIC"},
1205 {"Analog Right Capture Route", "Sub Mic", "SUBMIC"},
1206 {"Analog Right Capture Route", "Aux/FM Right", "AFMR"},
1207
1208 {"MicAmpL", NULL, "Analog Left Capture Route"},
1209 {"MicAmpR", NULL, "Analog Right Capture Route"},
1210
1211 {"ADC Left", NULL, "MicAmpL"},
1212 {"ADC Right", NULL, "MicAmpR"},
1213
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001214 /* AFM path */
Peter Ujfalusi5bf692d2011-09-22 11:05:47 +03001215 {"AFMAmpL", NULL, "AFML"},
1216 {"AFMAmpR", NULL, "AFMR"},
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001217
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001218 {"HS Left Playback", "HS DAC", "HSDAC Left"},
1219 {"HS Left Playback", "Line-In amp", "AFMAmpL"},
1220
1221 {"HS Right Playback", "HS DAC", "HSDAC Right"},
1222 {"HS Right Playback", "Line-In amp", "AFMAmpR"},
1223
Peter Ujfalusi5bf692d2011-09-22 11:05:47 +03001224 {"Headset Left Driver", NULL, "HS Left Playback"},
1225 {"Headset Right Driver", NULL, "HS Right Playback"},
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001226
1227 {"HSOL", NULL, "Headset Left Driver"},
1228 {"HSOR", NULL, "Headset Right Driver"},
1229
Jorge Eduardo Candelaria871a05a2010-05-18 12:44:18 -05001230 /* Earphone playback path */
1231 {"Earphone Driver", "Switch", "HSDAC Left"},
1232 {"EP", NULL, "Earphone Driver"},
1233
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001234 {"HF Left Playback", "HF DAC", "HFDAC Left"},
1235 {"HF Left Playback", "Line-In amp", "AFMAmpL"},
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001236
Jorge Eduardo Candelaria370a0312010-12-10 21:05:32 -06001237 {"HF Right Playback", "HF DAC", "HFDAC Right"},
1238 {"HF Right Playback", "Line-In amp", "AFMAmpR"},
1239
1240 {"HFDAC Left PGA", NULL, "HF Left Playback"},
1241 {"HFDAC Right PGA", NULL, "HF Right Playback"},
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001242
1243 {"Handsfree Left Driver", "Switch", "HFDAC Left PGA"},
1244 {"Handsfree Right Driver", "Switch", "HFDAC Right PGA"},
1245
1246 {"HFL", NULL, "Handsfree Left Driver"},
1247 {"HFR", NULL, "Handsfree Right Driver"},
1248};
1249
1250static int twl6040_add_widgets(struct snd_soc_codec *codec)
1251{
Liam Girdwoodce6120c2010-11-05 15:53:46 +02001252 struct snd_soc_dapm_context *dapm = &codec->dapm;
1253
1254 snd_soc_dapm_new_controls(dapm, twl6040_dapm_widgets,
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001255 ARRAY_SIZE(twl6040_dapm_widgets));
Liam Girdwoodce6120c2010-11-05 15:53:46 +02001256 snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
1257 snd_soc_dapm_new_widgets(dapm);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001258
1259 return 0;
1260}
1261
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001262static int twl6040_set_bias_level(struct snd_soc_codec *codec,
1263 enum snd_soc_bias_level level)
1264{
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001265 struct twl6040 *twl6040 = codec->control_data;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +02001266 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001267 int ret;
1268
1269 switch (level) {
1270 case SND_SOC_BIAS_ON:
1271 break;
1272 case SND_SOC_BIAS_PREPARE:
1273 break;
1274 case SND_SOC_BIAS_STANDBY:
1275 if (priv->codec_powered)
1276 break;
1277
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001278 ret = twl6040_power(twl6040, 1);
1279 if (ret)
1280 return ret;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001281
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001282 priv->codec_powered = 1;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001283
Peter Ujfalusia52762e2011-09-15 15:39:27 +03001284 twl6040_restore_regs(codec);
Olaya, Margarita65b7cec2010-12-14 19:18:36 -06001285
1286 /* Set external boost GPO */
1287 twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001288 break;
1289 case SND_SOC_BIAS_OFF:
1290 if (!priv->codec_powered)
1291 break;
1292
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001293 twl6040_power(twl6040, 0);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001294 priv->codec_powered = 0;
1295 break;
1296 }
1297
Liam Girdwoodce6120c2010-11-05 15:53:46 +02001298 codec->dapm.bias_level = level;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001299
1300 return 0;
1301}
1302
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001303static int twl6040_startup(struct snd_pcm_substream *substream,
1304 struct snd_soc_dai *dai)
1305{
1306 struct snd_soc_pcm_runtime *rtd = substream->private_data;
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001307 struct snd_soc_codec *codec = rtd->codec;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +02001308 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001309
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001310 snd_pcm_hw_constraint_list(substream->runtime, 0,
1311 SNDRV_PCM_HW_PARAM_RATE,
Peter Ujfalusif53c3462011-06-29 13:28:18 +03001312 &sysclk_constraints[priv->pll_power_mode]);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001313
1314 return 0;
1315}
1316
1317static int twl6040_hw_params(struct snd_pcm_substream *substream,
1318 struct snd_pcm_hw_params *params,
1319 struct snd_soc_dai *dai)
1320{
1321 struct snd_soc_pcm_runtime *rtd = substream->private_data;
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001322 struct snd_soc_codec *codec = rtd->codec;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +02001323 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001324 int rate;
1325
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001326 rate = params_rate(params);
1327 switch (rate) {
Olaya, Margarita60ea4ce2010-12-10 21:05:58 -06001328 case 11250:
1329 case 22500:
1330 case 44100:
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001331 case 88200:
Peter Ujfalusi753621c2011-07-03 02:06:07 +03001332 /* These rates are not supported when HPPLL is in use */
1333 if (unlikely(priv->pll == TWL6040_SYSCLK_SEL_HPPLL)) {
1334 dev_err(codec->dev, "HPPLL does not support rate %d\n",
1335 rate);
1336 return -EINVAL;
1337 }
1338 /* Capture is not supported with 17.64MHz sysclk */
1339 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
1340 dev_err(codec->dev,
1341 "capture mode is not supported at %dHz\n",
1342 rate);
1343 return -EINVAL;
1344 }
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001345 priv->sysclk = 17640000;
1346 break;
Olaya, Margarita60ea4ce2010-12-10 21:05:58 -06001347 case 8000:
1348 case 16000:
1349 case 32000:
1350 case 48000:
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001351 case 96000:
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001352 priv->sysclk = 19200000;
1353 break;
1354 default:
1355 dev_err(codec->dev, "unsupported rate %d\n", rate);
1356 return -EINVAL;
1357 }
1358
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001359 return 0;
1360}
1361
Olaya, Margarita4e624d02010-12-10 21:05:54 -06001362static int twl6040_prepare(struct snd_pcm_substream *substream,
1363 struct snd_soc_dai *dai)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001364{
1365 struct snd_soc_pcm_runtime *rtd = substream->private_data;
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001366 struct snd_soc_codec *codec = rtd->codec;
Peter Ujfalusi753621c2011-07-03 02:06:07 +03001367 struct twl6040 *twl6040 = codec->control_data;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +02001368 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Peter Ujfalusi753621c2011-07-03 02:06:07 +03001369 int ret;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001370
Olaya, Margarita4e624d02010-12-10 21:05:54 -06001371 if (!priv->sysclk) {
1372 dev_err(codec->dev,
1373 "no mclk configured, call set_sysclk() on init\n");
1374 return -EINVAL;
1375 }
1376
Olaya, Margarita4e624d02010-12-10 21:05:54 -06001377 if ((priv->sysclk == 17640000) && priv->non_lp) {
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001378 dev_err(codec->dev,
1379 "some enabled paths aren't supported at %dHz\n",
1380 priv->sysclk);
1381 return -EPERM;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001382 }
Peter Ujfalusi753621c2011-07-03 02:06:07 +03001383
1384 ret = twl6040_set_pll(twl6040, priv->pll, priv->clk_in, priv->sysclk);
1385 if (ret) {
1386 dev_err(codec->dev, "Can not set PLL (%d)\n", ret);
1387 return -EPERM;
1388 }
1389
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001390 return 0;
1391}
1392
1393static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
1394 int clk_id, unsigned int freq, int dir)
1395{
1396 struct snd_soc_codec *codec = codec_dai->codec;
Takashi Iwaid4a8ca22010-04-20 08:20:31 +02001397 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001398
1399 switch (clk_id) {
1400 case TWL6040_SYSCLK_SEL_LPPLL:
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001401 case TWL6040_SYSCLK_SEL_HPPLL:
Peter Ujfalusi753621c2011-07-03 02:06:07 +03001402 priv->pll = clk_id;
1403 priv->clk_in = freq;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001404 break;
1405 default:
1406 dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
1407 return -EINVAL;
1408 }
1409
1410 return 0;
1411}
1412
1413static struct snd_soc_dai_ops twl6040_dai_ops = {
1414 .startup = twl6040_startup,
1415 .hw_params = twl6040_hw_params,
Olaya, Margarita4e624d02010-12-10 21:05:54 -06001416 .prepare = twl6040_prepare,
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001417 .set_sysclk = twl6040_set_dai_sysclk,
1418};
1419
Liam Girdwood6510bdc2011-02-11 17:37:51 +00001420static struct snd_soc_dai_driver twl6040_dai[] = {
1421{
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001422 .name = "twl6040-hifi",
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001423 .playback = {
1424 .stream_name = "Playback",
1425 .channels_min = 1,
Peter Ujfalusicdd50542011-09-15 15:59:19 +03001426 .channels_max = 5,
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001427 .rates = TWL6040_RATES,
1428 .formats = TWL6040_FORMATS,
1429 },
1430 .capture = {
1431 .stream_name = "Capture",
1432 .channels_min = 1,
1433 .channels_max = 2,
1434 .rates = TWL6040_RATES,
1435 .formats = TWL6040_FORMATS,
1436 },
1437 .ops = &twl6040_dai_ops,
Peter Ujfalusi21385ee2011-07-05 22:35:53 +03001438},
1439{
Liam Girdwood6510bdc2011-02-11 17:37:51 +00001440 .name = "twl6040-ul",
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001441 .capture = {
1442 .stream_name = "Capture",
1443 .channels_min = 1,
1444 .channels_max = 2,
1445 .rates = TWL6040_RATES,
1446 .formats = TWL6040_FORMATS,
1447 },
1448 .ops = &twl6040_dai_ops,
Liam Girdwood6510bdc2011-02-11 17:37:51 +00001449},
1450{
1451 .name = "twl6040-dl1",
1452 .playback = {
1453 .stream_name = "Headset Playback",
1454 .channels_min = 1,
1455 .channels_max = 2,
1456 .rates = TWL6040_RATES,
1457 .formats = TWL6040_FORMATS,
1458 },
1459 .ops = &twl6040_dai_ops,
1460},
1461{
1462 .name = "twl6040-dl2",
1463 .playback = {
1464 .stream_name = "Handsfree Playback",
1465 .channels_min = 1,
1466 .channels_max = 2,
1467 .rates = TWL6040_RATES,
1468 .formats = TWL6040_FORMATS,
1469 },
1470 .ops = &twl6040_dai_ops,
1471},
1472{
1473 .name = "twl6040-vib",
1474 .playback = {
1475 .stream_name = "Vibra Playback",
Peter Ujfalusid8dd0322011-09-15 15:59:18 +03001476 .channels_min = 1,
1477 .channels_max = 1,
Liam Girdwood6510bdc2011-02-11 17:37:51 +00001478 .rates = SNDRV_PCM_RATE_CONTINUOUS,
1479 .formats = TWL6040_FORMATS,
1480 },
1481 .ops = &twl6040_dai_ops,
1482},
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001483};
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001484
1485#ifdef CONFIG_PM
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001486static int twl6040_suspend(struct snd_soc_codec *codec, pm_message_t state)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001487{
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001488 twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
1489
1490 return 0;
1491}
1492
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001493static int twl6040_resume(struct snd_soc_codec *codec)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001494{
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001495 twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
Olaya, Margarita6c311042010-12-10 21:05:46 -06001496 twl6040_set_bias_level(codec, codec->dapm.suspend_bias_level);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001497
1498 return 0;
1499}
1500#else
1501#define twl6040_suspend NULL
1502#define twl6040_resume NULL
1503#endif
1504
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001505static int twl6040_probe(struct snd_soc_codec *codec)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001506{
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001507 struct twl6040_data *priv;
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -06001508 struct twl4030_codec_data *pdata = dev_get_platdata(codec->dev);
Peter Ujfalusi2a433b92011-07-04 19:52:26 +03001509 struct platform_device *pdev = container_of(codec->dev,
1510 struct platform_device, dev);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001511 int ret = 0;
1512
1513 priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
1514 if (priv == NULL)
1515 return -ENOMEM;
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001516 snd_soc_codec_set_drvdata(codec, priv);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001517
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001518 priv->codec = codec;
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001519 codec->control_data = dev_get_drvdata(codec->dev->parent);
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001520
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -06001521 if (pdata && pdata->hs_left_step && pdata->hs_right_step) {
1522 priv->hs_left_step = pdata->hs_left_step;
1523 priv->hs_right_step = pdata->hs_right_step;
1524 } else {
1525 priv->hs_left_step = 1;
1526 priv->hs_right_step = 1;
1527 }
Olaya, Margarita99903ea2010-12-10 21:06:07 -06001528
Axel Castaneda Gonzalez1fbe9952011-02-23 20:08:28 -06001529 if (pdata && pdata->hf_left_step && pdata->hf_right_step) {
1530 priv->hf_left_step = pdata->hf_left_step;
1531 priv->hf_right_step = pdata->hf_right_step;
1532 } else {
1533 priv->hf_left_step = 1;
1534 priv->hf_right_step = 1;
1535 }
Olaya, Margarita99903ea2010-12-10 21:06:07 -06001536
Peter Ujfalusi2a433b92011-07-04 19:52:26 +03001537 priv->plug_irq = platform_get_irq(pdev, 0);
1538 if (priv->plug_irq < 0) {
1539 dev_err(codec->dev, "invalid irq\n");
1540 ret = -EINVAL;
1541 goto work_err;
1542 }
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001543
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001544 priv->workqueue = create_singlethread_workqueue("twl6040-codec");
Axel Lin19aab082011-03-26 15:53:58 +08001545 if (!priv->workqueue) {
1546 ret = -ENOMEM;
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001547 goto work_err;
Axel Lin19aab082011-03-26 15:53:58 +08001548 }
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001549
1550 INIT_DELAYED_WORK(&priv->delayed_work, twl6040_accessory_work);
1551
1552 mutex_init(&priv->mutex);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001553
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001554 init_completion(&priv->headset.ramp_done);
1555 init_completion(&priv->handsfree.ramp_done);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001556
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001557 priv->hf_workqueue = create_singlethread_workqueue("twl6040-hf");
1558 if (priv->hf_workqueue == NULL) {
1559 ret = -ENOMEM;
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001560 goto hfwq_err;
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001561 }
1562 priv->hs_workqueue = create_singlethread_workqueue("twl6040-hs");
1563 if (priv->hs_workqueue == NULL) {
1564 ret = -ENOMEM;
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001565 goto hswq_err;
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001566 }
1567
1568 INIT_DELAYED_WORK(&priv->hs_delayed_work, twl6040_pga_hs_work);
1569 INIT_DELAYED_WORK(&priv->hf_delayed_work, twl6040_pga_hf_work);
1570
Peter Ujfalusi2a433b92011-07-04 19:52:26 +03001571 ret = request_threaded_irq(priv->plug_irq, NULL, twl6040_audio_handler,
1572 0, "twl6040_irq_plug", codec);
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001573 if (ret) {
1574 dev_err(codec->dev, "PLUG IRQ request failed: %d\n", ret);
1575 goto plugirq_err;
1576 }
1577
Peter Ujfalusia52762e2011-09-15 15:39:27 +03001578 twl6040_init_chip(codec);
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001579
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001580 /* power on device */
1581 ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
1582 if (ret)
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001583 goto bias_err;
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001584
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001585 snd_soc_add_controls(codec, twl6040_snd_controls,
1586 ARRAY_SIZE(twl6040_snd_controls));
1587 twl6040_add_widgets(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001588
1589 return 0;
1590
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001591bias_err:
Peter Ujfalusi2a433b92011-07-04 19:52:26 +03001592 free_irq(priv->plug_irq, codec);
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001593plugirq_err:
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001594 destroy_workqueue(priv->hs_workqueue);
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001595hswq_err:
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001596 destroy_workqueue(priv->hf_workqueue);
Misael Lopez Cruzfb34d3d2011-05-01 21:27:00 -05001597hfwq_err:
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001598 destroy_workqueue(priv->workqueue);
1599work_err:
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001600 kfree(priv);
1601 return ret;
1602}
1603
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001604static int twl6040_remove(struct snd_soc_codec *codec)
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001605{
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001606 struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001607
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001608 twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
Peter Ujfalusi2a433b92011-07-04 19:52:26 +03001609 free_irq(priv->plug_irq, codec);
Jorge Eduardo Candelariaa2d23622010-12-10 20:45:17 -06001610 destroy_workqueue(priv->workqueue);
Margarita Olaya Cabrera1bf84752010-12-14 19:00:21 -06001611 destroy_workqueue(priv->hf_workqueue);
1612 destroy_workqueue(priv->hs_workqueue);
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001613 kfree(priv);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001614
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001615 return 0;
1616}
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001617
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001618static struct snd_soc_codec_driver soc_codec_dev_twl6040 = {
1619 .probe = twl6040_probe,
1620 .remove = twl6040_remove,
1621 .suspend = twl6040_suspend,
1622 .resume = twl6040_resume,
1623 .read = twl6040_read_reg_cache,
1624 .write = twl6040_write,
1625 .set_bias_level = twl6040_set_bias_level,
1626 .reg_cache_size = ARRAY_SIZE(twl6040_reg),
1627 .reg_word_size = sizeof(u8),
1628 .reg_cache_default = twl6040_reg,
1629};
1630
1631static int __devinit twl6040_codec_probe(struct platform_device *pdev)
1632{
Liam Girdwood6510bdc2011-02-11 17:37:51 +00001633 return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_twl6040,
1634 twl6040_dai, ARRAY_SIZE(twl6040_dai));
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001635}
1636
1637static int __devexit twl6040_codec_remove(struct platform_device *pdev)
1638{
1639 snd_soc_unregister_codec(&pdev->dev);
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001640 return 0;
1641}
1642
1643static struct platform_driver twl6040_codec_driver = {
1644 .driver = {
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +00001645 .name = "twl6040-codec",
Misael Lopez Cruz8ecbabd2010-03-19 11:25:51 +00001646 .owner = THIS_MODULE,
1647 },
1648 .probe = twl6040_codec_probe,
1649 .remove = __devexit_p(twl6040_codec_remove),
1650};
1651
1652static int __init twl6040_codec_init(void)
1653{
1654 return platform_driver_register(&twl6040_codec_driver);
1655}
1656module_init(twl6040_codec_init);
1657
1658static void __exit twl6040_codec_exit(void)
1659{
1660 platform_driver_unregister(&twl6040_codec_driver);
1661}
1662module_exit(twl6040_codec_exit);
1663
1664MODULE_DESCRIPTION("ASoC TWL6040 codec driver");
1665MODULE_AUTHOR("Misael Lopez Cruz");
1666MODULE_LICENSE("GPL");