1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Speyside 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/wm8996.h" |
14 | #include "../codecs/wm9081.h" |
15 | |
16 | #define WM8996_HPSEL_GPIO 214 |
17 | #define MCLK_AUDIO_RATE (512 * 48000) |
18 | |
19 | static int speyside_set_bias_level(struct snd_soc_card *card, |
20 | struct snd_soc_dapm_context *dapm, |
21 | enum snd_soc_bias_level level) |
22 | { |
23 | struct snd_soc_pcm_runtime *rtd; |
24 | struct snd_soc_dai *codec_dai; |
25 | int ret; |
26 | |
27 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[1]); |
28 | codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
29 | |
30 | if (dapm->dev != codec_dai->dev) |
31 | return 0; |
32 | |
33 | switch (level) { |
34 | case SND_SOC_BIAS_STANDBY: |
35 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8996_SYSCLK_MCLK2, |
36 | freq: 32768, SND_SOC_CLOCK_IN); |
37 | if (ret < 0) |
38 | return ret; |
39 | |
40 | ret = snd_soc_dai_set_pll(dai: codec_dai, WM8996_FLL_MCLK2, |
41 | source: 0, freq_in: 0, freq_out: 0); |
42 | if (ret < 0) { |
43 | pr_err("Failed to stop FLL\n" ); |
44 | return ret; |
45 | } |
46 | break; |
47 | |
48 | default: |
49 | break; |
50 | } |
51 | |
52 | return 0; |
53 | } |
54 | |
55 | static int speyside_set_bias_level_post(struct snd_soc_card *card, |
56 | struct snd_soc_dapm_context *dapm, |
57 | enum snd_soc_bias_level level) |
58 | { |
59 | struct snd_soc_pcm_runtime *rtd; |
60 | struct snd_soc_dai *codec_dai; |
61 | int ret; |
62 | |
63 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[1]); |
64 | codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
65 | |
66 | if (dapm->dev != codec_dai->dev) |
67 | return 0; |
68 | |
69 | switch (level) { |
70 | case SND_SOC_BIAS_PREPARE: |
71 | if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) { |
72 | ret = snd_soc_dai_set_pll(dai: codec_dai, pll_id: 0, |
73 | WM8996_FLL_MCLK2, |
74 | freq_in: 32768, MCLK_AUDIO_RATE); |
75 | if (ret < 0) { |
76 | pr_err("Failed to start FLL\n" ); |
77 | return ret; |
78 | } |
79 | |
80 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, |
81 | WM8996_SYSCLK_FLL, |
82 | MCLK_AUDIO_RATE, |
83 | SND_SOC_CLOCK_IN); |
84 | if (ret < 0) |
85 | return ret; |
86 | } |
87 | break; |
88 | |
89 | default: |
90 | break; |
91 | } |
92 | |
93 | card->dapm.bias_level = level; |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static struct snd_soc_jack speyside_headset; |
99 | |
100 | /* Headset jack detection DAPM pins */ |
101 | static struct snd_soc_jack_pin speyside_headset_pins[] = { |
102 | { |
103 | .pin = "Headset Mic" , |
104 | .mask = SND_JACK_MICROPHONE, |
105 | }, |
106 | }; |
107 | |
108 | /* Default the headphone selection to active high */ |
109 | static int speyside_jack_polarity; |
110 | |
111 | static int speyside_get_micbias(struct snd_soc_dapm_widget *source, |
112 | struct snd_soc_dapm_widget *sink) |
113 | { |
114 | if (speyside_jack_polarity && (snd_soc_dapm_widget_name_cmp(widget: source, s: "MICB1" ) == 0)) |
115 | return 1; |
116 | if (!speyside_jack_polarity && (snd_soc_dapm_widget_name_cmp(widget: source, s: "MICB2" ) == 0)) |
117 | return 1; |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static void speyside_set_polarity(struct snd_soc_component *component, |
123 | int polarity) |
124 | { |
125 | speyside_jack_polarity = !polarity; |
126 | gpio_direction_output(WM8996_HPSEL_GPIO, value: speyside_jack_polarity); |
127 | |
128 | /* Re-run DAPM to make sure we're using the correct mic bias */ |
129 | snd_soc_dapm_sync(dapm: snd_soc_component_get_dapm(component)); |
130 | } |
131 | |
132 | static int speyside_wm0010_init(struct snd_soc_pcm_runtime *rtd) |
133 | { |
134 | struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0); |
135 | int ret; |
136 | |
137 | ret = snd_soc_dai_set_sysclk(dai, clk_id: 0, MCLK_AUDIO_RATE, dir: 0); |
138 | if (ret < 0) |
139 | return ret; |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | static int speyside_wm8996_init(struct snd_soc_pcm_runtime *rtd) |
145 | { |
146 | struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0); |
147 | struct snd_soc_component *component = dai->component; |
148 | int ret; |
149 | |
150 | ret = snd_soc_dai_set_sysclk(dai, WM8996_SYSCLK_MCLK2, freq: 32768, dir: 0); |
151 | if (ret < 0) |
152 | return ret; |
153 | |
154 | ret = gpio_request(WM8996_HPSEL_GPIO, label: "HP_SEL" ); |
155 | if (ret != 0) |
156 | pr_err("Failed to request HP_SEL GPIO: %d\n" , ret); |
157 | gpio_direction_output(WM8996_HPSEL_GPIO, value: speyside_jack_polarity); |
158 | |
159 | ret = snd_soc_card_jack_new_pins(card: rtd->card, id: "Headset" , |
160 | type: SND_JACK_LINEOUT | SND_JACK_HEADSET | |
161 | SND_JACK_BTN_0, |
162 | jack: &speyside_headset, |
163 | pins: speyside_headset_pins, |
164 | ARRAY_SIZE(speyside_headset_pins)); |
165 | if (ret) |
166 | return ret; |
167 | |
168 | wm8996_detect(component, jack: &speyside_headset, polarity_cb: speyside_set_polarity); |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int speyside_late_probe(struct snd_soc_card *card) |
174 | { |
175 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "Headphone" ); |
176 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "Headset Mic" ); |
177 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "Main AMIC" ); |
178 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "Main DMIC" ); |
179 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "Main Speaker" ); |
180 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "WM1250 Output" ); |
181 | snd_soc_dapm_ignore_suspend(dapm: &card->dapm, pin: "WM1250 Input" ); |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | static const struct snd_soc_pcm_stream dsp_codec_params = { |
187 | .formats = SNDRV_PCM_FMTBIT_S32_LE, |
188 | .rate_min = 48000, |
189 | .rate_max = 48000, |
190 | .channels_min = 2, |
191 | .channels_max = 2, |
192 | }; |
193 | |
194 | SND_SOC_DAILINK_DEFS(cpu_dsp, |
195 | DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0" )), |
196 | DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0" , "wm0010-sdi1" )), |
197 | DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0" ))); |
198 | |
199 | SND_SOC_DAILINK_DEFS(dsp_codec, |
200 | DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2" )), |
201 | DAILINK_COMP_ARRAY(COMP_CODEC("wm8996.1-001a" , "wm8996-aif1" ))); |
202 | |
203 | SND_SOC_DAILINK_DEFS(baseband, |
204 | DAILINK_COMP_ARRAY(COMP_CPU("wm8996-aif2" )), |
205 | DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027" , "wm1250-ev1" ))); |
206 | |
207 | static struct snd_soc_dai_link speyside_dai[] = { |
208 | { |
209 | .name = "CPU-DSP" , |
210 | .stream_name = "CPU-DSP" , |
211 | .init = speyside_wm0010_init, |
212 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
213 | | SND_SOC_DAIFMT_CBM_CFM, |
214 | SND_SOC_DAILINK_REG(cpu_dsp), |
215 | }, |
216 | { |
217 | .name = "DSP-CODEC" , |
218 | .stream_name = "DSP-CODEC" , |
219 | .init = speyside_wm8996_init, |
220 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
221 | | SND_SOC_DAIFMT_CBM_CFM, |
222 | .c2c_params = &dsp_codec_params, |
223 | .num_c2c_params = 1, |
224 | .ignore_suspend = 1, |
225 | SND_SOC_DAILINK_REG(dsp_codec), |
226 | }, |
227 | { |
228 | .name = "Baseband" , |
229 | .stream_name = "Baseband" , |
230 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
231 | | SND_SOC_DAIFMT_CBM_CFM, |
232 | .ignore_suspend = 1, |
233 | SND_SOC_DAILINK_REG(baseband), |
234 | }, |
235 | }; |
236 | |
237 | static int speyside_wm9081_init(struct snd_soc_component *component) |
238 | { |
239 | /* At any time the WM9081 is active it will have this clock */ |
240 | return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, source: 0, |
241 | MCLK_AUDIO_RATE, dir: 0); |
242 | } |
243 | |
244 | static struct snd_soc_aux_dev speyside_aux_dev[] = { |
245 | { |
246 | .dlc = COMP_AUX("wm9081.1-006c" ), |
247 | .init = speyside_wm9081_init, |
248 | }, |
249 | }; |
250 | |
251 | static struct snd_soc_codec_conf speyside_codec_conf[] = { |
252 | { |
253 | .dlc = COMP_CODEC_CONF("wm9081.1-006c" ), |
254 | .name_prefix = "Sub" , |
255 | }, |
256 | }; |
257 | |
258 | static const struct snd_kcontrol_new controls[] = { |
259 | SOC_DAPM_PIN_SWITCH("Main Speaker" ), |
260 | SOC_DAPM_PIN_SWITCH("Main DMIC" ), |
261 | SOC_DAPM_PIN_SWITCH("Main AMIC" ), |
262 | SOC_DAPM_PIN_SWITCH("WM1250 Input" ), |
263 | SOC_DAPM_PIN_SWITCH("WM1250 Output" ), |
264 | SOC_DAPM_PIN_SWITCH("Headphone" ), |
265 | }; |
266 | |
267 | static const struct snd_soc_dapm_widget widgets[] = { |
268 | SND_SOC_DAPM_HP("Headphone" , NULL), |
269 | SND_SOC_DAPM_MIC("Headset Mic" , NULL), |
270 | |
271 | SND_SOC_DAPM_SPK("Main Speaker" , NULL), |
272 | |
273 | SND_SOC_DAPM_MIC("Main AMIC" , NULL), |
274 | SND_SOC_DAPM_MIC("Main DMIC" , NULL), |
275 | }; |
276 | |
277 | static const struct snd_soc_dapm_route audio_paths[] = { |
278 | { "IN1RN" , NULL, "MICB1" }, |
279 | { "IN1RP" , NULL, "MICB1" }, |
280 | { "IN1RN" , NULL, "MICB2" }, |
281 | { "IN1RP" , NULL, "MICB2" }, |
282 | { "MICB1" , NULL, "Headset Mic" , speyside_get_micbias }, |
283 | { "MICB2" , NULL, "Headset Mic" , speyside_get_micbias }, |
284 | |
285 | { "IN1LP" , NULL, "MICB2" }, |
286 | { "IN1RN" , NULL, "MICB1" }, |
287 | { "MICB2" , NULL, "Main AMIC" }, |
288 | |
289 | { "DMIC1DAT" , NULL, "MICB1" }, |
290 | { "DMIC2DAT" , NULL, "MICB1" }, |
291 | { "MICB1" , NULL, "Main DMIC" }, |
292 | |
293 | { "Headphone" , NULL, "HPOUT1L" }, |
294 | { "Headphone" , NULL, "HPOUT1R" }, |
295 | |
296 | { "Sub IN1" , NULL, "HPOUT2L" }, |
297 | { "Sub IN2" , NULL, "HPOUT2R" }, |
298 | |
299 | { "Main Speaker" , NULL, "Sub SPKN" }, |
300 | { "Main Speaker" , NULL, "Sub SPKP" }, |
301 | { "Main Speaker" , NULL, "SPKDAT" }, |
302 | }; |
303 | |
304 | static struct snd_soc_card speyside = { |
305 | .name = "Speyside" , |
306 | .owner = THIS_MODULE, |
307 | .dai_link = speyside_dai, |
308 | .num_links = ARRAY_SIZE(speyside_dai), |
309 | .aux_dev = speyside_aux_dev, |
310 | .num_aux_devs = ARRAY_SIZE(speyside_aux_dev), |
311 | .codec_conf = speyside_codec_conf, |
312 | .num_configs = ARRAY_SIZE(speyside_codec_conf), |
313 | |
314 | .set_bias_level = speyside_set_bias_level, |
315 | .set_bias_level_post = speyside_set_bias_level_post, |
316 | |
317 | .controls = controls, |
318 | .num_controls = ARRAY_SIZE(controls), |
319 | .dapm_widgets = widgets, |
320 | .num_dapm_widgets = ARRAY_SIZE(widgets), |
321 | .dapm_routes = audio_paths, |
322 | .num_dapm_routes = ARRAY_SIZE(audio_paths), |
323 | .fully_routed = true, |
324 | |
325 | .late_probe = speyside_late_probe, |
326 | }; |
327 | |
328 | static int speyside_probe(struct platform_device *pdev) |
329 | { |
330 | struct snd_soc_card *card = &speyside; |
331 | int ret; |
332 | |
333 | card->dev = &pdev->dev; |
334 | |
335 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
336 | if (ret) |
337 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "snd_soc_register_card() failed\n" ); |
338 | |
339 | return ret; |
340 | } |
341 | |
342 | static struct platform_driver speyside_driver = { |
343 | .driver = { |
344 | .name = "speyside" , |
345 | .pm = &snd_soc_pm_ops, |
346 | }, |
347 | .probe = speyside_probe, |
348 | }; |
349 | |
350 | module_platform_driver(speyside_driver); |
351 | |
352 | MODULE_DESCRIPTION("Speyside audio support" ); |
353 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>" ); |
354 | MODULE_LICENSE("GPL" ); |
355 | MODULE_ALIAS("platform:speyside" ); |
356 | |