1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Littlemill audio support |
4 | // |
5 | // Copyright 2011 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/wm8994.h" |
14 | |
15 | static int sample_rate = 44100; |
16 | |
17 | static int littlemill_set_bias_level(struct snd_soc_card *card, |
18 | struct snd_soc_dapm_context *dapm, |
19 | enum snd_soc_bias_level level) |
20 | { |
21 | struct snd_soc_pcm_runtime *rtd; |
22 | struct snd_soc_dai *aif1_dai; |
23 | int ret; |
24 | |
25 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[0]); |
26 | aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
27 | |
28 | if (dapm->dev != aif1_dai->dev) |
29 | return 0; |
30 | |
31 | switch (level) { |
32 | case SND_SOC_BIAS_PREPARE: |
33 | /* |
34 | * If we've not already clocked things via hw_params() |
35 | * then do so now, otherwise these are noops. |
36 | */ |
37 | if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { |
38 | ret = snd_soc_dai_set_pll(dai: aif1_dai, WM8994_FLL1, |
39 | WM8994_FLL_SRC_MCLK2, freq_in: 32768, |
40 | freq_out: sample_rate * 512); |
41 | if (ret < 0) { |
42 | pr_err("Failed to start FLL: %d\n" , ret); |
43 | return ret; |
44 | } |
45 | |
46 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, |
47 | WM8994_SYSCLK_FLL1, |
48 | freq: sample_rate * 512, |
49 | SND_SOC_CLOCK_IN); |
50 | if (ret < 0) { |
51 | pr_err("Failed to set SYSCLK: %d\n" , ret); |
52 | return ret; |
53 | } |
54 | } |
55 | break; |
56 | |
57 | default: |
58 | break; |
59 | } |
60 | |
61 | return 0; |
62 | } |
63 | |
64 | static int littlemill_set_bias_level_post(struct snd_soc_card *card, |
65 | struct snd_soc_dapm_context *dapm, |
66 | enum snd_soc_bias_level level) |
67 | { |
68 | struct snd_soc_pcm_runtime *rtd; |
69 | struct snd_soc_dai *aif1_dai; |
70 | int ret; |
71 | |
72 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[0]); |
73 | aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
74 | |
75 | if (dapm->dev != aif1_dai->dev) |
76 | return 0; |
77 | |
78 | switch (level) { |
79 | case SND_SOC_BIAS_STANDBY: |
80 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, WM8994_SYSCLK_MCLK2, |
81 | freq: 32768, SND_SOC_CLOCK_IN); |
82 | if (ret < 0) { |
83 | pr_err("Failed to switch away from FLL1: %d\n" , ret); |
84 | return ret; |
85 | } |
86 | |
87 | ret = snd_soc_dai_set_pll(dai: aif1_dai, WM8994_FLL1, |
88 | source: 0, freq_in: 0, freq_out: 0); |
89 | if (ret < 0) { |
90 | pr_err("Failed to stop FLL1: %d\n" , ret); |
91 | return ret; |
92 | } |
93 | break; |
94 | |
95 | default: |
96 | break; |
97 | } |
98 | |
99 | dapm->bias_level = level; |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | static int littlemill_hw_params(struct snd_pcm_substream *substream, |
105 | struct snd_pcm_hw_params *params) |
106 | { |
107 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
108 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
109 | int ret; |
110 | |
111 | sample_rate = params_rate(p: params); |
112 | |
113 | ret = snd_soc_dai_set_pll(dai: codec_dai, WM8994_FLL1, |
114 | WM8994_FLL_SRC_MCLK2, freq_in: 32768, |
115 | freq_out: sample_rate * 512); |
116 | if (ret < 0) { |
117 | pr_err("Failed to start FLL: %d\n" , ret); |
118 | return ret; |
119 | } |
120 | |
121 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, |
122 | WM8994_SYSCLK_FLL1, |
123 | freq: sample_rate * 512, |
124 | SND_SOC_CLOCK_IN); |
125 | if (ret < 0) { |
126 | pr_err("Failed to set SYSCLK: %d\n" , ret); |
127 | return ret; |
128 | } |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static const struct snd_soc_ops littlemill_ops = { |
134 | .hw_params = littlemill_hw_params, |
135 | }; |
136 | |
137 | static const struct snd_soc_pcm_stream baseband_params = { |
138 | .formats = SNDRV_PCM_FMTBIT_S32_LE, |
139 | .rate_min = 8000, |
140 | .rate_max = 8000, |
141 | .channels_min = 2, |
142 | .channels_max = 2, |
143 | }; |
144 | |
145 | SND_SOC_DAILINK_DEFS(cpu, |
146 | DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0" )), |
147 | DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec" , "wm8994-aif1" )), |
148 | DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0" ))); |
149 | |
150 | SND_SOC_DAILINK_DEFS(baseband, |
151 | DAILINK_COMP_ARRAY(COMP_CPU("wm8994-aif2" )), |
152 | DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027" , |
153 | "wm1250-ev1" ))); |
154 | |
155 | static struct snd_soc_dai_link littlemill_dai[] = { |
156 | { |
157 | .name = "CPU" , |
158 | .stream_name = "CPU" , |
159 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
160 | | SND_SOC_DAIFMT_CBM_CFM, |
161 | .ops = &littlemill_ops, |
162 | SND_SOC_DAILINK_REG(cpu), |
163 | }, |
164 | { |
165 | .name = "Baseband" , |
166 | .stream_name = "Baseband" , |
167 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
168 | | SND_SOC_DAIFMT_CBM_CFM, |
169 | .ignore_suspend = 1, |
170 | .c2c_params = &baseband_params, |
171 | .num_c2c_params = 1, |
172 | SND_SOC_DAILINK_REG(baseband), |
173 | }, |
174 | }; |
175 | |
176 | static int bbclk_ev(struct snd_soc_dapm_widget *w, |
177 | struct snd_kcontrol *kcontrol, int event) |
178 | { |
179 | struct snd_soc_card *card = w->dapm->card; |
180 | struct snd_soc_pcm_runtime *rtd; |
181 | struct snd_soc_dai *aif2_dai; |
182 | int ret; |
183 | |
184 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[1]); |
185 | aif2_dai = snd_soc_rtd_to_cpu(rtd, 0); |
186 | |
187 | switch (event) { |
188 | case SND_SOC_DAPM_PRE_PMU: |
189 | ret = snd_soc_dai_set_pll(dai: aif2_dai, WM8994_FLL2, |
190 | WM8994_FLL_SRC_BCLK, freq_in: 64 * 8000, |
191 | freq_out: 8000 * 256); |
192 | if (ret < 0) { |
193 | pr_err("Failed to start FLL: %d\n" , ret); |
194 | return ret; |
195 | } |
196 | |
197 | ret = snd_soc_dai_set_sysclk(dai: aif2_dai, WM8994_SYSCLK_FLL2, |
198 | freq: 8000 * 256, |
199 | SND_SOC_CLOCK_IN); |
200 | if (ret < 0) { |
201 | pr_err("Failed to set SYSCLK: %d\n" , ret); |
202 | return ret; |
203 | } |
204 | break; |
205 | case SND_SOC_DAPM_POST_PMD: |
206 | ret = snd_soc_dai_set_sysclk(dai: aif2_dai, WM8994_SYSCLK_MCLK2, |
207 | freq: 32768, SND_SOC_CLOCK_IN); |
208 | if (ret < 0) { |
209 | pr_err("Failed to switch away from FLL2: %d\n" , ret); |
210 | return ret; |
211 | } |
212 | |
213 | ret = snd_soc_dai_set_pll(dai: aif2_dai, WM8994_FLL2, |
214 | source: 0, freq_in: 0, freq_out: 0); |
215 | if (ret < 0) { |
216 | pr_err("Failed to stop FLL2: %d\n" , ret); |
217 | return ret; |
218 | } |
219 | break; |
220 | default: |
221 | return -EINVAL; |
222 | } |
223 | |
224 | return 0; |
225 | } |
226 | |
227 | static const struct snd_kcontrol_new controls[] = { |
228 | SOC_DAPM_PIN_SWITCH("Headphone" ), |
229 | SOC_DAPM_PIN_SWITCH("Headset Mic" ), |
230 | SOC_DAPM_PIN_SWITCH("WM1250 Input" ), |
231 | SOC_DAPM_PIN_SWITCH("WM1250 Output" ), |
232 | }; |
233 | |
234 | static const struct snd_soc_dapm_widget widgets[] = { |
235 | SND_SOC_DAPM_HP("Headphone" , NULL), |
236 | SND_SOC_DAPM_HP("Headset Mic" , NULL), |
237 | |
238 | SND_SOC_DAPM_MIC("AMIC" , NULL), |
239 | SND_SOC_DAPM_MIC("DMIC" , NULL), |
240 | |
241 | SND_SOC_DAPM_SUPPLY_S("Baseband Clock" , -1, SND_SOC_NOPM, 0, 0, |
242 | bbclk_ev, |
243 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), |
244 | }; |
245 | |
246 | static const struct snd_soc_dapm_route audio_paths[] = { |
247 | { "Headphone" , NULL, "HPOUT1L" }, |
248 | { "Headphone" , NULL, "HPOUT1R" }, |
249 | |
250 | { "AMIC" , NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */ |
251 | { "IN1LN" , NULL, "AMIC" }, |
252 | |
253 | { "DMIC" , NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */ |
254 | { "DMIC1DAT" , NULL, "DMIC" }, |
255 | { "DMIC2DAT" , NULL, "DMIC" }, |
256 | |
257 | { "AIF2CLK" , NULL, "Baseband Clock" }, |
258 | }; |
259 | |
260 | static struct snd_soc_jack littlemill_headset; |
261 | static struct snd_soc_jack_pin littlemill_headset_pins[] = { |
262 | { |
263 | .pin = "Headphone" , |
264 | .mask = SND_JACK_HEADPHONE, |
265 | }, |
266 | { |
267 | .pin = "Headset Mic" , |
268 | .mask = SND_JACK_MICROPHONE, |
269 | }, |
270 | }; |
271 | |
272 | static int littlemill_late_probe(struct snd_soc_card *card) |
273 | { |
274 | struct snd_soc_pcm_runtime *rtd; |
275 | struct snd_soc_component *component; |
276 | struct snd_soc_dai *aif1_dai; |
277 | struct snd_soc_dai *aif2_dai; |
278 | int ret; |
279 | |
280 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[0]); |
281 | component = snd_soc_rtd_to_codec(rtd, 0)->component; |
282 | aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
283 | |
284 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[1]); |
285 | aif2_dai = snd_soc_rtd_to_cpu(rtd, 0); |
286 | |
287 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, WM8994_SYSCLK_MCLK2, |
288 | freq: 32768, SND_SOC_CLOCK_IN); |
289 | if (ret < 0) |
290 | return ret; |
291 | |
292 | ret = snd_soc_dai_set_sysclk(dai: aif2_dai, WM8994_SYSCLK_MCLK2, |
293 | freq: 32768, SND_SOC_CLOCK_IN); |
294 | if (ret < 0) |
295 | return ret; |
296 | |
297 | ret = snd_soc_card_jack_new_pins(card, id: "Headset" , |
298 | type: SND_JACK_HEADSET | SND_JACK_MECHANICAL | |
299 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | |
300 | SND_JACK_BTN_2 | SND_JACK_BTN_3 | |
301 | SND_JACK_BTN_4 | SND_JACK_BTN_5, |
302 | jack: &littlemill_headset, |
303 | pins: littlemill_headset_pins, |
304 | ARRAY_SIZE(littlemill_headset_pins)); |
305 | if (ret) |
306 | return ret; |
307 | |
308 | /* This will check device compatibility itself */ |
309 | wm8958_mic_detect(component, jack: &littlemill_headset, NULL, NULL, NULL, NULL); |
310 | |
311 | /* As will this */ |
312 | wm8994_mic_detect(component, jack: &littlemill_headset, micbias: 1); |
313 | |
314 | return 0; |
315 | } |
316 | |
317 | static struct snd_soc_card littlemill = { |
318 | .name = "Littlemill" , |
319 | .owner = THIS_MODULE, |
320 | .dai_link = littlemill_dai, |
321 | .num_links = ARRAY_SIZE(littlemill_dai), |
322 | |
323 | .set_bias_level = littlemill_set_bias_level, |
324 | .set_bias_level_post = littlemill_set_bias_level_post, |
325 | |
326 | .controls = controls, |
327 | .num_controls = ARRAY_SIZE(controls), |
328 | .dapm_widgets = widgets, |
329 | .num_dapm_widgets = ARRAY_SIZE(widgets), |
330 | .dapm_routes = audio_paths, |
331 | .num_dapm_routes = ARRAY_SIZE(audio_paths), |
332 | |
333 | .late_probe = littlemill_late_probe, |
334 | }; |
335 | |
336 | static int littlemill_probe(struct platform_device *pdev) |
337 | { |
338 | struct snd_soc_card *card = &littlemill; |
339 | int ret; |
340 | |
341 | card->dev = &pdev->dev; |
342 | |
343 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
344 | if (ret) |
345 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "snd_soc_register_card() failed\n" ); |
346 | |
347 | return ret; |
348 | } |
349 | |
350 | static struct platform_driver littlemill_driver = { |
351 | .driver = { |
352 | .name = "littlemill" , |
353 | .pm = &snd_soc_pm_ops, |
354 | }, |
355 | .probe = littlemill_probe, |
356 | }; |
357 | |
358 | module_platform_driver(littlemill_driver); |
359 | |
360 | MODULE_DESCRIPTION("Littlemill audio support" ); |
361 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>" ); |
362 | MODULE_LICENSE("GPL" ); |
363 | MODULE_ALIAS("platform:littlemill" ); |
364 | |