Commit | Line | Data |
---|---|---|
b545dd92 MB |
1 | /* |
2 | * Bells audio support | |
3 | * | |
4 | * Copyright 2012 Wolfson Microelectronics | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. | |
10 | */ | |
11 | ||
12 | #include <sound/soc.h> | |
13 | #include <sound/soc-dapm.h> | |
14 | #include <sound/jack.h> | |
15 | #include <linux/gpio.h> | |
16 | #include <linux/module.h> | |
17 | ||
18 | #include "../codecs/wm5102.h" | |
19 | #include "../codecs/wm9081.h" | |
20 | ||
b545dd92 MB |
21 | /* BCLK2 is fixed at this currently */ |
22 | #define BCLK2_RATE (64 * 8000) | |
23 | ||
24 | /* | |
25 | * Expect a 24.576MHz crystal if one is fitted (the driver will function | |
26 | * if this is not fitted). | |
27 | */ | |
28 | #define MCLK_RATE 24576000 | |
29 | ||
344c5ede | 30 | #define SYS_AUDIO_RATE 44100 |
208229ec | 31 | #define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512) |
344c5ede MB |
32 | |
33 | #define DAI_AP_DSP 0 | |
34 | #define DAI_DSP_CODEC 1 | |
35 | #define DAI_CODEC_CP 2 | |
36 | #define DAI_CODEC_SUB 3 | |
b545dd92 | 37 | |
b1387078 MB |
38 | struct bells_drvdata { |
39 | int sysclk_rate; | |
40 | int asyncclk_rate; | |
41 | }; | |
42 | ||
8d47e8a5 MB |
43 | static struct bells_drvdata wm2200_drvdata = { |
44 | .sysclk_rate = 22579200, | |
45 | }; | |
46 | ||
b1387078 MB |
47 | static struct bells_drvdata wm5102_drvdata = { |
48 | .sysclk_rate = 45158400, | |
49 | .asyncclk_rate = 49152000, | |
50 | }; | |
51 | ||
52 | static struct bells_drvdata wm5110_drvdata = { | |
53 | .sysclk_rate = 135475200, | |
54 | .asyncclk_rate = 147456000, | |
55 | }; | |
56 | ||
b545dd92 MB |
57 | static int bells_set_bias_level(struct snd_soc_card *card, |
58 | struct snd_soc_dapm_context *dapm, | |
59 | enum snd_soc_bias_level level) | |
60 | { | |
5015920a ML |
61 | struct snd_soc_pcm_runtime *rtd; |
62 | struct snd_soc_dai *codec_dai; | |
63 | struct snd_soc_codec *codec; | |
b1387078 | 64 | struct bells_drvdata *bells = card->drvdata; |
b545dd92 MB |
65 | int ret; |
66 | ||
5015920a ML |
67 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_DSP_CODEC].name); |
68 | codec_dai = rtd->codec_dai; | |
69 | codec = codec_dai->codec; | |
70 | ||
b545dd92 MB |
71 | if (dapm->dev != codec_dai->dev) |
72 | return 0; | |
73 | ||
74 | switch (level) { | |
75 | case SND_SOC_BIAS_PREPARE: | |
b1387078 MB |
76 | if (dapm->bias_level != SND_SOC_BIAS_STANDBY) |
77 | break; | |
b545dd92 | 78 | |
b1387078 MB |
79 | ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, |
80 | ARIZONA_FLL_SRC_MCLK1, | |
81 | MCLK_RATE, | |
82 | bells->sysclk_rate); | |
83 | if (ret < 0) | |
84 | pr_err("Failed to start FLL: %d\n", ret); | |
85 | ||
86 | if (bells->asyncclk_rate) { | |
b545dd92 MB |
87 | ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, |
88 | ARIZONA_FLL_SRC_AIF2BCLK, | |
89 | BCLK2_RATE, | |
b1387078 | 90 | bells->asyncclk_rate); |
b545dd92 MB |
91 | if (ret < 0) |
92 | pr_err("Failed to start FLL: %d\n", ret); | |
93 | } | |
94 | break; | |
95 | ||
96 | default: | |
97 | break; | |
98 | } | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
103 | static int bells_set_bias_level_post(struct snd_soc_card *card, | |
104 | struct snd_soc_dapm_context *dapm, | |
105 | enum snd_soc_bias_level level) | |
106 | { | |
5015920a ML |
107 | struct snd_soc_pcm_runtime *rtd; |
108 | struct snd_soc_dai *codec_dai; | |
109 | struct snd_soc_codec *codec; | |
b1387078 | 110 | struct bells_drvdata *bells = card->drvdata; |
b545dd92 MB |
111 | int ret; |
112 | ||
5015920a ML |
113 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_DSP_CODEC].name); |
114 | codec_dai = rtd->codec_dai; | |
115 | codec = codec_dai->codec; | |
116 | ||
b545dd92 MB |
117 | if (dapm->dev != codec_dai->dev) |
118 | return 0; | |
119 | ||
120 | switch (level) { | |
121 | case SND_SOC_BIAS_STANDBY: | |
122 | ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 0, 0, 0); | |
123 | if (ret < 0) { | |
124 | pr_err("Failed to stop FLL: %d\n", ret); | |
125 | return ret; | |
126 | } | |
127 | ||
b1387078 MB |
128 | if (bells->asyncclk_rate) { |
129 | ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, | |
130 | 0, 0, 0); | |
131 | if (ret < 0) { | |
132 | pr_err("Failed to stop FLL: %d\n", ret); | |
133 | return ret; | |
134 | } | |
b545dd92 MB |
135 | } |
136 | break; | |
137 | ||
138 | default: | |
139 | break; | |
140 | } | |
141 | ||
142 | dapm->bias_level = level; | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | static int bells_late_probe(struct snd_soc_card *card) | |
148 | { | |
b1387078 | 149 | struct bells_drvdata *bells = card->drvdata; |
5015920a ML |
150 | struct snd_soc_pcm_runtime *rtd; |
151 | struct snd_soc_codec *wm0010; | |
152 | struct snd_soc_codec *codec; | |
153 | struct snd_soc_dai *aif1_dai; | |
b1387078 MB |
154 | struct snd_soc_dai *aif2_dai; |
155 | struct snd_soc_dai *aif3_dai; | |
156 | struct snd_soc_dai *wm9081_dai; | |
b545dd92 MB |
157 | int ret; |
158 | ||
5015920a ML |
159 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_AP_DSP].name); |
160 | wm0010 = rtd->codec; | |
161 | ||
162 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_DSP_CODEC].name); | |
163 | codec = rtd->codec; | |
164 | aif1_dai = rtd->codec_dai; | |
165 | ||
b1387078 MB |
166 | ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK, |
167 | ARIZONA_CLK_SRC_FLL1, | |
168 | bells->sysclk_rate, | |
169 | SND_SOC_CLOCK_IN); | |
170 | if (ret != 0) { | |
171 | dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret); | |
172 | return ret; | |
173 | } | |
174 | ||
344c5ede MB |
175 | ret = snd_soc_codec_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0); |
176 | if (ret != 0) { | |
177 | dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret); | |
178 | return ret; | |
179 | } | |
180 | ||
b545dd92 | 181 | ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0); |
b1387078 | 182 | if (ret != 0) |
b545dd92 | 183 | dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); |
b545dd92 | 184 | |
b1387078 MB |
185 | ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_OPCLK, 0, |
186 | SYS_MCLK_RATE, SND_SOC_CLOCK_OUT); | |
187 | if (ret != 0) | |
188 | dev_err(codec->dev, "Failed to set OPCLK: %d\n", ret); | |
b545dd92 | 189 | |
b1387078 MB |
190 | if (card->num_rtd == DAI_CODEC_CP) |
191 | return 0; | |
b545dd92 | 192 | |
b1387078 MB |
193 | ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK, |
194 | ARIZONA_CLK_SRC_FLL2, | |
195 | bells->asyncclk_rate, | |
b545dd92 MB |
196 | SND_SOC_CLOCK_IN); |
197 | if (ret != 0) { | |
b1387078 | 198 | dev_err(codec->dev, "Failed to set ASYNCCLK: %d\n", ret); |
b545dd92 MB |
199 | return ret; |
200 | } | |
201 | ||
5015920a ML |
202 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_CODEC_CP].name); |
203 | aif2_dai = rtd->cpu_dai; | |
b1387078 MB |
204 | |
205 | ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0); | |
b545dd92 | 206 | if (ret != 0) { |
b1387078 | 207 | dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret); |
b545dd92 MB |
208 | return ret; |
209 | } | |
210 | ||
b1387078 MB |
211 | if (card->num_rtd == DAI_CODEC_SUB) |
212 | return 0; | |
213 | ||
5015920a ML |
214 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_CODEC_SUB].name); |
215 | aif3_dai = rtd->cpu_dai; | |
216 | wm9081_dai = rtd->codec_dai; | |
b1387078 MB |
217 | |
218 | ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0); | |
b545dd92 | 219 | if (ret != 0) { |
b1387078 | 220 | dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); |
b545dd92 MB |
221 | return ret; |
222 | } | |
223 | ||
224 | ret = snd_soc_codec_set_sysclk(wm9081_dai->codec, WM9081_SYSCLK_MCLK, | |
344c5ede | 225 | 0, SYS_MCLK_RATE, 0); |
b545dd92 MB |
226 | if (ret != 0) { |
227 | dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret); | |
228 | return ret; | |
229 | } | |
230 | ||
231 | return 0; | |
232 | } | |
233 | ||
234 | static const struct snd_soc_pcm_stream baseband_params = { | |
235 | .formats = SNDRV_PCM_FMTBIT_S32_LE, | |
236 | .rate_min = 8000, | |
237 | .rate_max = 8000, | |
238 | .channels_min = 2, | |
239 | .channels_max = 2, | |
240 | }; | |
241 | ||
242 | static const struct snd_soc_pcm_stream sub_params = { | |
243 | .formats = SNDRV_PCM_FMTBIT_S32_LE, | |
344c5ede MB |
244 | .rate_min = SYS_AUDIO_RATE, |
245 | .rate_max = SYS_AUDIO_RATE, | |
b545dd92 MB |
246 | .channels_min = 2, |
247 | .channels_max = 2, | |
248 | }; | |
249 | ||
8d47e8a5 MB |
250 | static struct snd_soc_dai_link bells_dai_wm2200[] = { |
251 | { | |
252 | .name = "CPU-DSP", | |
253 | .stream_name = "CPU-DSP", | |
254 | .cpu_dai_name = "samsung-i2s.0", | |
255 | .codec_dai_name = "wm0010-sdi1", | |
a08485d8 | 256 | .platform_name = "samsung-i2s.0", |
8d47e8a5 MB |
257 | .codec_name = "spi0.0", |
258 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
259 | | SND_SOC_DAIFMT_CBM_CFM, | |
260 | }, | |
261 | { | |
262 | .name = "DSP-CODEC", | |
263 | .stream_name = "DSP-CODEC", | |
264 | .cpu_dai_name = "wm0010-sdi2", | |
265 | .codec_dai_name = "wm2200", | |
266 | .codec_name = "wm2200.1-003a", | |
267 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
268 | | SND_SOC_DAIFMT_CBM_CFM, | |
269 | .params = &sub_params, | |
270 | .ignore_suspend = 1, | |
271 | }, | |
272 | }; | |
273 | ||
b545dd92 MB |
274 | static struct snd_soc_dai_link bells_dai_wm5102[] = { |
275 | { | |
344c5ede MB |
276 | .name = "CPU-DSP", |
277 | .stream_name = "CPU-DSP", | |
b545dd92 | 278 | .cpu_dai_name = "samsung-i2s.0", |
344c5ede | 279 | .codec_dai_name = "wm0010-sdi1", |
a08485d8 | 280 | .platform_name = "samsung-i2s.0", |
344c5ede MB |
281 | .codec_name = "spi0.0", |
282 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
283 | | SND_SOC_DAIFMT_CBM_CFM, | |
284 | }, | |
285 | { | |
286 | .name = "DSP-CODEC", | |
287 | .stream_name = "DSP-CODEC", | |
288 | .cpu_dai_name = "wm0010-sdi2", | |
289 | .codec_dai_name = "wm5102-aif1", | |
b545dd92 MB |
290 | .codec_name = "wm5102-codec", |
291 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
292 | | SND_SOC_DAIFMT_CBM_CFM, | |
344c5ede MB |
293 | .params = &sub_params, |
294 | .ignore_suspend = 1, | |
b545dd92 MB |
295 | }, |
296 | { | |
297 | .name = "Baseband", | |
298 | .stream_name = "Baseband", | |
299 | .cpu_dai_name = "wm5102-aif2", | |
300 | .codec_dai_name = "wm1250-ev1", | |
301 | .codec_name = "wm1250-ev1.1-0027", | |
302 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
303 | | SND_SOC_DAIFMT_CBM_CFM, | |
304 | .ignore_suspend = 1, | |
305 | .params = &baseband_params, | |
306 | }, | |
307 | { | |
308 | .name = "Sub", | |
309 | .stream_name = "Sub", | |
310 | .cpu_dai_name = "wm5102-aif3", | |
311 | .codec_dai_name = "wm9081-hifi", | |
312 | .codec_name = "wm9081.1-006c", | |
313 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
314 | | SND_SOC_DAIFMT_CBS_CFS, | |
315 | .ignore_suspend = 1, | |
316 | .params = &sub_params, | |
317 | }, | |
318 | }; | |
319 | ||
320 | static struct snd_soc_dai_link bells_dai_wm5110[] = { | |
321 | { | |
344c5ede MB |
322 | .name = "CPU-DSP", |
323 | .stream_name = "CPU-DSP", | |
b545dd92 | 324 | .cpu_dai_name = "samsung-i2s.0", |
344c5ede | 325 | .codec_dai_name = "wm0010-sdi1", |
a08485d8 | 326 | .platform_name = "samsung-i2s.0", |
344c5ede MB |
327 | .codec_name = "spi0.0", |
328 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
329 | | SND_SOC_DAIFMT_CBM_CFM, | |
330 | }, | |
331 | { | |
332 | .name = "DSP-CODEC", | |
333 | .stream_name = "DSP-CODEC", | |
334 | .cpu_dai_name = "wm0010-sdi2", | |
335 | .codec_dai_name = "wm5110-aif1", | |
b545dd92 MB |
336 | .codec_name = "wm5110-codec", |
337 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
338 | | SND_SOC_DAIFMT_CBM_CFM, | |
344c5ede MB |
339 | .params = &sub_params, |
340 | .ignore_suspend = 1, | |
b545dd92 MB |
341 | }, |
342 | { | |
343 | .name = "Baseband", | |
344 | .stream_name = "Baseband", | |
345 | .cpu_dai_name = "wm5110-aif2", | |
346 | .codec_dai_name = "wm1250-ev1", | |
347 | .codec_name = "wm1250-ev1.1-0027", | |
348 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
349 | | SND_SOC_DAIFMT_CBM_CFM, | |
350 | .ignore_suspend = 1, | |
351 | .params = &baseband_params, | |
352 | }, | |
353 | { | |
354 | .name = "Sub", | |
355 | .stream_name = "Sub", | |
ffaa839b | 356 | .cpu_dai_name = "wm5110-aif3", |
b545dd92 MB |
357 | .codec_dai_name = "wm9081-hifi", |
358 | .codec_name = "wm9081.1-006c", | |
359 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
360 | | SND_SOC_DAIFMT_CBS_CFS, | |
361 | .ignore_suspend = 1, | |
362 | .params = &sub_params, | |
363 | }, | |
364 | }; | |
365 | ||
366 | static struct snd_soc_codec_conf bells_codec_conf[] = { | |
367 | { | |
368 | .dev_name = "wm9081.1-006c", | |
369 | .name_prefix = "Sub", | |
370 | }, | |
371 | }; | |
372 | ||
939d3c6a CK |
373 | static struct snd_soc_dapm_widget bells_widgets[] = { |
374 | SND_SOC_DAPM_MIC("DMIC", NULL), | |
375 | }; | |
376 | ||
b545dd92 MB |
377 | static struct snd_soc_dapm_route bells_routes[] = { |
378 | { "Sub CLK_SYS", NULL, "OPCLK" }, | |
b3a6006e | 379 | { "CLKIN", NULL, "OPCLK" }, |
939d3c6a CK |
380 | |
381 | { "DMIC", NULL, "MICBIAS2" }, | |
382 | { "IN2L", NULL, "DMIC" }, | |
383 | { "IN2R", NULL, "DMIC" }, | |
b545dd92 MB |
384 | }; |
385 | ||
386 | static struct snd_soc_card bells_cards[] = { | |
8d47e8a5 MB |
387 | { |
388 | .name = "Bells WM2200", | |
389 | .owner = THIS_MODULE, | |
390 | .dai_link = bells_dai_wm2200, | |
391 | .num_links = ARRAY_SIZE(bells_dai_wm2200), | |
392 | .codec_conf = bells_codec_conf, | |
393 | .num_configs = ARRAY_SIZE(bells_codec_conf), | |
394 | ||
395 | .late_probe = bells_late_probe, | |
396 | ||
939d3c6a CK |
397 | .dapm_widgets = bells_widgets, |
398 | .num_dapm_widgets = ARRAY_SIZE(bells_widgets), | |
8d47e8a5 MB |
399 | .dapm_routes = bells_routes, |
400 | .num_dapm_routes = ARRAY_SIZE(bells_routes), | |
401 | ||
402 | .set_bias_level = bells_set_bias_level, | |
403 | .set_bias_level_post = bells_set_bias_level_post, | |
404 | ||
405 | .drvdata = &wm2200_drvdata, | |
406 | }, | |
b545dd92 MB |
407 | { |
408 | .name = "Bells WM5102", | |
409 | .owner = THIS_MODULE, | |
410 | .dai_link = bells_dai_wm5102, | |
411 | .num_links = ARRAY_SIZE(bells_dai_wm5102), | |
412 | .codec_conf = bells_codec_conf, | |
413 | .num_configs = ARRAY_SIZE(bells_codec_conf), | |
414 | ||
415 | .late_probe = bells_late_probe, | |
416 | ||
939d3c6a CK |
417 | .dapm_widgets = bells_widgets, |
418 | .num_dapm_widgets = ARRAY_SIZE(bells_widgets), | |
b545dd92 MB |
419 | .dapm_routes = bells_routes, |
420 | .num_dapm_routes = ARRAY_SIZE(bells_routes), | |
421 | ||
422 | .set_bias_level = bells_set_bias_level, | |
423 | .set_bias_level_post = bells_set_bias_level_post, | |
b1387078 MB |
424 | |
425 | .drvdata = &wm5102_drvdata, | |
b545dd92 MB |
426 | }, |
427 | { | |
428 | .name = "Bells WM5110", | |
429 | .owner = THIS_MODULE, | |
430 | .dai_link = bells_dai_wm5110, | |
431 | .num_links = ARRAY_SIZE(bells_dai_wm5110), | |
432 | .codec_conf = bells_codec_conf, | |
433 | .num_configs = ARRAY_SIZE(bells_codec_conf), | |
434 | ||
435 | .late_probe = bells_late_probe, | |
436 | ||
939d3c6a CK |
437 | .dapm_widgets = bells_widgets, |
438 | .num_dapm_widgets = ARRAY_SIZE(bells_widgets), | |
b545dd92 MB |
439 | .dapm_routes = bells_routes, |
440 | .num_dapm_routes = ARRAY_SIZE(bells_routes), | |
441 | ||
442 | .set_bias_level = bells_set_bias_level, | |
443 | .set_bias_level_post = bells_set_bias_level_post, | |
b1387078 MB |
444 | |
445 | .drvdata = &wm5110_drvdata, | |
b545dd92 MB |
446 | }, |
447 | }; | |
448 | ||
449 | ||
fdca21ad | 450 | static int bells_probe(struct platform_device *pdev) |
b545dd92 MB |
451 | { |
452 | int ret; | |
453 | ||
454 | bells_cards[pdev->id].dev = &pdev->dev; | |
455 | ||
c583883e TB |
456 | ret = devm_snd_soc_register_card(&pdev->dev, &bells_cards[pdev->id]); |
457 | if (ret) | |
b545dd92 MB |
458 | dev_err(&pdev->dev, |
459 | "snd_soc_register_card(%s) failed: %d\n", | |
460 | bells_cards[pdev->id].name, ret); | |
b545dd92 | 461 | |
c583883e | 462 | return ret; |
b545dd92 MB |
463 | } |
464 | ||
465 | static struct platform_driver bells_driver = { | |
466 | .driver = { | |
467 | .name = "bells", | |
b545dd92 MB |
468 | .pm = &snd_soc_pm_ops, |
469 | }, | |
470 | .probe = bells_probe, | |
b545dd92 MB |
471 | }; |
472 | ||
473 | module_platform_driver(bells_driver); | |
474 | ||
475 | MODULE_DESCRIPTION("Bells audio support"); | |
476 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | |
477 | MODULE_LICENSE("GPL"); | |
478 | MODULE_ALIAS("platform:bells"); |