1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | #include <linux/extcon.h> |
3 | #include <linux/iio/consumer.h> |
4 | #include <linux/input-event-codes.h> |
5 | #include <linux/mfd/wm8994/registers.h> |
6 | #include <linux/module.h> |
7 | #include <linux/of.h> |
8 | #include <linux/of_gpio.h> |
9 | #include <linux/regulator/consumer.h> |
10 | #include <sound/jack.h> |
11 | #include <sound/pcm_params.h> |
12 | #include <sound/soc.h> |
13 | |
14 | #include "i2s.h" |
15 | #include "../codecs/wm8994.h" |
16 | |
17 | #define ARIES_MCLK1_FREQ 24000000 |
18 | |
19 | struct aries_wm8994_variant { |
20 | unsigned int modem_dai_fmt; |
21 | bool has_fm_radio; |
22 | }; |
23 | |
24 | struct aries_wm8994_data { |
25 | struct extcon_dev *usb_extcon; |
26 | struct regulator *reg_main_micbias; |
27 | struct regulator *reg_headset_micbias; |
28 | struct gpio_desc *gpio_headset_detect; |
29 | struct gpio_desc *gpio_headset_key; |
30 | struct gpio_desc *gpio_earpath_sel; |
31 | struct iio_channel *adc; |
32 | const struct aries_wm8994_variant *variant; |
33 | }; |
34 | |
35 | /* USB dock */ |
36 | static struct snd_soc_jack aries_dock; |
37 | |
38 | static struct snd_soc_jack_pin dock_pins[] = { |
39 | { |
40 | .pin = "LINE" , |
41 | .mask = SND_JACK_LINEOUT, |
42 | }, |
43 | }; |
44 | |
45 | static int aries_extcon_notifier(struct notifier_block *this, |
46 | unsigned long connected, void *_cmd) |
47 | { |
48 | if (connected) |
49 | snd_soc_jack_report(jack: &aries_dock, status: SND_JACK_LINEOUT, |
50 | mask: SND_JACK_LINEOUT); |
51 | else |
52 | snd_soc_jack_report(jack: &aries_dock, status: 0, mask: SND_JACK_LINEOUT); |
53 | |
54 | return NOTIFY_DONE; |
55 | } |
56 | |
57 | static struct notifier_block aries_extcon_notifier_block = { |
58 | .notifier_call = aries_extcon_notifier, |
59 | }; |
60 | |
61 | /* Headset jack */ |
62 | static struct snd_soc_jack aries_headset; |
63 | |
64 | static struct snd_soc_jack_pin jack_pins[] = { |
65 | { |
66 | .pin = "HP" , |
67 | .mask = SND_JACK_HEADPHONE, |
68 | }, { |
69 | .pin = "Headset Mic" , |
70 | .mask = SND_JACK_MICROPHONE, |
71 | }, |
72 | }; |
73 | |
74 | static struct snd_soc_jack_zone headset_zones[] = { |
75 | { |
76 | .min_mv = 0, |
77 | .max_mv = 241, |
78 | .jack_type = SND_JACK_HEADPHONE, |
79 | }, { |
80 | .min_mv = 242, |
81 | .max_mv = 2980, |
82 | .jack_type = SND_JACK_HEADSET, |
83 | }, { |
84 | .min_mv = 2981, |
85 | .max_mv = UINT_MAX, |
86 | .jack_type = SND_JACK_HEADPHONE, |
87 | }, |
88 | }; |
89 | |
90 | static irqreturn_t headset_det_irq_thread(int irq, void *data) |
91 | { |
92 | struct aries_wm8994_data *priv = (struct aries_wm8994_data *) data; |
93 | int ret = 0; |
94 | int time_left_ms = 300; |
95 | int adc; |
96 | |
97 | while (time_left_ms > 0) { |
98 | if (!gpiod_get_value(desc: priv->gpio_headset_detect)) { |
99 | snd_soc_jack_report(jack: &aries_headset, status: 0, |
100 | mask: SND_JACK_HEADSET); |
101 | gpiod_set_value(desc: priv->gpio_earpath_sel, value: 0); |
102 | return IRQ_HANDLED; |
103 | } |
104 | msleep(msecs: 20); |
105 | time_left_ms -= 20; |
106 | } |
107 | |
108 | /* Temporarily enable micbias and earpath selector */ |
109 | ret = regulator_enable(regulator: priv->reg_headset_micbias); |
110 | if (ret) |
111 | pr_err("%s failed to enable micbias: %d" , __func__, ret); |
112 | |
113 | gpiod_set_value(desc: priv->gpio_earpath_sel, value: 1); |
114 | |
115 | ret = iio_read_channel_processed(chan: priv->adc, val: &adc); |
116 | if (ret < 0) { |
117 | /* failed to read ADC, so assume headphone */ |
118 | pr_err("%s failed to read ADC, assuming headphones" , __func__); |
119 | snd_soc_jack_report(jack: &aries_headset, status: SND_JACK_HEADPHONE, |
120 | mask: SND_JACK_HEADSET); |
121 | } else { |
122 | snd_soc_jack_report(jack: &aries_headset, |
123 | status: snd_soc_jack_get_type(jack: &aries_headset, micbias_voltage: adc), |
124 | mask: SND_JACK_HEADSET); |
125 | } |
126 | |
127 | ret = regulator_disable(regulator: priv->reg_headset_micbias); |
128 | if (ret) |
129 | pr_err("%s failed disable micbias: %d" , __func__, ret); |
130 | |
131 | /* Disable earpath selector when no mic connected */ |
132 | if (!(aries_headset.status & SND_JACK_MICROPHONE)) |
133 | gpiod_set_value(desc: priv->gpio_earpath_sel, value: 0); |
134 | |
135 | return IRQ_HANDLED; |
136 | } |
137 | |
138 | static int headset_button_check(void *data) |
139 | { |
140 | struct aries_wm8994_data *priv = (struct aries_wm8994_data *) data; |
141 | |
142 | /* Filter out keypresses when 4 pole jack not detected */ |
143 | if (gpiod_get_value_cansleep(desc: priv->gpio_headset_key) && |
144 | aries_headset.status & SND_JACK_MICROPHONE) |
145 | return SND_JACK_BTN_0; |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static struct snd_soc_jack_gpio headset_button_gpio[] = { |
151 | { |
152 | .name = "Media Button" , |
153 | .report = SND_JACK_BTN_0, |
154 | .debounce_time = 30, |
155 | .jack_status_check = headset_button_check, |
156 | }, |
157 | }; |
158 | |
159 | static int aries_spk_cfg(struct snd_soc_dapm_widget *w, |
160 | struct snd_kcontrol *kcontrol, int event) |
161 | { |
162 | struct snd_soc_card *card = w->dapm->card; |
163 | struct snd_soc_pcm_runtime *rtd; |
164 | struct snd_soc_component *component; |
165 | int ret = 0; |
166 | |
167 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[0]); |
168 | component = snd_soc_rtd_to_codec(rtd, 0)->component; |
169 | |
170 | /** |
171 | * We have an odd setup - the SPKMODE pin is pulled up so |
172 | * we only have access to the left side SPK configs, |
173 | * but SPKOUTR isn't bridged so when playing back in |
174 | * stereo, we only get the left hand channel. The only |
175 | * option we're left with is to force the AIF into mono |
176 | * mode. |
177 | */ |
178 | switch (event) { |
179 | case SND_SOC_DAPM_POST_PMU: |
180 | ret = snd_soc_component_update_bits(component, |
181 | WM8994_AIF1_DAC1_FILTERS_1, |
182 | WM8994_AIF1DAC1_MONO, WM8994_AIF1DAC1_MONO); |
183 | break; |
184 | case SND_SOC_DAPM_PRE_PMD: |
185 | ret = snd_soc_component_update_bits(component, |
186 | WM8994_AIF1_DAC1_FILTERS_1, |
187 | WM8994_AIF1DAC1_MONO, val: 0); |
188 | break; |
189 | } |
190 | |
191 | return ret; |
192 | } |
193 | |
194 | static int aries_main_bias(struct snd_soc_dapm_widget *w, |
195 | struct snd_kcontrol *kcontrol, int event) |
196 | { |
197 | struct snd_soc_card *card = w->dapm->card; |
198 | struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card); |
199 | int ret = 0; |
200 | |
201 | switch (event) { |
202 | case SND_SOC_DAPM_PRE_PMU: |
203 | ret = regulator_enable(regulator: priv->reg_main_micbias); |
204 | break; |
205 | case SND_SOC_DAPM_POST_PMD: |
206 | ret = regulator_disable(regulator: priv->reg_main_micbias); |
207 | break; |
208 | } |
209 | |
210 | return ret; |
211 | } |
212 | |
213 | static int aries_headset_bias(struct snd_soc_dapm_widget *w, |
214 | struct snd_kcontrol *kcontrol, int event) |
215 | { |
216 | struct snd_soc_card *card = w->dapm->card; |
217 | struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card); |
218 | int ret = 0; |
219 | |
220 | switch (event) { |
221 | case SND_SOC_DAPM_PRE_PMU: |
222 | ret = regulator_enable(regulator: priv->reg_headset_micbias); |
223 | break; |
224 | case SND_SOC_DAPM_POST_PMD: |
225 | ret = regulator_disable(regulator: priv->reg_headset_micbias); |
226 | break; |
227 | } |
228 | |
229 | return ret; |
230 | } |
231 | |
232 | static const struct snd_kcontrol_new aries_controls[] = { |
233 | SOC_DAPM_PIN_SWITCH("Modem In" ), |
234 | SOC_DAPM_PIN_SWITCH("Modem Out" ), |
235 | }; |
236 | |
237 | static const struct snd_soc_dapm_widget aries_dapm_widgets[] = { |
238 | SND_SOC_DAPM_HP("HP" , NULL), |
239 | |
240 | SND_SOC_DAPM_SPK("SPK" , aries_spk_cfg), |
241 | SND_SOC_DAPM_SPK("RCV" , NULL), |
242 | |
243 | SND_SOC_DAPM_LINE("LINE" , NULL), |
244 | |
245 | SND_SOC_DAPM_MIC("Main Mic" , aries_main_bias), |
246 | SND_SOC_DAPM_MIC("Headset Mic" , aries_headset_bias), |
247 | |
248 | SND_SOC_DAPM_MIC("Bluetooth Mic" , NULL), |
249 | SND_SOC_DAPM_SPK("Bluetooth SPK" , NULL), |
250 | |
251 | SND_SOC_DAPM_LINE("Modem In" , NULL), |
252 | SND_SOC_DAPM_LINE("Modem Out" , NULL), |
253 | |
254 | /* This must be last as it is conditionally not used */ |
255 | SND_SOC_DAPM_LINE("FM In" , NULL), |
256 | }; |
257 | |
258 | static int aries_hw_params(struct snd_pcm_substream *substream, |
259 | struct snd_pcm_hw_params *params) |
260 | { |
261 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
262 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
263 | unsigned int pll_out; |
264 | int ret; |
265 | |
266 | /* AIF1CLK should be >=3MHz for optimal performance */ |
267 | if (params_width(p: params) == 24) |
268 | pll_out = params_rate(p: params) * 384; |
269 | else if (params_rate(p: params) == 8000 || params_rate(p: params) == 11025) |
270 | pll_out = params_rate(p: params) * 512; |
271 | else |
272 | pll_out = params_rate(p: params) * 256; |
273 | |
274 | ret = snd_soc_dai_set_pll(dai: codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, |
275 | ARIES_MCLK1_FREQ, freq_out: pll_out); |
276 | if (ret < 0) |
277 | return ret; |
278 | |
279 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8994_SYSCLK_FLL1, |
280 | freq: pll_out, SND_SOC_CLOCK_IN); |
281 | if (ret < 0) |
282 | return ret; |
283 | |
284 | return 0; |
285 | } |
286 | |
287 | static int aries_hw_free(struct snd_pcm_substream *substream) |
288 | { |
289 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
290 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
291 | int ret; |
292 | |
293 | /* Switch sysclk to MCLK1 */ |
294 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8994_SYSCLK_MCLK1, |
295 | ARIES_MCLK1_FREQ, SND_SOC_CLOCK_IN); |
296 | if (ret < 0) |
297 | return ret; |
298 | |
299 | /* Stop PLL */ |
300 | ret = snd_soc_dai_set_pll(dai: codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, |
301 | ARIES_MCLK1_FREQ, freq_out: 0); |
302 | if (ret < 0) |
303 | return ret; |
304 | |
305 | return 0; |
306 | } |
307 | |
308 | /* |
309 | * Main DAI operations |
310 | */ |
311 | static const struct snd_soc_ops aries_ops = { |
312 | .hw_params = aries_hw_params, |
313 | .hw_free = aries_hw_free, |
314 | }; |
315 | |
316 | static int aries_baseband_init(struct snd_soc_pcm_runtime *rtd) |
317 | { |
318 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
319 | unsigned int pll_out; |
320 | int ret; |
321 | |
322 | pll_out = 8000 * 512; |
323 | |
324 | /* Set the codec FLL */ |
325 | ret = snd_soc_dai_set_pll(dai: codec_dai, WM8994_FLL2, WM8994_FLL_SRC_MCLK1, |
326 | ARIES_MCLK1_FREQ, freq_out: pll_out); |
327 | if (ret < 0) |
328 | return ret; |
329 | |
330 | /* Set the codec system clock */ |
331 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8994_SYSCLK_FLL2, |
332 | freq: pll_out, SND_SOC_CLOCK_IN); |
333 | if (ret < 0) |
334 | return ret; |
335 | |
336 | return 0; |
337 | } |
338 | |
339 | static int aries_late_probe(struct snd_soc_card *card) |
340 | { |
341 | struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card); |
342 | int ret, irq; |
343 | |
344 | ret = snd_soc_card_jack_new_pins(card, id: "Dock" , type: SND_JACK_LINEOUT, |
345 | jack: &aries_dock, pins: dock_pins, ARRAY_SIZE(dock_pins)); |
346 | if (ret) |
347 | return ret; |
348 | |
349 | ret = devm_extcon_register_notifier(dev: card->dev, |
350 | edev: priv->usb_extcon, EXTCON_JACK_LINE_OUT, |
351 | nb: &aries_extcon_notifier_block); |
352 | if (ret) |
353 | return ret; |
354 | |
355 | if (extcon_get_state(edev: priv->usb_extcon, |
356 | EXTCON_JACK_LINE_OUT) > 0) |
357 | snd_soc_jack_report(jack: &aries_dock, status: SND_JACK_LINEOUT, |
358 | mask: SND_JACK_LINEOUT); |
359 | else |
360 | snd_soc_jack_report(jack: &aries_dock, status: 0, mask: SND_JACK_LINEOUT); |
361 | |
362 | ret = snd_soc_card_jack_new_pins(card, id: "Headset" , |
363 | type: SND_JACK_HEADSET | SND_JACK_BTN_0, |
364 | jack: &aries_headset, |
365 | pins: jack_pins, ARRAY_SIZE(jack_pins)); |
366 | if (ret) |
367 | return ret; |
368 | |
369 | ret = snd_soc_jack_add_zones(jack: &aries_headset, ARRAY_SIZE(headset_zones), |
370 | zones: headset_zones); |
371 | if (ret) |
372 | return ret; |
373 | |
374 | irq = gpiod_to_irq(desc: priv->gpio_headset_detect); |
375 | if (irq < 0) { |
376 | dev_err(card->dev, "Failed to map headset detect gpio to irq" ); |
377 | return -EINVAL; |
378 | } |
379 | |
380 | ret = devm_request_threaded_irq(dev: card->dev, irq, NULL, |
381 | thread_fn: headset_det_irq_thread, |
382 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | |
383 | IRQF_ONESHOT, devname: "headset_detect" , dev_id: priv); |
384 | if (ret) { |
385 | dev_err(card->dev, "Failed to request headset detect irq" ); |
386 | return ret; |
387 | } |
388 | |
389 | headset_button_gpio[0].data = priv; |
390 | headset_button_gpio[0].desc = priv->gpio_headset_key; |
391 | |
392 | snd_jack_set_key(jack: aries_headset.jack, type: SND_JACK_BTN_0, KEY_MEDIA); |
393 | |
394 | return snd_soc_jack_add_gpios(jack: &aries_headset, |
395 | ARRAY_SIZE(headset_button_gpio), gpios: headset_button_gpio); |
396 | } |
397 | |
398 | static const struct snd_soc_pcm_stream baseband_params = { |
399 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
400 | .rate_min = 8000, |
401 | .rate_max = 8000, |
402 | .channels_min = 1, |
403 | .channels_max = 1, |
404 | }; |
405 | |
406 | static const struct snd_soc_pcm_stream bluetooth_params = { |
407 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
408 | .rate_min = 8000, |
409 | .rate_max = 8000, |
410 | .channels_min = 1, |
411 | .channels_max = 2, |
412 | }; |
413 | |
414 | static const struct snd_soc_dapm_widget aries_modem_widgets[] = { |
415 | SND_SOC_DAPM_INPUT("Modem RX" ), |
416 | SND_SOC_DAPM_OUTPUT("Modem TX" ), |
417 | }; |
418 | |
419 | static const struct snd_soc_dapm_route aries_modem_routes[] = { |
420 | { "Modem Capture" , NULL, "Modem RX" }, |
421 | { "Modem TX" , NULL, "Modem Playback" }, |
422 | }; |
423 | |
424 | static const struct snd_soc_component_driver aries_component = { |
425 | .name = "aries-audio" , |
426 | .dapm_widgets = aries_modem_widgets, |
427 | .num_dapm_widgets = ARRAY_SIZE(aries_modem_widgets), |
428 | .dapm_routes = aries_modem_routes, |
429 | .num_dapm_routes = ARRAY_SIZE(aries_modem_routes), |
430 | .idle_bias_on = 1, |
431 | .use_pmdown_time = 1, |
432 | .endianness = 1, |
433 | }; |
434 | |
435 | static struct snd_soc_dai_driver aries_ext_dai[] = { |
436 | { |
437 | .name = "Voice call" , |
438 | .playback = { |
439 | .stream_name = "Modem Playback" , |
440 | .channels_min = 1, |
441 | .channels_max = 1, |
442 | .rate_min = 8000, |
443 | .rate_max = 8000, |
444 | .rates = SNDRV_PCM_RATE_8000, |
445 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
446 | }, |
447 | .capture = { |
448 | .stream_name = "Modem Capture" , |
449 | .channels_min = 1, |
450 | .channels_max = 1, |
451 | .rate_min = 8000, |
452 | .rate_max = 8000, |
453 | .rates = SNDRV_PCM_RATE_8000, |
454 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
455 | }, |
456 | }, |
457 | }; |
458 | |
459 | SND_SOC_DAILINK_DEFS(aif1, |
460 | DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)), |
461 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1" )), |
462 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
463 | |
464 | SND_SOC_DAILINK_DEFS(baseband, |
465 | DAILINK_COMP_ARRAY(COMP_CPU("Voice call" )), |
466 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif2" ))); |
467 | |
468 | SND_SOC_DAILINK_DEFS(bluetooth, |
469 | DAILINK_COMP_ARRAY(COMP_CPU("bt-sco-pcm" )), |
470 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif3" ))); |
471 | |
472 | static struct snd_soc_dai_link aries_dai[] = { |
473 | { |
474 | .name = "WM8994 AIF1" , |
475 | .stream_name = "HiFi" , |
476 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
477 | SND_SOC_DAIFMT_CBM_CFM, |
478 | .ops = &aries_ops, |
479 | SND_SOC_DAILINK_REG(aif1), |
480 | }, |
481 | { |
482 | .name = "WM8994 AIF2" , |
483 | .stream_name = "Baseband" , |
484 | .init = &aries_baseband_init, |
485 | .c2c_params = &baseband_params, |
486 | .num_c2c_params = 1, |
487 | .ignore_suspend = 1, |
488 | SND_SOC_DAILINK_REG(baseband), |
489 | }, |
490 | { |
491 | .name = "WM8994 AIF3" , |
492 | .stream_name = "Bluetooth" , |
493 | .c2c_params = &bluetooth_params, |
494 | .num_c2c_params = 1, |
495 | .ignore_suspend = 1, |
496 | SND_SOC_DAILINK_REG(bluetooth), |
497 | }, |
498 | }; |
499 | |
500 | static struct snd_soc_card aries_card = { |
501 | .name = "ARIES" , |
502 | .owner = THIS_MODULE, |
503 | .dai_link = aries_dai, |
504 | .num_links = ARRAY_SIZE(aries_dai), |
505 | .controls = aries_controls, |
506 | .num_controls = ARRAY_SIZE(aries_controls), |
507 | .dapm_widgets = aries_dapm_widgets, |
508 | .num_dapm_widgets = ARRAY_SIZE(aries_dapm_widgets), |
509 | .late_probe = aries_late_probe, |
510 | }; |
511 | |
512 | static const struct aries_wm8994_variant fascinate4g_variant = { |
513 | .modem_dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS |
514 | | SND_SOC_DAIFMT_IB_NF, |
515 | .has_fm_radio = false, |
516 | }; |
517 | |
518 | static const struct aries_wm8994_variant aries_variant = { |
519 | .modem_dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM |
520 | | SND_SOC_DAIFMT_IB_NF, |
521 | .has_fm_radio = true, |
522 | }; |
523 | |
524 | static const struct of_device_id samsung_wm8994_of_match[] = { |
525 | { |
526 | .compatible = "samsung,fascinate4g-wm8994" , |
527 | .data = &fascinate4g_variant, |
528 | }, |
529 | { |
530 | .compatible = "samsung,aries-wm8994" , |
531 | .data = &aries_variant, |
532 | }, |
533 | { /* sentinel */ }, |
534 | }; |
535 | MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match); |
536 | |
537 | static int aries_audio_probe(struct platform_device *pdev) |
538 | { |
539 | struct device_node *np = pdev->dev.of_node; |
540 | struct device_node *cpu, *codec, *extcon_np; |
541 | struct device *dev = &pdev->dev; |
542 | struct snd_soc_card *card = &aries_card; |
543 | struct aries_wm8994_data *priv; |
544 | struct snd_soc_dai_link *dai_link; |
545 | const struct of_device_id *match; |
546 | enum iio_chan_type channel_type; |
547 | int ret, i; |
548 | |
549 | if (!np) |
550 | return -EINVAL; |
551 | |
552 | card->dev = dev; |
553 | |
554 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
555 | if (!priv) |
556 | return -ENOMEM; |
557 | |
558 | snd_soc_card_set_drvdata(card, data: priv); |
559 | |
560 | match = of_match_node(matches: samsung_wm8994_of_match, node: np); |
561 | priv->variant = match->data; |
562 | |
563 | /* Remove FM widget if not present */ |
564 | if (!priv->variant->has_fm_radio) |
565 | card->num_dapm_widgets--; |
566 | |
567 | priv->reg_main_micbias = devm_regulator_get(dev, id: "main-micbias" ); |
568 | if (IS_ERR(ptr: priv->reg_main_micbias)) { |
569 | dev_err(dev, "Failed to get main micbias regulator\n" ); |
570 | return PTR_ERR(ptr: priv->reg_main_micbias); |
571 | } |
572 | |
573 | priv->reg_headset_micbias = devm_regulator_get(dev, id: "headset-micbias" ); |
574 | if (IS_ERR(ptr: priv->reg_headset_micbias)) { |
575 | dev_err(dev, "Failed to get headset micbias regulator\n" ); |
576 | return PTR_ERR(ptr: priv->reg_headset_micbias); |
577 | } |
578 | |
579 | priv->gpio_earpath_sel = devm_gpiod_get(dev, con_id: "earpath-sel" , |
580 | flags: GPIOD_OUT_LOW); |
581 | if (IS_ERR(ptr: priv->gpio_earpath_sel)) { |
582 | dev_err(dev, "Failed to get earpath selector gpio" ); |
583 | return PTR_ERR(ptr: priv->gpio_earpath_sel); |
584 | } |
585 | |
586 | extcon_np = of_parse_phandle(np, phandle_name: "extcon" , index: 0); |
587 | priv->usb_extcon = extcon_find_edev_by_node(node: extcon_np); |
588 | of_node_put(node: extcon_np); |
589 | if (IS_ERR(ptr: priv->usb_extcon)) |
590 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->usb_extcon), |
591 | fmt: "Failed to get extcon device" ); |
592 | |
593 | priv->adc = devm_iio_channel_get(dev, consumer_channel: "headset-detect" ); |
594 | if (IS_ERR(ptr: priv->adc)) |
595 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->adc), |
596 | fmt: "Failed to get ADC channel" ); |
597 | |
598 | ret = iio_get_channel_type(channel: priv->adc, type: &channel_type); |
599 | if (ret) |
600 | return dev_err_probe(dev, err: ret, |
601 | fmt: "Failed to get ADC channel type" ); |
602 | if (channel_type != IIO_VOLTAGE) |
603 | return -EINVAL; |
604 | |
605 | priv->gpio_headset_key = devm_gpiod_get(dev, con_id: "headset-key" , |
606 | flags: GPIOD_IN); |
607 | if (IS_ERR(ptr: priv->gpio_headset_key)) { |
608 | dev_err(dev, "Failed to get headset key gpio" ); |
609 | return PTR_ERR(ptr: priv->gpio_headset_key); |
610 | } |
611 | |
612 | priv->gpio_headset_detect = devm_gpiod_get(dev, |
613 | con_id: "headset-detect" , flags: GPIOD_IN); |
614 | if (IS_ERR(ptr: priv->gpio_headset_detect)) { |
615 | dev_err(dev, "Failed to get headset detect gpio" ); |
616 | return PTR_ERR(ptr: priv->gpio_headset_detect); |
617 | } |
618 | |
619 | /* Update card-name if provided through DT, else use default name */ |
620 | snd_soc_of_parse_card_name(card, propname: "model" ); |
621 | |
622 | ret = snd_soc_of_parse_audio_routing(card, propname: "audio-routing" ); |
623 | if (ret < 0) { |
624 | /* Backwards compatible way */ |
625 | ret = snd_soc_of_parse_audio_routing(card, propname: "samsung,audio-routing" ); |
626 | if (ret < 0) { |
627 | dev_err(dev, "Audio routing invalid/unspecified\n" ); |
628 | return ret; |
629 | } |
630 | } |
631 | |
632 | aries_dai[1].dai_fmt = priv->variant->modem_dai_fmt; |
633 | |
634 | cpu = of_get_child_by_name(node: dev->of_node, name: "cpu" ); |
635 | if (!cpu) |
636 | return -EINVAL; |
637 | |
638 | codec = of_get_child_by_name(node: dev->of_node, name: "codec" ); |
639 | if (!codec) { |
640 | ret = -EINVAL; |
641 | goto out; |
642 | } |
643 | |
644 | for_each_card_prelinks(card, i, dai_link) { |
645 | dai_link->codecs->of_node = of_parse_phandle(np: codec, |
646 | phandle_name: "sound-dai" , index: 0); |
647 | if (!dai_link->codecs->of_node) { |
648 | ret = -EINVAL; |
649 | goto out; |
650 | } |
651 | } |
652 | |
653 | /* Set CPU and platform of_node for main DAI */ |
654 | aries_dai[0].cpus->of_node = of_parse_phandle(np: cpu, |
655 | phandle_name: "sound-dai" , index: 0); |
656 | if (!aries_dai[0].cpus->of_node) { |
657 | ret = -EINVAL; |
658 | goto out; |
659 | } |
660 | |
661 | aries_dai[0].platforms->of_node = aries_dai[0].cpus->of_node; |
662 | |
663 | /* Set CPU of_node for BT DAI */ |
664 | aries_dai[2].cpus->of_node = of_parse_phandle(np: cpu, |
665 | phandle_name: "sound-dai" , index: 1); |
666 | if (!aries_dai[2].cpus->of_node) { |
667 | ret = -EINVAL; |
668 | goto out; |
669 | } |
670 | |
671 | ret = devm_snd_soc_register_component(dev, component_driver: &aries_component, |
672 | dai_drv: aries_ext_dai, ARRAY_SIZE(aries_ext_dai)); |
673 | if (ret < 0) { |
674 | dev_err(dev, "Failed to register component: %d\n" , ret); |
675 | goto out; |
676 | } |
677 | |
678 | ret = devm_snd_soc_register_card(dev, card); |
679 | if (ret) |
680 | dev_err(dev, "snd_soc_register_card() failed:%d\n" , ret); |
681 | |
682 | out: |
683 | of_node_put(node: cpu); |
684 | of_node_put(node: codec); |
685 | |
686 | return ret; |
687 | } |
688 | |
689 | static struct platform_driver aries_audio_driver = { |
690 | .driver = { |
691 | .name = "aries-audio-wm8994" , |
692 | .of_match_table = of_match_ptr(samsung_wm8994_of_match), |
693 | .pm = &snd_soc_pm_ops, |
694 | }, |
695 | .probe = aries_audio_probe, |
696 | }; |
697 | |
698 | module_platform_driver(aries_audio_driver); |
699 | |
700 | MODULE_DESCRIPTION("ALSA SoC ARIES WM8994" ); |
701 | MODULE_LICENSE("GPL" ); |
702 | MODULE_ALIAS("platform:aries-audio-wm8994" ); |
703 | |