diff options
Diffstat (limited to 'sound/soc/codecs/cs42l51.c')
-rw-r--r-- | sound/soc/codecs/cs42l51.c | 763 |
1 files changed, 763 insertions, 0 deletions
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c new file mode 100644 index 000000000000..dd9b8550c402 --- /dev/null +++ b/sound/soc/codecs/cs42l51.c | |||
@@ -0,0 +1,763 @@ | |||
1 | /* | ||
2 | * cs42l51.c | ||
3 | * | ||
4 | * ASoC Driver for Cirrus Logic CS42L51 codecs | ||
5 | * | ||
6 | * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
7 | * | ||
8 | * Based on cs4270.c - Copyright (c) Freescale Semiconductor | ||
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, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * For now: | ||
20 | * - Only I2C is support. Not SPI | ||
21 | * - master mode *NOT* supported | ||
22 | */ | ||
23 | |||
24 | #include <linux/module.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <sound/core.h> | ||
28 | #include <sound/soc.h> | ||
29 | #include <sound/soc-dapm.h> | ||
30 | #include <sound/tlv.h> | ||
31 | #include <sound/initval.h> | ||
32 | #include <sound/pcm_params.h> | ||
33 | #include <sound/pcm.h> | ||
34 | #include <linux/i2c.h> | ||
35 | |||
36 | #include "cs42l51.h" | ||
37 | |||
38 | enum master_slave_mode { | ||
39 | MODE_SLAVE, | ||
40 | MODE_SLAVE_AUTO, | ||
41 | MODE_MASTER, | ||
42 | }; | ||
43 | |||
44 | struct cs42l51_private { | ||
45 | unsigned int mclk; | ||
46 | unsigned int audio_mode; /* The mode (I2S or left-justified) */ | ||
47 | enum master_slave_mode func; | ||
48 | struct snd_soc_codec codec; | ||
49 | u8 reg_cache[CS42L51_NUMREGS]; | ||
50 | }; | ||
51 | |||
52 | static struct snd_soc_codec *cs42l51_codec; | ||
53 | |||
54 | #define CS42L51_FORMATS ( \ | ||
55 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ | ||
56 | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ | ||
57 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ | ||
58 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) | ||
59 | |||
60 | static int cs42l51_fill_cache(struct snd_soc_codec *codec) | ||
61 | { | ||
62 | u8 *cache = codec->reg_cache + 1; | ||
63 | struct i2c_client *i2c_client = codec->control_data; | ||
64 | s32 length; | ||
65 | |||
66 | length = i2c_smbus_read_i2c_block_data(i2c_client, | ||
67 | CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache); | ||
68 | if (length != CS42L51_NUMREGS) { | ||
69 | dev_err(&i2c_client->dev, | ||
70 | "I2C read failure, addr=0x%x (ret=%d vs %d)\n", | ||
71 | i2c_client->addr, length, CS42L51_NUMREGS); | ||
72 | return -EIO; | ||
73 | } | ||
74 | |||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | static int cs42l51_i2c_probe(struct i2c_client *i2c_client, | ||
79 | const struct i2c_device_id *id) | ||
80 | { | ||
81 | struct snd_soc_codec *codec; | ||
82 | struct cs42l51_private *cs42l51; | ||
83 | int ret = 0; | ||
84 | int reg; | ||
85 | |||
86 | if (cs42l51_codec) | ||
87 | return -EBUSY; | ||
88 | |||
89 | /* Verify that we have a CS42L51 */ | ||
90 | ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID); | ||
91 | if (ret < 0) { | ||
92 | dev_err(&i2c_client->dev, "failed to read I2C\n"); | ||
93 | goto error; | ||
94 | } | ||
95 | |||
96 | if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && | ||
97 | (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { | ||
98 | dev_err(&i2c_client->dev, "Invalid chip id\n"); | ||
99 | ret = -ENODEV; | ||
100 | goto error; | ||
101 | } | ||
102 | |||
103 | dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n", | ||
104 | ret & 7); | ||
105 | |||
106 | cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL); | ||
107 | if (!cs42l51) { | ||
108 | dev_err(&i2c_client->dev, "could not allocate codec\n"); | ||
109 | return -ENOMEM; | ||
110 | } | ||
111 | codec = &cs42l51->codec; | ||
112 | |||
113 | mutex_init(&codec->mutex); | ||
114 | INIT_LIST_HEAD(&codec->dapm_widgets); | ||
115 | INIT_LIST_HEAD(&codec->dapm_paths); | ||
116 | |||
117 | codec->dev = &i2c_client->dev; | ||
118 | codec->name = "CS42L51"; | ||
119 | codec->owner = THIS_MODULE; | ||
120 | codec->dai = &cs42l51_dai; | ||
121 | codec->num_dai = 1; | ||
122 | snd_soc_codec_set_drvdata(codec, cs42l51); | ||
123 | |||
124 | codec->control_data = i2c_client; | ||
125 | codec->reg_cache = cs42l51->reg_cache; | ||
126 | codec->reg_cache_size = CS42L51_NUMREGS; | ||
127 | i2c_set_clientdata(i2c_client, codec); | ||
128 | |||
129 | ret = cs42l51_fill_cache(codec); | ||
130 | if (ret < 0) { | ||
131 | dev_err(&i2c_client->dev, "failed to fill register cache\n"); | ||
132 | goto error_alloc; | ||
133 | } | ||
134 | |||
135 | ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); | ||
136 | if (ret < 0) { | ||
137 | dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret); | ||
138 | goto error_alloc; | ||
139 | } | ||
140 | |||
141 | /* | ||
142 | * DAC configuration | ||
143 | * - Use signal processor | ||
144 | * - auto mute | ||
145 | * - vol changes immediate | ||
146 | * - no de-emphasize | ||
147 | */ | ||
148 | reg = CS42L51_DAC_CTL_DATA_SEL(1) | ||
149 | | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); | ||
150 | ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); | ||
151 | if (ret < 0) | ||
152 | goto error_alloc; | ||
153 | |||
154 | cs42l51_dai.dev = codec->dev; | ||
155 | cs42l51_codec = codec; | ||
156 | |||
157 | ret = snd_soc_register_codec(codec); | ||
158 | if (ret != 0) { | ||
159 | dev_err(codec->dev, "Failed to register codec: %d\n", ret); | ||
160 | goto error_alloc; | ||
161 | } | ||
162 | |||
163 | ret = snd_soc_register_dai(&cs42l51_dai); | ||
164 | if (ret < 0) { | ||
165 | dev_err(&i2c_client->dev, "failed to register DAIe\n"); | ||
166 | goto error_reg; | ||
167 | } | ||
168 | |||
169 | return 0; | ||
170 | |||
171 | error_reg: | ||
172 | snd_soc_unregister_codec(codec); | ||
173 | error_alloc: | ||
174 | kfree(cs42l51); | ||
175 | error: | ||
176 | return ret; | ||
177 | } | ||
178 | |||
179 | static int cs42l51_i2c_remove(struct i2c_client *client) | ||
180 | { | ||
181 | struct cs42l51_private *cs42l51 = i2c_get_clientdata(client); | ||
182 | snd_soc_unregister_dai(&cs42l51_dai); | ||
183 | snd_soc_unregister_codec(cs42l51_codec); | ||
184 | cs42l51_codec = NULL; | ||
185 | kfree(cs42l51); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | |||
190 | static const struct i2c_device_id cs42l51_id[] = { | ||
191 | {"cs42l51", 0}, | ||
192 | {} | ||
193 | }; | ||
194 | MODULE_DEVICE_TABLE(i2c, cs42l51_id); | ||
195 | |||
196 | static struct i2c_driver cs42l51_i2c_driver = { | ||
197 | .driver = { | ||
198 | .name = "CS42L51 I2C", | ||
199 | .owner = THIS_MODULE, | ||
200 | }, | ||
201 | .id_table = cs42l51_id, | ||
202 | .probe = cs42l51_i2c_probe, | ||
203 | .remove = cs42l51_i2c_remove, | ||
204 | }; | ||
205 | |||
206 | static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, | ||
207 | struct snd_ctl_elem_value *ucontrol) | ||
208 | { | ||
209 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
210 | unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; | ||
211 | |||
212 | switch (value) { | ||
213 | default: | ||
214 | case 0: | ||
215 | ucontrol->value.integer.value[0] = 0; | ||
216 | break; | ||
217 | /* same value : (L+R)/2 and (R+L)/2 */ | ||
218 | case 1: | ||
219 | case 2: | ||
220 | ucontrol->value.integer.value[0] = 1; | ||
221 | break; | ||
222 | case 3: | ||
223 | ucontrol->value.integer.value[0] = 2; | ||
224 | break; | ||
225 | } | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | #define CHAN_MIX_NORMAL 0x00 | ||
231 | #define CHAN_MIX_BOTH 0x55 | ||
232 | #define CHAN_MIX_SWAP 0xFF | ||
233 | |||
234 | static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, | ||
235 | struct snd_ctl_elem_value *ucontrol) | ||
236 | { | ||
237 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
238 | unsigned char val; | ||
239 | |||
240 | switch (ucontrol->value.integer.value[0]) { | ||
241 | default: | ||
242 | case 0: | ||
243 | val = CHAN_MIX_NORMAL; | ||
244 | break; | ||
245 | case 1: | ||
246 | val = CHAN_MIX_BOTH; | ||
247 | break; | ||
248 | case 2: | ||
249 | val = CHAN_MIX_SWAP; | ||
250 | break; | ||
251 | } | ||
252 | |||
253 | snd_soc_write(codec, CS42L51_PCM_MIXER, val); | ||
254 | |||
255 | return 1; | ||
256 | } | ||
257 | |||
258 | static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); | ||
259 | static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); | ||
260 | /* This is a lie. after -102 db, it stays at -102 */ | ||
261 | /* maybe a range would be better */ | ||
262 | static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0); | ||
263 | |||
264 | static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); | ||
265 | static const char *chan_mix[] = { | ||
266 | "L R", | ||
267 | "L+R", | ||
268 | "R L", | ||
269 | }; | ||
270 | |||
271 | static const struct soc_enum cs42l51_chan_mix = | ||
272 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix); | ||
273 | |||
274 | static const struct snd_kcontrol_new cs42l51_snd_controls[] = { | ||
275 | SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", | ||
276 | CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, | ||
277 | 7, 0xffffff99, 0x18, adc_pcm_tlv), | ||
278 | SOC_DOUBLE_R("PCM Playback Switch", | ||
279 | CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), | ||
280 | SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", | ||
281 | CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, | ||
282 | 8, 0xffffff19, 0x18, aout_tlv), | ||
283 | SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", | ||
284 | CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, | ||
285 | 7, 0xffffff99, 0x18, adc_pcm_tlv), | ||
286 | SOC_DOUBLE_R("ADC Mixer Switch", | ||
287 | CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), | ||
288 | SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), | ||
289 | SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), | ||
290 | SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), | ||
291 | SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), | ||
292 | SOC_DOUBLE_TLV("Mic Boost Volume", | ||
293 | CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), | ||
294 | SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), | ||
295 | SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), | ||
296 | SOC_ENUM_EXT("PCM channel mixer", | ||
297 | cs42l51_chan_mix, | ||
298 | cs42l51_get_chan_mix, cs42l51_set_chan_mix), | ||
299 | }; | ||
300 | |||
301 | /* | ||
302 | * to power down, one must: | ||
303 | * 1.) Enable the PDN bit | ||
304 | * 2.) enable power-down for the select channels | ||
305 | * 3.) disable the PDN bit. | ||
306 | */ | ||
307 | static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, | ||
308 | struct snd_kcontrol *kcontrol, int event) | ||
309 | { | ||
310 | unsigned long value; | ||
311 | |||
312 | value = snd_soc_read(w->codec, CS42L51_POWER_CTL1); | ||
313 | value &= ~CS42L51_POWER_CTL1_PDN; | ||
314 | |||
315 | switch (event) { | ||
316 | case SND_SOC_DAPM_PRE_PMD: | ||
317 | value |= CS42L51_POWER_CTL1_PDN; | ||
318 | break; | ||
319 | default: | ||
320 | case SND_SOC_DAPM_POST_PMD: | ||
321 | break; | ||
322 | } | ||
323 | snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, | ||
324 | CS42L51_POWER_CTL1_PDN, value); | ||
325 | |||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static const char *cs42l51_dac_names[] = {"Direct PCM", | ||
330 | "DSP PCM", "ADC"}; | ||
331 | static const struct soc_enum cs42l51_dac_mux_enum = | ||
332 | SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names); | ||
333 | static const struct snd_kcontrol_new cs42l51_dac_mux_controls = | ||
334 | SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); | ||
335 | |||
336 | static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", | ||
337 | "MIC Left", "MIC+preamp Left"}; | ||
338 | static const struct soc_enum cs42l51_adcl_mux_enum = | ||
339 | SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names); | ||
340 | static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = | ||
341 | SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); | ||
342 | |||
343 | static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", | ||
344 | "MIC Right", "MIC+preamp Right"}; | ||
345 | static const struct soc_enum cs42l51_adcr_mux_enum = | ||
346 | SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names); | ||
347 | static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = | ||
348 | SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); | ||
349 | |||
350 | static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { | ||
351 | SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), | ||
352 | SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, | ||
353 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
354 | SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, | ||
355 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
356 | SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", | ||
357 | CS42L51_POWER_CTL1, 1, 1, | ||
358 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
359 | SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", | ||
360 | CS42L51_POWER_CTL1, 2, 1, | ||
361 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
362 | SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", | ||
363 | CS42L51_POWER_CTL1, 5, 1, | ||
364 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
365 | SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", | ||
366 | CS42L51_POWER_CTL1, 6, 1, | ||
367 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
368 | |||
369 | /* analog/mic */ | ||
370 | SND_SOC_DAPM_INPUT("AIN1L"), | ||
371 | SND_SOC_DAPM_INPUT("AIN1R"), | ||
372 | SND_SOC_DAPM_INPUT("AIN2L"), | ||
373 | SND_SOC_DAPM_INPUT("AIN2R"), | ||
374 | SND_SOC_DAPM_INPUT("MICL"), | ||
375 | SND_SOC_DAPM_INPUT("MICR"), | ||
376 | |||
377 | SND_SOC_DAPM_MIXER("Mic Preamp Left", | ||
378 | CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), | ||
379 | SND_SOC_DAPM_MIXER("Mic Preamp Right", | ||
380 | CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), | ||
381 | |||
382 | /* HP */ | ||
383 | SND_SOC_DAPM_OUTPUT("HPL"), | ||
384 | SND_SOC_DAPM_OUTPUT("HPR"), | ||
385 | |||
386 | /* mux */ | ||
387 | SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, | ||
388 | &cs42l51_dac_mux_controls), | ||
389 | SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, | ||
390 | &cs42l51_adcl_mux_controls), | ||
391 | SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, | ||
392 | &cs42l51_adcr_mux_controls), | ||
393 | }; | ||
394 | |||
395 | static const struct snd_soc_dapm_route cs42l51_routes[] = { | ||
396 | {"HPL", NULL, "Left DAC"}, | ||
397 | {"HPR", NULL, "Right DAC"}, | ||
398 | |||
399 | {"Left ADC", NULL, "Left PGA"}, | ||
400 | {"Right ADC", NULL, "Right PGA"}, | ||
401 | |||
402 | {"Mic Preamp Left", NULL, "MICL"}, | ||
403 | {"Mic Preamp Right", NULL, "MICR"}, | ||
404 | |||
405 | {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, | ||
406 | {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, | ||
407 | {"PGA-ADC Mux Left", "MIC Left", "MICL" }, | ||
408 | {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, | ||
409 | {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, | ||
410 | {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, | ||
411 | {"PGA-ADC Mux Right", "MIC Right", "MICR" }, | ||
412 | {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, | ||
413 | |||
414 | {"Left PGA", NULL, "PGA-ADC Mux Left"}, | ||
415 | {"Right PGA", NULL, "PGA-ADC Mux Right"}, | ||
416 | }; | ||
417 | |||
418 | static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, | ||
419 | unsigned int format) | ||
420 | { | ||
421 | struct snd_soc_codec *codec = codec_dai->codec; | ||
422 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | ||
423 | int ret = 0; | ||
424 | |||
425 | switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
426 | case SND_SOC_DAIFMT_I2S: | ||
427 | case SND_SOC_DAIFMT_LEFT_J: | ||
428 | case SND_SOC_DAIFMT_RIGHT_J: | ||
429 | cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; | ||
430 | break; | ||
431 | default: | ||
432 | dev_err(codec->dev, "invalid DAI format\n"); | ||
433 | ret = -EINVAL; | ||
434 | } | ||
435 | |||
436 | switch (format & SND_SOC_DAIFMT_MASTER_MASK) { | ||
437 | case SND_SOC_DAIFMT_CBM_CFM: | ||
438 | cs42l51->func = MODE_MASTER; | ||
439 | break; | ||
440 | case SND_SOC_DAIFMT_CBS_CFS: | ||
441 | cs42l51->func = MODE_SLAVE_AUTO; | ||
442 | break; | ||
443 | default: | ||
444 | ret = -EINVAL; | ||
445 | break; | ||
446 | } | ||
447 | |||
448 | return ret; | ||
449 | } | ||
450 | |||
451 | struct cs42l51_ratios { | ||
452 | unsigned int ratio; | ||
453 | unsigned char speed_mode; | ||
454 | unsigned char mclk; | ||
455 | }; | ||
456 | |||
457 | static struct cs42l51_ratios slave_ratios[] = { | ||
458 | { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, | ||
459 | { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, | ||
460 | { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, | ||
461 | { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, | ||
462 | { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, | ||
463 | { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, | ||
464 | { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, | ||
465 | { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, | ||
466 | { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, | ||
467 | { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, | ||
468 | { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, | ||
469 | }; | ||
470 | |||
471 | static struct cs42l51_ratios slave_auto_ratios[] = { | ||
472 | { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, | ||
473 | { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, | ||
474 | { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, | ||
475 | { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, | ||
476 | { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, | ||
477 | { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, | ||
478 | { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, | ||
479 | { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, | ||
480 | }; | ||
481 | |||
482 | static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, | ||
483 | int clk_id, unsigned int freq, int dir) | ||
484 | { | ||
485 | struct snd_soc_codec *codec = codec_dai->codec; | ||
486 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | ||
487 | struct cs42l51_ratios *ratios = NULL; | ||
488 | int nr_ratios = 0; | ||
489 | unsigned int rates = 0; | ||
490 | unsigned int rate_min = -1; | ||
491 | unsigned int rate_max = 0; | ||
492 | int i; | ||
493 | |||
494 | cs42l51->mclk = freq; | ||
495 | |||
496 | switch (cs42l51->func) { | ||
497 | case MODE_MASTER: | ||
498 | return -EINVAL; | ||
499 | case MODE_SLAVE: | ||
500 | ratios = slave_ratios; | ||
501 | nr_ratios = ARRAY_SIZE(slave_ratios); | ||
502 | break; | ||
503 | case MODE_SLAVE_AUTO: | ||
504 | ratios = slave_auto_ratios; | ||
505 | nr_ratios = ARRAY_SIZE(slave_auto_ratios); | ||
506 | break; | ||
507 | } | ||
508 | |||
509 | for (i = 0; i < nr_ratios; i++) { | ||
510 | unsigned int rate = freq / ratios[i].ratio; | ||
511 | rates |= snd_pcm_rate_to_rate_bit(rate); | ||
512 | if (rate < rate_min) | ||
513 | rate_min = rate; | ||
514 | if (rate > rate_max) | ||
515 | rate_max = rate; | ||
516 | } | ||
517 | rates &= ~SNDRV_PCM_RATE_KNOT; | ||
518 | |||
519 | if (!rates) { | ||
520 | dev_err(codec->dev, "could not find a valid sample rate\n"); | ||
521 | return -EINVAL; | ||
522 | } | ||
523 | |||
524 | codec_dai->playback.rates = rates; | ||
525 | codec_dai->playback.rate_min = rate_min; | ||
526 | codec_dai->playback.rate_max = rate_max; | ||
527 | |||
528 | codec_dai->capture.rates = rates; | ||
529 | codec_dai->capture.rate_min = rate_min; | ||
530 | codec_dai->capture.rate_max = rate_max; | ||
531 | |||
532 | return 0; | ||
533 | } | ||
534 | |||
535 | static int cs42l51_hw_params(struct snd_pcm_substream *substream, | ||
536 | struct snd_pcm_hw_params *params, | ||
537 | struct snd_soc_dai *dai) | ||
538 | { | ||
539 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
540 | struct snd_soc_device *socdev = rtd->socdev; | ||
541 | struct snd_soc_codec *codec = socdev->card->codec; | ||
542 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | ||
543 | int ret; | ||
544 | unsigned int i; | ||
545 | unsigned int rate; | ||
546 | unsigned int ratio; | ||
547 | struct cs42l51_ratios *ratios = NULL; | ||
548 | int nr_ratios = 0; | ||
549 | int intf_ctl, power_ctl, fmt; | ||
550 | |||
551 | switch (cs42l51->func) { | ||
552 | case MODE_MASTER: | ||
553 | return -EINVAL; | ||
554 | case MODE_SLAVE: | ||
555 | ratios = slave_ratios; | ||
556 | nr_ratios = ARRAY_SIZE(slave_ratios); | ||
557 | break; | ||
558 | case MODE_SLAVE_AUTO: | ||
559 | ratios = slave_auto_ratios; | ||
560 | nr_ratios = ARRAY_SIZE(slave_auto_ratios); | ||
561 | break; | ||
562 | } | ||
563 | |||
564 | /* Figure out which MCLK/LRCK ratio to use */ | ||
565 | rate = params_rate(params); /* Sampling rate, in Hz */ | ||
566 | ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ | ||
567 | for (i = 0; i < nr_ratios; i++) { | ||
568 | if (ratios[i].ratio == ratio) | ||
569 | break; | ||
570 | } | ||
571 | |||
572 | if (i == nr_ratios) { | ||
573 | /* We did not find a matching ratio */ | ||
574 | dev_err(codec->dev, "could not find matching ratio\n"); | ||
575 | return -EINVAL; | ||
576 | } | ||
577 | |||
578 | intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL); | ||
579 | power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL); | ||
580 | |||
581 | intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S | ||
582 | | CS42L51_INTF_CTL_DAC_FORMAT(7)); | ||
583 | power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) | ||
584 | | CS42L51_MIC_POWER_CTL_MCLK_DIV2); | ||
585 | |||
586 | switch (cs42l51->func) { | ||
587 | case MODE_MASTER: | ||
588 | intf_ctl |= CS42L51_INTF_CTL_MASTER; | ||
589 | power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); | ||
590 | break; | ||
591 | case MODE_SLAVE: | ||
592 | power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); | ||
593 | break; | ||
594 | case MODE_SLAVE_AUTO: | ||
595 | power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; | ||
596 | break; | ||
597 | } | ||
598 | |||
599 | switch (cs42l51->audio_mode) { | ||
600 | case SND_SOC_DAIFMT_I2S: | ||
601 | intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; | ||
602 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); | ||
603 | break; | ||
604 | case SND_SOC_DAIFMT_LEFT_J: | ||
605 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); | ||
606 | break; | ||
607 | case SND_SOC_DAIFMT_RIGHT_J: | ||
608 | switch (params_format(params)) { | ||
609 | case SNDRV_PCM_FORMAT_S16_LE: | ||
610 | case SNDRV_PCM_FORMAT_S16_BE: | ||
611 | fmt = CS42L51_DAC_DIF_RJ16; | ||
612 | break; | ||
613 | case SNDRV_PCM_FORMAT_S18_3LE: | ||
614 | case SNDRV_PCM_FORMAT_S18_3BE: | ||
615 | fmt = CS42L51_DAC_DIF_RJ18; | ||
616 | break; | ||
617 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
618 | case SNDRV_PCM_FORMAT_S20_3BE: | ||
619 | fmt = CS42L51_DAC_DIF_RJ20; | ||
620 | break; | ||
621 | case SNDRV_PCM_FORMAT_S24_LE: | ||
622 | case SNDRV_PCM_FORMAT_S24_BE: | ||
623 | fmt = CS42L51_DAC_DIF_RJ24; | ||
624 | break; | ||
625 | default: | ||
626 | dev_err(codec->dev, "unknown format\n"); | ||
627 | return -EINVAL; | ||
628 | } | ||
629 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); | ||
630 | break; | ||
631 | default: | ||
632 | dev_err(codec->dev, "unknown format\n"); | ||
633 | return -EINVAL; | ||
634 | } | ||
635 | |||
636 | if (ratios[i].mclk) | ||
637 | power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; | ||
638 | |||
639 | ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl); | ||
640 | if (ret < 0) | ||
641 | return ret; | ||
642 | |||
643 | ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl); | ||
644 | if (ret < 0) | ||
645 | return ret; | ||
646 | |||
647 | return 0; | ||
648 | } | ||
649 | |||
650 | static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) | ||
651 | { | ||
652 | struct snd_soc_codec *codec = dai->codec; | ||
653 | int reg; | ||
654 | int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; | ||
655 | |||
656 | reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL); | ||
657 | |||
658 | if (mute) | ||
659 | reg |= mask; | ||
660 | else | ||
661 | reg &= ~mask; | ||
662 | |||
663 | return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg); | ||
664 | } | ||
665 | |||
666 | static struct snd_soc_dai_ops cs42l51_dai_ops = { | ||
667 | .hw_params = cs42l51_hw_params, | ||
668 | .set_sysclk = cs42l51_set_dai_sysclk, | ||
669 | .set_fmt = cs42l51_set_dai_fmt, | ||
670 | .digital_mute = cs42l51_dai_mute, | ||
671 | }; | ||
672 | |||
673 | struct snd_soc_dai cs42l51_dai = { | ||
674 | .name = "CS42L51 HiFi", | ||
675 | .playback = { | ||
676 | .stream_name = "Playback", | ||
677 | .channels_min = 1, | ||
678 | .channels_max = 2, | ||
679 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
680 | .formats = CS42L51_FORMATS, | ||
681 | }, | ||
682 | .capture = { | ||
683 | .stream_name = "Capture", | ||
684 | .channels_min = 1, | ||
685 | .channels_max = 2, | ||
686 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
687 | .formats = CS42L51_FORMATS, | ||
688 | }, | ||
689 | .ops = &cs42l51_dai_ops, | ||
690 | }; | ||
691 | EXPORT_SYMBOL_GPL(cs42l51_dai); | ||
692 | |||
693 | |||
694 | static int cs42l51_probe(struct platform_device *pdev) | ||
695 | { | ||
696 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
697 | struct snd_soc_codec *codec; | ||
698 | int ret = 0; | ||
699 | |||
700 | if (!cs42l51_codec) { | ||
701 | dev_err(&pdev->dev, "CS42L51 codec not yet registered\n"); | ||
702 | return -EINVAL; | ||
703 | } | ||
704 | |||
705 | socdev->card->codec = cs42l51_codec; | ||
706 | codec = socdev->card->codec; | ||
707 | |||
708 | /* Register PCMs */ | ||
709 | ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); | ||
710 | if (ret < 0) { | ||
711 | dev_err(&pdev->dev, "failed to create PCMs\n"); | ||
712 | return ret; | ||
713 | } | ||
714 | |||
715 | snd_soc_add_controls(codec, cs42l51_snd_controls, | ||
716 | ARRAY_SIZE(cs42l51_snd_controls)); | ||
717 | snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets, | ||
718 | ARRAY_SIZE(cs42l51_dapm_widgets)); | ||
719 | snd_soc_dapm_add_routes(codec, cs42l51_routes, | ||
720 | ARRAY_SIZE(cs42l51_routes)); | ||
721 | |||
722 | return 0; | ||
723 | } | ||
724 | |||
725 | |||
726 | static int cs42l51_remove(struct platform_device *pdev) | ||
727 | { | ||
728 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
729 | |||
730 | snd_soc_free_pcms(socdev); | ||
731 | snd_soc_dapm_free(socdev); | ||
732 | |||
733 | return 0; | ||
734 | } | ||
735 | |||
736 | struct snd_soc_codec_device soc_codec_device_cs42l51 = { | ||
737 | .probe = cs42l51_probe, | ||
738 | .remove = cs42l51_remove | ||
739 | }; | ||
740 | EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51); | ||
741 | |||
742 | static int __init cs42l51_init(void) | ||
743 | { | ||
744 | int ret; | ||
745 | |||
746 | ret = i2c_add_driver(&cs42l51_i2c_driver); | ||
747 | if (ret != 0) { | ||
748 | printk(KERN_ERR "%s: can't add i2c driver\n", __func__); | ||
749 | return ret; | ||
750 | } | ||
751 | return 0; | ||
752 | } | ||
753 | module_init(cs42l51_init); | ||
754 | |||
755 | static void __exit cs42l51_exit(void) | ||
756 | { | ||
757 | i2c_del_driver(&cs42l51_i2c_driver); | ||
758 | } | ||
759 | module_exit(cs42l51_exit); | ||
760 | |||
761 | MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); | ||
762 | MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); | ||
763 | MODULE_LICENSE("GPL"); | ||