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