]>
Commit | Line | Data |
---|---|---|
72ed5a8c | 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> | |
72ed5a8c | 25 | #include <linux/slab.h> |
26 | #include <sound/core.h> | |
27 | #include <sound/soc.h> | |
72ed5a8c | 28 | #include <sound/tlv.h> |
29 | #include <sound/initval.h> | |
30 | #include <sound/pcm_params.h> | |
31 | #include <sound/pcm.h> | |
da071489 | 32 | #include <linux/regmap.h> |
72ed5a8c | 33 | |
34 | #include "cs42l51.h" | |
35 | ||
36 | enum master_slave_mode { | |
37 | MODE_SLAVE, | |
38 | MODE_SLAVE_AUTO, | |
39 | MODE_MASTER, | |
40 | }; | |
41 | ||
42 | struct cs42l51_private { | |
43 | unsigned int mclk; | |
44 | unsigned int audio_mode; /* The mode (I2S or left-justified) */ | |
45 | enum master_slave_mode func; | |
72ed5a8c | 46 | }; |
47 | ||
72ed5a8c | 48 | #define CS42L51_FORMATS ( \ |
49 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ | |
50 | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ | |
51 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ | |
52 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) | |
53 | ||
72ed5a8c | 54 | static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, |
55 | struct snd_ctl_elem_value *ucontrol) | |
56 | { | |
ea53bf77 | 57 | struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); |
72ed5a8c | 58 | unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; |
59 | ||
60 | switch (value) { | |
61 | default: | |
62 | case 0: | |
89300b4e | 63 | ucontrol->value.enumerated.item[0] = 0; |
72ed5a8c | 64 | break; |
65 | /* same value : (L+R)/2 and (R+L)/2 */ | |
66 | case 1: | |
67 | case 2: | |
89300b4e | 68 | ucontrol->value.enumerated.item[0] = 1; |
72ed5a8c | 69 | break; |
70 | case 3: | |
89300b4e | 71 | ucontrol->value.enumerated.item[0] = 2; |
72ed5a8c | 72 | break; |
73 | } | |
74 | ||
75 | return 0; | |
76 | } | |
77 | ||
78 | #define CHAN_MIX_NORMAL 0x00 | |
79 | #define CHAN_MIX_BOTH 0x55 | |
80 | #define CHAN_MIX_SWAP 0xFF | |
81 | ||
82 | static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, | |
83 | struct snd_ctl_elem_value *ucontrol) | |
84 | { | |
ea53bf77 | 85 | struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); |
72ed5a8c | 86 | unsigned char val; |
87 | ||
89300b4e | 88 | switch (ucontrol->value.enumerated.item[0]) { |
72ed5a8c | 89 | default: |
90 | case 0: | |
91 | val = CHAN_MIX_NORMAL; | |
92 | break; | |
93 | case 1: | |
94 | val = CHAN_MIX_BOTH; | |
95 | break; | |
96 | case 2: | |
97 | val = CHAN_MIX_SWAP; | |
98 | break; | |
99 | } | |
100 | ||
101 | snd_soc_write(codec, CS42L51_PCM_MIXER, val); | |
102 | ||
103 | return 1; | |
104 | } | |
105 | ||
106 | static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); | |
107 | static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); | |
7272e051 BA |
108 | |
109 | static const DECLARE_TLV_DB_SCALE(aout_tlv, -10200, 50, 0); | |
72ed5a8c | 110 | |
111 | static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); | |
112 | static const char *chan_mix[] = { | |
113 | "L R", | |
114 | "L+R", | |
115 | "R L", | |
116 | }; | |
117 | ||
6109ab2b | 118 | static SOC_ENUM_SINGLE_EXT_DECL(cs42l51_chan_mix, chan_mix); |
72ed5a8c | 119 | |
120 | static const struct snd_kcontrol_new cs42l51_snd_controls[] = { | |
121 | SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", | |
122 | CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, | |
7272e051 | 123 | 0, 0x19, 0x7F, adc_pcm_tlv), |
72ed5a8c | 124 | SOC_DOUBLE_R("PCM Playback Switch", |
125 | CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), | |
126 | SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", | |
127 | CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, | |
1d99f243 | 128 | 0, 0x34, 0xE4, aout_tlv), |
72ed5a8c | 129 | SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", |
130 | CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, | |
7272e051 | 131 | 0, 0x19, 0x7F, adc_pcm_tlv), |
72ed5a8c | 132 | SOC_DOUBLE_R("ADC Mixer Switch", |
133 | CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), | |
134 | SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), | |
135 | SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), | |
136 | SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), | |
137 | SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), | |
138 | SOC_DOUBLE_TLV("Mic Boost Volume", | |
139 | CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), | |
140 | SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), | |
141 | SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), | |
142 | SOC_ENUM_EXT("PCM channel mixer", | |
143 | cs42l51_chan_mix, | |
144 | cs42l51_get_chan_mix, cs42l51_set_chan_mix), | |
145 | }; | |
146 | ||
147 | /* | |
148 | * to power down, one must: | |
149 | * 1.) Enable the PDN bit | |
150 | * 2.) enable power-down for the select channels | |
151 | * 3.) disable the PDN bit. | |
152 | */ | |
153 | static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, | |
154 | struct snd_kcontrol *kcontrol, int event) | |
155 | { | |
ceb3c068 LPC |
156 | struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); |
157 | ||
72ed5a8c | 158 | switch (event) { |
159 | case SND_SOC_DAPM_PRE_PMD: | |
ceb3c068 | 160 | snd_soc_update_bits(codec, CS42L51_POWER_CTL1, |
e94de1e8 AL |
161 | CS42L51_POWER_CTL1_PDN, |
162 | CS42L51_POWER_CTL1_PDN); | |
72ed5a8c | 163 | break; |
164 | default: | |
165 | case SND_SOC_DAPM_POST_PMD: | |
ceb3c068 | 166 | snd_soc_update_bits(codec, CS42L51_POWER_CTL1, |
e94de1e8 | 167 | CS42L51_POWER_CTL1_PDN, 0); |
72ed5a8c | 168 | break; |
169 | } | |
72ed5a8c | 170 | |
171 | return 0; | |
172 | } | |
173 | ||
174 | static const char *cs42l51_dac_names[] = {"Direct PCM", | |
175 | "DSP PCM", "ADC"}; | |
6109ab2b TI |
176 | static SOC_ENUM_SINGLE_DECL(cs42l51_dac_mux_enum, |
177 | CS42L51_DAC_CTL, 6, cs42l51_dac_names); | |
72ed5a8c | 178 | static const struct snd_kcontrol_new cs42l51_dac_mux_controls = |
179 | SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); | |
180 | ||
181 | static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", | |
182 | "MIC Left", "MIC+preamp Left"}; | |
6109ab2b TI |
183 | static SOC_ENUM_SINGLE_DECL(cs42l51_adcl_mux_enum, |
184 | CS42L51_ADC_INPUT, 4, cs42l51_adcl_names); | |
72ed5a8c | 185 | static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = |
186 | SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); | |
187 | ||
188 | static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", | |
189 | "MIC Right", "MIC+preamp Right"}; | |
6109ab2b TI |
190 | static SOC_ENUM_SINGLE_DECL(cs42l51_adcr_mux_enum, |
191 | CS42L51_ADC_INPUT, 6, cs42l51_adcr_names); | |
72ed5a8c | 192 | static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = |
193 | SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); | |
194 | ||
195 | static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { | |
196 | SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), | |
197 | SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, | |
198 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | |
199 | SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, | |
200 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | |
201 | SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", | |
202 | CS42L51_POWER_CTL1, 1, 1, | |
203 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | |
204 | SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", | |
205 | CS42L51_POWER_CTL1, 2, 1, | |
206 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | |
207 | SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", | |
208 | CS42L51_POWER_CTL1, 5, 1, | |
209 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | |
210 | SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", | |
211 | CS42L51_POWER_CTL1, 6, 1, | |
212 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | |
213 | ||
214 | /* analog/mic */ | |
215 | SND_SOC_DAPM_INPUT("AIN1L"), | |
216 | SND_SOC_DAPM_INPUT("AIN1R"), | |
217 | SND_SOC_DAPM_INPUT("AIN2L"), | |
218 | SND_SOC_DAPM_INPUT("AIN2R"), | |
219 | SND_SOC_DAPM_INPUT("MICL"), | |
220 | SND_SOC_DAPM_INPUT("MICR"), | |
221 | ||
222 | SND_SOC_DAPM_MIXER("Mic Preamp Left", | |
223 | CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), | |
224 | SND_SOC_DAPM_MIXER("Mic Preamp Right", | |
225 | CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), | |
226 | ||
227 | /* HP */ | |
228 | SND_SOC_DAPM_OUTPUT("HPL"), | |
229 | SND_SOC_DAPM_OUTPUT("HPR"), | |
230 | ||
231 | /* mux */ | |
232 | SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, | |
233 | &cs42l51_dac_mux_controls), | |
234 | SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, | |
235 | &cs42l51_adcl_mux_controls), | |
236 | SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, | |
237 | &cs42l51_adcr_mux_controls), | |
238 | }; | |
239 | ||
240 | static const struct snd_soc_dapm_route cs42l51_routes[] = { | |
241 | {"HPL", NULL, "Left DAC"}, | |
242 | {"HPR", NULL, "Right DAC"}, | |
243 | ||
244 | {"Left ADC", NULL, "Left PGA"}, | |
245 | {"Right ADC", NULL, "Right PGA"}, | |
246 | ||
247 | {"Mic Preamp Left", NULL, "MICL"}, | |
248 | {"Mic Preamp Right", NULL, "MICR"}, | |
249 | ||
250 | {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, | |
251 | {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, | |
252 | {"PGA-ADC Mux Left", "MIC Left", "MICL" }, | |
253 | {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, | |
254 | {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, | |
255 | {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, | |
256 | {"PGA-ADC Mux Right", "MIC Right", "MICR" }, | |
257 | {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, | |
258 | ||
259 | {"Left PGA", NULL, "PGA-ADC Mux Left"}, | |
260 | {"Right PGA", NULL, "PGA-ADC Mux Right"}, | |
261 | }; | |
262 | ||
263 | static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, | |
264 | unsigned int format) | |
265 | { | |
266 | struct snd_soc_codec *codec = codec_dai->codec; | |
267 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | |
72ed5a8c | 268 | |
269 | switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { | |
270 | case SND_SOC_DAIFMT_I2S: | |
271 | case SND_SOC_DAIFMT_LEFT_J: | |
272 | case SND_SOC_DAIFMT_RIGHT_J: | |
273 | cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; | |
274 | break; | |
275 | default: | |
276 | dev_err(codec->dev, "invalid DAI format\n"); | |
ac60155f | 277 | return -EINVAL; |
72ed5a8c | 278 | } |
279 | ||
280 | switch (format & SND_SOC_DAIFMT_MASTER_MASK) { | |
281 | case SND_SOC_DAIFMT_CBM_CFM: | |
282 | cs42l51->func = MODE_MASTER; | |
283 | break; | |
284 | case SND_SOC_DAIFMT_CBS_CFS: | |
285 | cs42l51->func = MODE_SLAVE_AUTO; | |
286 | break; | |
287 | default: | |
ac60155f AL |
288 | dev_err(codec->dev, "Unknown master/slave configuration\n"); |
289 | return -EINVAL; | |
72ed5a8c | 290 | } |
291 | ||
ac60155f | 292 | return 0; |
72ed5a8c | 293 | } |
294 | ||
295 | struct cs42l51_ratios { | |
296 | unsigned int ratio; | |
297 | unsigned char speed_mode; | |
298 | unsigned char mclk; | |
299 | }; | |
300 | ||
301 | static struct cs42l51_ratios slave_ratios[] = { | |
302 | { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, | |
303 | { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, | |
304 | { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, | |
305 | { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, | |
306 | { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, | |
307 | { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, | |
308 | { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, | |
309 | { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, | |
310 | { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, | |
311 | { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, | |
312 | { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, | |
313 | }; | |
314 | ||
315 | static struct cs42l51_ratios slave_auto_ratios[] = { | |
316 | { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, | |
317 | { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, | |
318 | { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, | |
319 | { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, | |
320 | { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, | |
321 | { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, | |
322 | { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, | |
323 | { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, | |
324 | }; | |
325 | ||
326 | static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, | |
327 | int clk_id, unsigned int freq, int dir) | |
328 | { | |
329 | struct snd_soc_codec *codec = codec_dai->codec; | |
330 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | |
72ed5a8c | 331 | |
332 | cs42l51->mclk = freq; | |
72ed5a8c | 333 | return 0; |
334 | } | |
335 | ||
336 | static int cs42l51_hw_params(struct snd_pcm_substream *substream, | |
337 | struct snd_pcm_hw_params *params, | |
338 | struct snd_soc_dai *dai) | |
339 | { | |
e6968a17 | 340 | struct snd_soc_codec *codec = dai->codec; |
72ed5a8c | 341 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); |
342 | int ret; | |
343 | unsigned int i; | |
344 | unsigned int rate; | |
345 | unsigned int ratio; | |
346 | struct cs42l51_ratios *ratios = NULL; | |
347 | int nr_ratios = 0; | |
348 | int intf_ctl, power_ctl, fmt; | |
349 | ||
350 | switch (cs42l51->func) { | |
351 | case MODE_MASTER: | |
352 | return -EINVAL; | |
353 | case MODE_SLAVE: | |
354 | ratios = slave_ratios; | |
355 | nr_ratios = ARRAY_SIZE(slave_ratios); | |
356 | break; | |
357 | case MODE_SLAVE_AUTO: | |
358 | ratios = slave_auto_ratios; | |
359 | nr_ratios = ARRAY_SIZE(slave_auto_ratios); | |
360 | break; | |
361 | } | |
362 | ||
363 | /* Figure out which MCLK/LRCK ratio to use */ | |
364 | rate = params_rate(params); /* Sampling rate, in Hz */ | |
365 | ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ | |
366 | for (i = 0; i < nr_ratios; i++) { | |
367 | if (ratios[i].ratio == ratio) | |
368 | break; | |
369 | } | |
370 | ||
371 | if (i == nr_ratios) { | |
372 | /* We did not find a matching ratio */ | |
373 | dev_err(codec->dev, "could not find matching ratio\n"); | |
374 | return -EINVAL; | |
375 | } | |
376 | ||
377 | intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL); | |
378 | power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL); | |
379 | ||
380 | intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S | |
381 | | CS42L51_INTF_CTL_DAC_FORMAT(7)); | |
382 | power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) | |
383 | | CS42L51_MIC_POWER_CTL_MCLK_DIV2); | |
384 | ||
385 | switch (cs42l51->func) { | |
386 | case MODE_MASTER: | |
387 | intf_ctl |= CS42L51_INTF_CTL_MASTER; | |
388 | power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); | |
389 | break; | |
390 | case MODE_SLAVE: | |
391 | power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); | |
392 | break; | |
393 | case MODE_SLAVE_AUTO: | |
394 | power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; | |
395 | break; | |
396 | } | |
397 | ||
398 | switch (cs42l51->audio_mode) { | |
399 | case SND_SOC_DAIFMT_I2S: | |
400 | intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; | |
401 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); | |
402 | break; | |
403 | case SND_SOC_DAIFMT_LEFT_J: | |
404 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); | |
405 | break; | |
406 | case SND_SOC_DAIFMT_RIGHT_J: | |
1b6b0dfa MB |
407 | switch (params_width(params)) { |
408 | case 16: | |
72ed5a8c | 409 | fmt = CS42L51_DAC_DIF_RJ16; |
410 | break; | |
1b6b0dfa | 411 | case 18: |
72ed5a8c | 412 | fmt = CS42L51_DAC_DIF_RJ18; |
413 | break; | |
1b6b0dfa | 414 | case 20: |
72ed5a8c | 415 | fmt = CS42L51_DAC_DIF_RJ20; |
416 | break; | |
1b6b0dfa | 417 | case 24: |
72ed5a8c | 418 | fmt = CS42L51_DAC_DIF_RJ24; |
419 | break; | |
420 | default: | |
421 | dev_err(codec->dev, "unknown format\n"); | |
422 | return -EINVAL; | |
423 | } | |
424 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); | |
425 | break; | |
426 | default: | |
427 | dev_err(codec->dev, "unknown format\n"); | |
428 | return -EINVAL; | |
429 | } | |
430 | ||
431 | if (ratios[i].mclk) | |
432 | power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; | |
433 | ||
434 | ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl); | |
435 | if (ret < 0) | |
436 | return ret; | |
437 | ||
438 | ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl); | |
439 | if (ret < 0) | |
440 | return ret; | |
441 | ||
442 | return 0; | |
443 | } | |
444 | ||
445 | static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) | |
446 | { | |
447 | struct snd_soc_codec *codec = dai->codec; | |
448 | int reg; | |
449 | int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; | |
450 | ||
451 | reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL); | |
452 | ||
453 | if (mute) | |
454 | reg |= mask; | |
455 | else | |
456 | reg &= ~mask; | |
457 | ||
458 | return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg); | |
459 | } | |
460 | ||
85e7652d | 461 | static const struct snd_soc_dai_ops cs42l51_dai_ops = { |
72ed5a8c | 462 | .hw_params = cs42l51_hw_params, |
463 | .set_sysclk = cs42l51_set_dai_sysclk, | |
464 | .set_fmt = cs42l51_set_dai_fmt, | |
465 | .digital_mute = cs42l51_dai_mute, | |
466 | }; | |
467 | ||
f0fba2ad LG |
468 | static struct snd_soc_dai_driver cs42l51_dai = { |
469 | .name = "cs42l51-hifi", | |
72ed5a8c | 470 | .playback = { |
471 | .stream_name = "Playback", | |
472 | .channels_min = 1, | |
473 | .channels_max = 2, | |
474 | .rates = SNDRV_PCM_RATE_8000_96000, | |
475 | .formats = CS42L51_FORMATS, | |
476 | }, | |
477 | .capture = { | |
478 | .stream_name = "Capture", | |
479 | .channels_min = 1, | |
480 | .channels_max = 2, | |
481 | .rates = SNDRV_PCM_RATE_8000_96000, | |
482 | .formats = CS42L51_FORMATS, | |
483 | }, | |
484 | .ops = &cs42l51_dai_ops, | |
485 | }; | |
72ed5a8c | 486 | |
a1253ef6 | 487 | static int cs42l51_codec_probe(struct snd_soc_codec *codec) |
72ed5a8c | 488 | { |
f0fba2ad | 489 | int ret, reg; |
72ed5a8c | 490 | |
f0fba2ad LG |
491 | /* |
492 | * DAC configuration | |
493 | * - Use signal processor | |
494 | * - auto mute | |
495 | * - vol changes immediate | |
496 | * - no de-emphasize | |
497 | */ | |
498 | reg = CS42L51_DAC_CTL_DATA_SEL(1) | |
499 | | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); | |
500 | ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); | |
501 | if (ret < 0) | |
502 | return ret; | |
503 | ||
72ed5a8c | 504 | return 0; |
505 | } | |
506 | ||
f0fba2ad | 507 | static struct snd_soc_codec_driver soc_codec_device_cs42l51 = { |
a1253ef6 | 508 | .probe = cs42l51_codec_probe, |
3f7cec04 | 509 | |
9353d25d KM |
510 | .component_driver = { |
511 | .controls = cs42l51_snd_controls, | |
512 | .num_controls = ARRAY_SIZE(cs42l51_snd_controls), | |
513 | .dapm_widgets = cs42l51_dapm_widgets, | |
514 | .num_dapm_widgets = ARRAY_SIZE(cs42l51_dapm_widgets), | |
515 | .dapm_routes = cs42l51_routes, | |
516 | .num_dapm_routes = ARRAY_SIZE(cs42l51_routes), | |
517 | }, | |
f0fba2ad | 518 | }; |
72ed5a8c | 519 | |
a1253ef6 | 520 | const struct regmap_config cs42l51_regmap = { |
da071489 MB |
521 | .max_register = CS42L51_CHARGE_FREQ, |
522 | .cache_type = REGCACHE_RBTREE, | |
523 | }; | |
a1253ef6 | 524 | EXPORT_SYMBOL_GPL(cs42l51_regmap); |
da071489 | 525 | |
a1253ef6 | 526 | int cs42l51_probe(struct device *dev, struct regmap *regmap) |
72ed5a8c | 527 | { |
f0fba2ad | 528 | struct cs42l51_private *cs42l51; |
da071489 | 529 | unsigned int val; |
f0fba2ad | 530 | int ret; |
72ed5a8c | 531 | |
a1253ef6 BA |
532 | if (IS_ERR(regmap)) |
533 | return PTR_ERR(regmap); | |
534 | ||
535 | cs42l51 = devm_kzalloc(dev, sizeof(struct cs42l51_private), | |
536 | GFP_KERNEL); | |
537 | if (!cs42l51) | |
538 | return -ENOMEM; | |
539 | ||
540 | dev_set_drvdata(dev, cs42l51); | |
da071489 | 541 | |
f0fba2ad | 542 | /* Verify that we have a CS42L51 */ |
da071489 | 543 | ret = regmap_read(regmap, CS42L51_CHIP_REV_ID, &val); |
f0fba2ad | 544 | if (ret < 0) { |
a1253ef6 | 545 | dev_err(dev, "failed to read I2C\n"); |
f0fba2ad LG |
546 | goto error; |
547 | } | |
548 | ||
da071489 MB |
549 | if ((val != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && |
550 | (val != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { | |
a1253ef6 | 551 | dev_err(dev, "Invalid chip id: %x\n", val); |
f0fba2ad LG |
552 | ret = -ENODEV; |
553 | goto error; | |
554 | } | |
1025c05f AL |
555 | dev_info(dev, "Cirrus Logic CS42L51, Revision: %02X\n", |
556 | val & CS42L51_CHIP_REV_MASK); | |
f0fba2ad | 557 | |
a1253ef6 | 558 | ret = snd_soc_register_codec(dev, |
f0fba2ad | 559 | &soc_codec_device_cs42l51, &cs42l51_dai, 1); |
f0fba2ad LG |
560 | error: |
561 | return ret; | |
562 | } | |
a1253ef6 | 563 | EXPORT_SYMBOL_GPL(cs42l51_probe); |
f0fba2ad | 564 | |
2cb1e025 | 565 | const struct of_device_id cs42l51_of_match[] = { |
dfd72a68 TP |
566 | { .compatible = "cirrus,cs42l51", }, |
567 | { } | |
568 | }; | |
569 | MODULE_DEVICE_TABLE(of, cs42l51_of_match); | |
2cb1e025 TP |
570 | EXPORT_SYMBOL_GPL(cs42l51_of_match); |
571 | ||
69737897 | 572 | MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); |
72ed5a8c | 573 | MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); |
574 | MODULE_LICENSE("GPL"); |