diff options
author | Steve Sakoman <steve@sakoman.com> | 2008-10-31 00:35:26 -0400 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2008-10-31 08:33:27 -0400 |
commit | cc17557e7876a92e11d4b406a367d28e103e42e6 (patch) | |
tree | a214fd9c44be990feca2c8b8019c8d778c3984e3 /sound/soc/codecs/twl4030.c | |
parent | 57b41898c2ecd13a9d338b66ef23f66caab5c4e9 (diff) |
ASoC: Add support for TWL4030 audio codec
Signed-off-by: Steve Sakoman <steve@sakoman.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/twl4030.c')
-rw-r--r-- | sound/soc/codecs/twl4030.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c new file mode 100644 index 000000000000..ee2f0d37765c --- /dev/null +++ b/sound/soc/codecs/twl4030.c | |||
@@ -0,0 +1,653 @@ | |||
1 | /* | ||
2 | * ALSA SoC TWL4030 codec driver | ||
3 | * | ||
4 | * Author: Steve Sakoman, <steve@sakoman.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> | ||
27 | #include <linux/i2c.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/i2c/twl4030.h> | ||
30 | #include <sound/core.h> | ||
31 | #include <sound/pcm.h> | ||
32 | #include <sound/pcm_params.h> | ||
33 | #include <sound/soc.h> | ||
34 | #include <sound/soc-dapm.h> | ||
35 | #include <sound/initval.h> | ||
36 | |||
37 | #include "twl4030.h" | ||
38 | |||
39 | /* | ||
40 | * twl4030 register cache & default register settings | ||
41 | */ | ||
42 | static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { | ||
43 | 0x00, /* this register not used */ | ||
44 | 0x93, /* REG_CODEC_MODE (0x1) */ | ||
45 | 0xc3, /* REG_OPTION (0x2) */ | ||
46 | 0x00, /* REG_UNKNOWN (0x3) */ | ||
47 | 0x00, /* REG_MICBIAS_CTL (0x4) */ | ||
48 | 0x24, /* REG_ANAMICL (0x5) */ | ||
49 | 0x04, /* REG_ANAMICR (0x6) */ | ||
50 | 0x0a, /* REG_AVADC_CTL (0x7) */ | ||
51 | 0x00, /* REG_ADCMICSEL (0x8) */ | ||
52 | 0x00, /* REG_DIGMIXING (0x9) */ | ||
53 | 0x0c, /* REG_ATXL1PGA (0xA) */ | ||
54 | 0x0c, /* REG_ATXR1PGA (0xB) */ | ||
55 | 0x00, /* REG_AVTXL2PGA (0xC) */ | ||
56 | 0x00, /* REG_AVTXR2PGA (0xD) */ | ||
57 | 0x01, /* REG_AUDIO_IF (0xE) */ | ||
58 | 0x00, /* REG_VOICE_IF (0xF) */ | ||
59 | 0x00, /* REG_ARXR1PGA (0x10) */ | ||
60 | 0x00, /* REG_ARXL1PGA (0x11) */ | ||
61 | 0x6c, /* REG_ARXR2PGA (0x12) */ | ||
62 | 0x6c, /* REG_ARXL2PGA (0x13) */ | ||
63 | 0x00, /* REG_VRXPGA (0x14) */ | ||
64 | 0x00, /* REG_VSTPGA (0x15) */ | ||
65 | 0x00, /* REG_VRX2ARXPGA (0x16) */ | ||
66 | 0x0c, /* REG_AVDAC_CTL (0x17) */ | ||
67 | 0x00, /* REG_ARX2VTXPGA (0x18) */ | ||
68 | 0x00, /* REG_ARXL1_APGA_CTL (0x19) */ | ||
69 | 0x00, /* REG_ARXR1_APGA_CTL (0x1A) */ | ||
70 | 0x4b, /* REG_ARXL2_APGA_CTL (0x1B) */ | ||
71 | 0x4b, /* REG_ARXR2_APGA_CTL (0x1C) */ | ||
72 | 0x00, /* REG_ATX2ARXPGA (0x1D) */ | ||
73 | 0x00, /* REG_BT_IF (0x1E) */ | ||
74 | 0x00, /* REG_BTPGA (0x1F) */ | ||
75 | 0x00, /* REG_BTSTPGA (0x20) */ | ||
76 | 0x00, /* REG_EAR_CTL (0x21) */ | ||
77 | 0x24, /* REG_HS_SEL (0x22) */ | ||
78 | 0x0a, /* REG_HS_GAIN_SET (0x23) */ | ||
79 | 0x00, /* REG_HS_POPN_SET (0x24) */ | ||
80 | 0x00, /* REG_PREDL_CTL (0x25) */ | ||
81 | 0x00, /* REG_PREDR_CTL (0x26) */ | ||
82 | 0x00, /* REG_PRECKL_CTL (0x27) */ | ||
83 | 0x00, /* REG_PRECKR_CTL (0x28) */ | ||
84 | 0x00, /* REG_HFL_CTL (0x29) */ | ||
85 | 0x00, /* REG_HFR_CTL (0x2A) */ | ||
86 | 0x00, /* REG_ALC_CTL (0x2B) */ | ||
87 | 0x00, /* REG_ALC_SET1 (0x2C) */ | ||
88 | 0x00, /* REG_ALC_SET2 (0x2D) */ | ||
89 | 0x00, /* REG_BOOST_CTL (0x2E) */ | ||
90 | 0x01, /* REG_SOFTVOL_CTL (0x2F) */ | ||
91 | 0x00, /* REG_DTMF_FREQSEL (0x30) */ | ||
92 | 0x00, /* REG_DTMF_TONEXT1H (0x31) */ | ||
93 | 0x00, /* REG_DTMF_TONEXT1L (0x32) */ | ||
94 | 0x00, /* REG_DTMF_TONEXT2H (0x33) */ | ||
95 | 0x00, /* REG_DTMF_TONEXT2L (0x34) */ | ||
96 | 0x00, /* REG_DTMF_TONOFF (0x35) */ | ||
97 | 0x00, /* REG_DTMF_WANONOFF (0x36) */ | ||
98 | 0x00, /* REG_I2S_RX_SCRAMBLE_H (0x37) */ | ||
99 | 0x00, /* REG_I2S_RX_SCRAMBLE_M (0x38) */ | ||
100 | 0x00, /* REG_I2S_RX_SCRAMBLE_L (0x39) */ | ||
101 | 0x16, /* REG_APLL_CTL (0x3A) */ | ||
102 | 0x00, /* REG_DTMF_CTL (0x3B) */ | ||
103 | 0x00, /* REG_DTMF_PGA_CTL2 (0x3C) */ | ||
104 | 0x00, /* REG_DTMF_PGA_CTL1 (0x3D) */ | ||
105 | 0x00, /* REG_MISC_SET_1 (0x3E) */ | ||
106 | 0x00, /* REG_PCMBTMUX (0x3F) */ | ||
107 | 0x00, /* not used (0x40) */ | ||
108 | 0x00, /* not used (0x41) */ | ||
109 | 0x00, /* not used (0x42) */ | ||
110 | 0x00, /* REG_RX_PATH_SEL (0x43) */ | ||
111 | 0x00, /* REG_VDL_APGA_CTL (0x44) */ | ||
112 | 0x00, /* REG_VIBRA_CTL (0x45) */ | ||
113 | 0x00, /* REG_VIBRA_SET (0x46) */ | ||
114 | 0x00, /* REG_VIBRA_PWM_SET (0x47) */ | ||
115 | 0x00, /* REG_ANAMIC_GAIN (0x48) */ | ||
116 | 0x00, /* REG_MISC_SET_2 (0x49) */ | ||
117 | }; | ||
118 | |||
119 | /* | ||
120 | * read twl4030 register cache | ||
121 | */ | ||
122 | static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec, | ||
123 | unsigned int reg) | ||
124 | { | ||
125 | u8 *cache = codec->reg_cache; | ||
126 | |||
127 | return cache[reg]; | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * write twl4030 register cache | ||
132 | */ | ||
133 | static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec, | ||
134 | u8 reg, u8 value) | ||
135 | { | ||
136 | u8 *cache = codec->reg_cache; | ||
137 | |||
138 | if (reg >= TWL4030_CACHEREGNUM) | ||
139 | return; | ||
140 | cache[reg] = value; | ||
141 | } | ||
142 | |||
143 | /* | ||
144 | * write to the twl4030 register space | ||
145 | */ | ||
146 | static int twl4030_write(struct snd_soc_codec *codec, | ||
147 | unsigned int reg, unsigned int value) | ||
148 | { | ||
149 | twl4030_write_reg_cache(codec, reg, value); | ||
150 | return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); | ||
151 | } | ||
152 | |||
153 | static void twl4030_clear_codecpdz(struct snd_soc_codec *codec) | ||
154 | { | ||
155 | u8 mode; | ||
156 | |||
157 | mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); | ||
158 | twl4030_write(codec, TWL4030_REG_CODEC_MODE, | ||
159 | mode & ~TWL4030_CODECPDZ); | ||
160 | |||
161 | /* REVISIT: this delay is present in TI sample drivers */ | ||
162 | /* but there seems to be no TRM requirement for it */ | ||
163 | udelay(10); | ||
164 | } | ||
165 | |||
166 | static void twl4030_set_codecpdz(struct snd_soc_codec *codec) | ||
167 | { | ||
168 | u8 mode; | ||
169 | |||
170 | mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); | ||
171 | twl4030_write(codec, TWL4030_REG_CODEC_MODE, | ||
172 | mode | TWL4030_CODECPDZ); | ||
173 | |||
174 | /* REVISIT: this delay is present in TI sample drivers */ | ||
175 | /* but there seems to be no TRM requirement for it */ | ||
176 | udelay(10); | ||
177 | } | ||
178 | |||
179 | static void twl4030_init_chip(struct snd_soc_codec *codec) | ||
180 | { | ||
181 | int i; | ||
182 | |||
183 | /* clear CODECPDZ prior to setting register defaults */ | ||
184 | twl4030_clear_codecpdz(codec); | ||
185 | |||
186 | /* set all audio section registers to reasonable defaults */ | ||
187 | for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++) | ||
188 | twl4030_write(codec, i, twl4030_reg[i]); | ||
189 | |||
190 | } | ||
191 | |||
192 | static const struct snd_kcontrol_new twl4030_snd_controls[] = { | ||
193 | SOC_DOUBLE_R("Master Playback Volume", | ||
194 | TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, | ||
195 | 0, 127, 0), | ||
196 | SOC_DOUBLE_R("Capture Volume", | ||
197 | TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, | ||
198 | 0, 127, 0), | ||
199 | }; | ||
200 | |||
201 | /* add non dapm controls */ | ||
202 | static int twl4030_add_controls(struct snd_soc_codec *codec) | ||
203 | { | ||
204 | int err, i; | ||
205 | |||
206 | for (i = 0; i < ARRAY_SIZE(twl4030_snd_controls); i++) { | ||
207 | err = snd_ctl_add(codec->card, | ||
208 | snd_soc_cnew(&twl4030_snd_controls[i], | ||
209 | codec, NULL)); | ||
210 | if (err < 0) | ||
211 | return err; | ||
212 | } | ||
213 | |||
214 | return 0; | ||
215 | } | ||
216 | |||
217 | static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { | ||
218 | SND_SOC_DAPM_INPUT("INL"), | ||
219 | SND_SOC_DAPM_INPUT("INR"), | ||
220 | |||
221 | SND_SOC_DAPM_OUTPUT("OUTL"), | ||
222 | SND_SOC_DAPM_OUTPUT("OUTR"), | ||
223 | |||
224 | SND_SOC_DAPM_DAC("DACL", "Left Playback", SND_SOC_NOPM, 0, 0), | ||
225 | SND_SOC_DAPM_DAC("DACR", "Right Playback", SND_SOC_NOPM, 0, 0), | ||
226 | |||
227 | SND_SOC_DAPM_ADC("ADCL", "Left Capture", SND_SOC_NOPM, 0, 0), | ||
228 | SND_SOC_DAPM_ADC("ADCR", "Right Capture", SND_SOC_NOPM, 0, 0), | ||
229 | }; | ||
230 | |||
231 | static const struct snd_soc_dapm_route intercon[] = { | ||
232 | /* outputs */ | ||
233 | {"OUTL", NULL, "DACL"}, | ||
234 | {"OUTR", NULL, "DACR"}, | ||
235 | |||
236 | /* inputs */ | ||
237 | {"ADCL", NULL, "INL"}, | ||
238 | {"ADCR", NULL, "INR"}, | ||
239 | }; | ||
240 | |||
241 | static int twl4030_add_widgets(struct snd_soc_codec *codec) | ||
242 | { | ||
243 | snd_soc_dapm_new_controls(codec, twl4030_dapm_widgets, | ||
244 | ARRAY_SIZE(twl4030_dapm_widgets)); | ||
245 | |||
246 | snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); | ||
247 | |||
248 | snd_soc_dapm_new_widgets(codec); | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static void twl4030_power_up(struct snd_soc_codec *codec) | ||
253 | { | ||
254 | u8 anamicl, regmisc1, byte, popn, hsgain; | ||
255 | int i = 0; | ||
256 | |||
257 | /* set CODECPDZ to turn on codec */ | ||
258 | twl4030_set_codecpdz(codec); | ||
259 | |||
260 | /* initiate offset cancellation */ | ||
261 | anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); | ||
262 | twl4030_write(codec, TWL4030_REG_ANAMICL, | ||
263 | anamicl | TWL4030_CNCL_OFFSET_START); | ||
264 | |||
265 | /* wait for offset cancellation to complete */ | ||
266 | do { | ||
267 | /* this takes a little while, so don't slam i2c */ | ||
268 | udelay(2000); | ||
269 | twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, | ||
270 | TWL4030_REG_ANAMICL); | ||
271 | } while ((i++ < 100) && | ||
272 | ((byte & TWL4030_CNCL_OFFSET_START) == | ||
273 | TWL4030_CNCL_OFFSET_START)); | ||
274 | |||
275 | /* anti-pop when changing analog gain */ | ||
276 | regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1); | ||
277 | twl4030_write(codec, TWL4030_REG_MISC_SET_1, | ||
278 | regmisc1 | TWL4030_SMOOTH_ANAVOL_EN); | ||
279 | |||
280 | /* toggle CODECPDZ as per TRM */ | ||
281 | twl4030_clear_codecpdz(codec); | ||
282 | twl4030_set_codecpdz(codec); | ||
283 | |||
284 | /* program anti-pop with bias ramp delay */ | ||
285 | popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); | ||
286 | popn &= TWL4030_RAMP_DELAY; | ||
287 | popn |= TWL4030_RAMP_DELAY_645MS; | ||
288 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); | ||
289 | popn |= TWL4030_VMID_EN; | ||
290 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); | ||
291 | |||
292 | /* enable output stage and gain setting */ | ||
293 | hsgain = TWL4030_HSR_GAIN_0DB | TWL4030_HSL_GAIN_0DB; | ||
294 | twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hsgain); | ||
295 | |||
296 | /* enable anti-pop ramp */ | ||
297 | popn |= TWL4030_RAMP_EN; | ||
298 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); | ||
299 | } | ||
300 | |||
301 | static void twl4030_power_down(struct snd_soc_codec *codec) | ||
302 | { | ||
303 | u8 popn, hsgain; | ||
304 | |||
305 | /* disable anti-pop ramp */ | ||
306 | popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); | ||
307 | popn &= ~TWL4030_RAMP_EN; | ||
308 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); | ||
309 | |||
310 | /* disable output stage and gain setting */ | ||
311 | hsgain = TWL4030_HSR_GAIN_PWR_DOWN | TWL4030_HSL_GAIN_PWR_DOWN; | ||
312 | twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hsgain); | ||
313 | |||
314 | /* disable bias out */ | ||
315 | popn &= ~TWL4030_VMID_EN; | ||
316 | twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); | ||
317 | |||
318 | /* power down */ | ||
319 | twl4030_clear_codecpdz(codec); | ||
320 | } | ||
321 | |||
322 | static int twl4030_set_bias_level(struct snd_soc_codec *codec, | ||
323 | enum snd_soc_bias_level level) | ||
324 | { | ||
325 | switch (level) { | ||
326 | case SND_SOC_BIAS_ON: | ||
327 | twl4030_power_up(codec); | ||
328 | break; | ||
329 | case SND_SOC_BIAS_PREPARE: | ||
330 | /* TODO: develop a twl4030_prepare function */ | ||
331 | break; | ||
332 | case SND_SOC_BIAS_STANDBY: | ||
333 | /* TODO: develop a twl4030_standby function */ | ||
334 | twl4030_power_down(codec); | ||
335 | break; | ||
336 | case SND_SOC_BIAS_OFF: | ||
337 | twl4030_power_down(codec); | ||
338 | break; | ||
339 | } | ||
340 | codec->bias_level = level; | ||
341 | |||
342 | return 0; | ||
343 | } | ||
344 | |||
345 | static int twl4030_hw_params(struct snd_pcm_substream *substream, | ||
346 | struct snd_pcm_hw_params *params) | ||
347 | { | ||
348 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
349 | struct snd_soc_device *socdev = rtd->socdev; | ||
350 | struct snd_soc_codec *codec = socdev->codec; | ||
351 | u8 mode, old_mode, format, old_format; | ||
352 | |||
353 | |||
354 | /* bit rate */ | ||
355 | old_mode = twl4030_read_reg_cache(codec, | ||
356 | TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ; | ||
357 | mode = old_mode & ~TWL4030_APLL_RATE; | ||
358 | |||
359 | switch (params_rate(params)) { | ||
360 | case 8000: | ||
361 | mode |= TWL4030_APLL_RATE_8000; | ||
362 | break; | ||
363 | case 11025: | ||
364 | mode |= TWL4030_APLL_RATE_11025; | ||
365 | break; | ||
366 | case 12000: | ||
367 | mode |= TWL4030_APLL_RATE_12000; | ||
368 | break; | ||
369 | case 16000: | ||
370 | mode |= TWL4030_APLL_RATE_16000; | ||
371 | break; | ||
372 | case 22050: | ||
373 | mode |= TWL4030_APLL_RATE_22050; | ||
374 | break; | ||
375 | case 24000: | ||
376 | mode |= TWL4030_APLL_RATE_24000; | ||
377 | break; | ||
378 | case 32000: | ||
379 | mode |= TWL4030_APLL_RATE_32000; | ||
380 | break; | ||
381 | case 44100: | ||
382 | mode |= TWL4030_APLL_RATE_44100; | ||
383 | break; | ||
384 | case 48000: | ||
385 | mode |= TWL4030_APLL_RATE_48000; | ||
386 | break; | ||
387 | default: | ||
388 | printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n", | ||
389 | params_rate(params)); | ||
390 | return -EINVAL; | ||
391 | } | ||
392 | |||
393 | if (mode != old_mode) { | ||
394 | /* change rate and set CODECPDZ */ | ||
395 | twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); | ||
396 | twl4030_set_codecpdz(codec); | ||
397 | } | ||
398 | |||
399 | /* sample size */ | ||
400 | old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); | ||
401 | format = old_format; | ||
402 | format &= ~TWL4030_DATA_WIDTH; | ||
403 | switch (params_format(params)) { | ||
404 | case SNDRV_PCM_FORMAT_S16_LE: | ||
405 | format |= TWL4030_DATA_WIDTH_16S_16W; | ||
406 | break; | ||
407 | case SNDRV_PCM_FORMAT_S24_LE: | ||
408 | format |= TWL4030_DATA_WIDTH_32S_24W; | ||
409 | break; | ||
410 | default: | ||
411 | printk(KERN_ERR "TWL4030 hw params: unknown format %d\n", | ||
412 | params_format(params)); | ||
413 | return -EINVAL; | ||
414 | } | ||
415 | |||
416 | if (format != old_format) { | ||
417 | |||
418 | /* clear CODECPDZ before changing format (codec requirement) */ | ||
419 | twl4030_clear_codecpdz(codec); | ||
420 | |||
421 | /* change format */ | ||
422 | twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); | ||
423 | |||
424 | /* set CODECPDZ afterwards */ | ||
425 | twl4030_set_codecpdz(codec); | ||
426 | } | ||
427 | return 0; | ||
428 | } | ||
429 | |||
430 | static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai, | ||
431 | int clk_id, unsigned int freq, int dir) | ||
432 | { | ||
433 | struct snd_soc_codec *codec = codec_dai->codec; | ||
434 | u8 infreq; | ||
435 | |||
436 | switch (freq) { | ||
437 | case 19200000: | ||
438 | infreq = TWL4030_APLL_INFREQ_19200KHZ; | ||
439 | break; | ||
440 | case 26000000: | ||
441 | infreq = TWL4030_APLL_INFREQ_26000KHZ; | ||
442 | break; | ||
443 | case 38400000: | ||
444 | infreq = TWL4030_APLL_INFREQ_38400KHZ; | ||
445 | break; | ||
446 | default: | ||
447 | printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n", | ||
448 | freq); | ||
449 | return -EINVAL; | ||
450 | } | ||
451 | |||
452 | infreq |= TWL4030_APLL_EN; | ||
453 | twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq); | ||
454 | |||
455 | return 0; | ||
456 | } | ||
457 | |||
458 | static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, | ||
459 | unsigned int fmt) | ||
460 | { | ||
461 | struct snd_soc_codec *codec = codec_dai->codec; | ||
462 | u8 old_format, format; | ||
463 | |||
464 | /* get format */ | ||
465 | old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); | ||
466 | format = old_format; | ||
467 | |||
468 | /* set master/slave audio interface */ | ||
469 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
470 | case SND_SOC_DAIFMT_CBM_CFM: | ||
471 | format &= ~(TWL4030_AIF_SLAVE_EN); | ||
472 | format |= TWL4030_CLK256FS_EN; | ||
473 | break; | ||
474 | case SND_SOC_DAIFMT_CBS_CFS: | ||
475 | format &= ~(TWL4030_CLK256FS_EN); | ||
476 | format |= TWL4030_AIF_SLAVE_EN; | ||
477 | break; | ||
478 | default: | ||
479 | return -EINVAL; | ||
480 | } | ||
481 | |||
482 | /* interface format */ | ||
483 | format &= ~TWL4030_AIF_FORMAT; | ||
484 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
485 | case SND_SOC_DAIFMT_I2S: | ||
486 | format |= TWL4030_AIF_FORMAT_CODEC; | ||
487 | break; | ||
488 | default: | ||
489 | return -EINVAL; | ||
490 | } | ||
491 | |||
492 | if (format != old_format) { | ||
493 | |||
494 | /* clear CODECPDZ before changing format (codec requirement) */ | ||
495 | twl4030_clear_codecpdz(codec); | ||
496 | |||
497 | /* change format */ | ||
498 | twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); | ||
499 | |||
500 | /* set CODECPDZ afterwards */ | ||
501 | twl4030_set_codecpdz(codec); | ||
502 | } | ||
503 | |||
504 | return 0; | ||
505 | } | ||
506 | |||
507 | #define TWL4030_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) | ||
508 | #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) | ||
509 | |||
510 | struct snd_soc_dai twl4030_dai = { | ||
511 | .name = "twl4030", | ||
512 | .playback = { | ||
513 | .stream_name = "Playback", | ||
514 | .channels_min = 2, | ||
515 | .channels_max = 2, | ||
516 | .rates = TWL4030_RATES, | ||
517 | .formats = TWL4030_FORMATS,}, | ||
518 | .capture = { | ||
519 | .stream_name = "Capture", | ||
520 | .channels_min = 2, | ||
521 | .channels_max = 2, | ||
522 | .rates = TWL4030_RATES, | ||
523 | .formats = TWL4030_FORMATS,}, | ||
524 | .ops = { | ||
525 | .hw_params = twl4030_hw_params, | ||
526 | }, | ||
527 | .dai_ops = { | ||
528 | .set_sysclk = twl4030_set_dai_sysclk, | ||
529 | .set_fmt = twl4030_set_dai_fmt, | ||
530 | } | ||
531 | }; | ||
532 | EXPORT_SYMBOL_GPL(twl4030_dai); | ||
533 | |||
534 | static int twl4030_suspend(struct platform_device *pdev, pm_message_t state) | ||
535 | { | ||
536 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
537 | struct snd_soc_codec *codec = socdev->codec; | ||
538 | |||
539 | twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF); | ||
540 | |||
541 | return 0; | ||
542 | } | ||
543 | |||
544 | static int twl4030_resume(struct platform_device *pdev) | ||
545 | { | ||
546 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
547 | struct snd_soc_codec *codec = socdev->codec; | ||
548 | |||
549 | twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
550 | twl4030_set_bias_level(codec, codec->suspend_bias_level); | ||
551 | return 0; | ||
552 | } | ||
553 | |||
554 | /* | ||
555 | * initialize the driver | ||
556 | * register the mixer and dsp interfaces with the kernel | ||
557 | */ | ||
558 | |||
559 | static int twl4030_init(struct snd_soc_device *socdev) | ||
560 | { | ||
561 | struct snd_soc_codec *codec = socdev->codec; | ||
562 | int ret = 0; | ||
563 | |||
564 | printk(KERN_INFO "TWL4030 Audio Codec init \n"); | ||
565 | |||
566 | codec->name = "twl4030"; | ||
567 | codec->owner = THIS_MODULE; | ||
568 | codec->read = twl4030_read_reg_cache; | ||
569 | codec->write = twl4030_write; | ||
570 | codec->set_bias_level = twl4030_set_bias_level; | ||
571 | codec->dai = &twl4030_dai; | ||
572 | codec->num_dai = 1; | ||
573 | codec->reg_cache_size = sizeof(twl4030_reg); | ||
574 | codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg), | ||
575 | GFP_KERNEL); | ||
576 | if (codec->reg_cache == NULL) | ||
577 | return -ENOMEM; | ||
578 | |||
579 | /* register pcms */ | ||
580 | ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); | ||
581 | if (ret < 0) { | ||
582 | printk(KERN_ERR "twl4030: failed to create pcms\n"); | ||
583 | goto pcm_err; | ||
584 | } | ||
585 | |||
586 | twl4030_init_chip(codec); | ||
587 | |||
588 | /* power on device */ | ||
589 | twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
590 | |||
591 | twl4030_add_controls(codec); | ||
592 | twl4030_add_widgets(codec); | ||
593 | |||
594 | ret = snd_soc_register_card(socdev); | ||
595 | if (ret < 0) { | ||
596 | printk(KERN_ERR "twl4030: failed to register card\n"); | ||
597 | goto card_err; | ||
598 | } | ||
599 | |||
600 | return ret; | ||
601 | |||
602 | card_err: | ||
603 | snd_soc_free_pcms(socdev); | ||
604 | snd_soc_dapm_free(socdev); | ||
605 | pcm_err: | ||
606 | kfree(codec->reg_cache); | ||
607 | return ret; | ||
608 | } | ||
609 | |||
610 | static struct snd_soc_device *twl4030_socdev; | ||
611 | |||
612 | static int twl4030_probe(struct platform_device *pdev) | ||
613 | { | ||
614 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
615 | struct snd_soc_codec *codec; | ||
616 | |||
617 | codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); | ||
618 | if (codec == NULL) | ||
619 | return -ENOMEM; | ||
620 | |||
621 | socdev->codec = codec; | ||
622 | mutex_init(&codec->mutex); | ||
623 | INIT_LIST_HEAD(&codec->dapm_widgets); | ||
624 | INIT_LIST_HEAD(&codec->dapm_paths); | ||
625 | |||
626 | twl4030_socdev = socdev; | ||
627 | twl4030_init(socdev); | ||
628 | |||
629 | return 0; | ||
630 | } | ||
631 | |||
632 | static int twl4030_remove(struct platform_device *pdev) | ||
633 | { | ||
634 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
635 | struct snd_soc_codec *codec = socdev->codec; | ||
636 | |||
637 | printk(KERN_INFO "TWL4030 Audio Codec remove\n"); | ||
638 | kfree(codec); | ||
639 | |||
640 | return 0; | ||
641 | } | ||
642 | |||
643 | struct snd_soc_codec_device soc_codec_dev_twl4030 = { | ||
644 | .probe = twl4030_probe, | ||
645 | .remove = twl4030_remove, | ||
646 | .suspend = twl4030_suspend, | ||
647 | .resume = twl4030_resume, | ||
648 | }; | ||
649 | EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030); | ||
650 | |||
651 | MODULE_DESCRIPTION("ASoC TWL4030 codec driver"); | ||
652 | MODULE_AUTHOR("Steve Sakoman"); | ||
653 | MODULE_LICENSE("GPL"); | ||