1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Midas audio support |
4 | // |
5 | // Copyright (C) 2018 Simon Shields <simon@lineageos.org> |
6 | // Copyright (C) 2020 Samsung Electronics Co., Ltd. |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/mfd/wm8994/registers.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/regulator/consumer.h> |
14 | #include <sound/jack.h> |
15 | #include <sound/soc.h> |
16 | #include <sound/soc-dapm.h> |
17 | |
18 | #include "i2s.h" |
19 | #include "../codecs/wm8994.h" |
20 | |
21 | /* |
22 | * The MCLK1 clock source is XCLKOUT with its mux set to the external fixed rate |
23 | * oscillator (XXTI). |
24 | */ |
25 | #define MCLK1_RATE 24000000U |
26 | #define MCLK2_RATE 32768U |
27 | #define DEFAULT_FLL1_RATE 11289600U |
28 | |
29 | struct midas_priv { |
30 | struct regulator *reg_mic_bias; |
31 | struct regulator *reg_submic_bias; |
32 | struct gpio_desc *gpio_fm_sel; |
33 | struct gpio_desc *gpio_lineout_sel; |
34 | unsigned int fll1_rate; |
35 | |
36 | struct snd_soc_jack headset_jack; |
37 | }; |
38 | |
39 | static struct snd_soc_jack_pin headset_jack_pins[] = { |
40 | { |
41 | .pin = "Headphone" , |
42 | .mask = SND_JACK_HEADPHONE, |
43 | }, |
44 | { |
45 | .pin = "Headset Mic" , |
46 | .mask = SND_JACK_MICROPHONE, |
47 | }, |
48 | }; |
49 | |
50 | static int midas_start_fll1(struct snd_soc_pcm_runtime *rtd, unsigned int rate) |
51 | { |
52 | struct snd_soc_card *card = rtd->card; |
53 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
54 | struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
55 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
56 | int ret; |
57 | |
58 | if (!rate) |
59 | rate = priv->fll1_rate; |
60 | /* |
61 | * If no new rate is requested, set FLL1 to a sane default for jack |
62 | * detection. |
63 | */ |
64 | if (!rate) |
65 | rate = DEFAULT_FLL1_RATE; |
66 | |
67 | if (rate != priv->fll1_rate && priv->fll1_rate) { |
68 | /* while reconfiguring, switch to MCLK2 for SYSCLK */ |
69 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, WM8994_SYSCLK_MCLK2, |
70 | MCLK2_RATE, SND_SOC_CLOCK_IN); |
71 | if (ret < 0) { |
72 | dev_err(card->dev, "Unable to switch to MCLK2: %d\n" , ret); |
73 | return ret; |
74 | } |
75 | } |
76 | |
77 | ret = snd_soc_dai_set_pll(dai: aif1_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, |
78 | MCLK1_RATE, freq_out: rate); |
79 | if (ret < 0) { |
80 | dev_err(card->dev, "Failed to set FLL1 rate: %d\n" , ret); |
81 | return ret; |
82 | } |
83 | priv->fll1_rate = rate; |
84 | |
85 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, WM8994_SYSCLK_FLL1, |
86 | freq: priv->fll1_rate, SND_SOC_CLOCK_IN); |
87 | if (ret < 0) { |
88 | dev_err(card->dev, "Failed to set SYSCLK source: %d\n" , ret); |
89 | return ret; |
90 | } |
91 | |
92 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, SAMSUNG_I2S_OPCLK, freq: 0, |
93 | SAMSUNG_I2S_OPCLK_PCLK); |
94 | if (ret < 0) { |
95 | dev_err(card->dev, "Failed to set OPCLK source: %d\n" , ret); |
96 | return ret; |
97 | } |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | static int midas_stop_fll1(struct snd_soc_pcm_runtime *rtd) |
103 | { |
104 | struct snd_soc_card *card = rtd->card; |
105 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
106 | struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
107 | int ret; |
108 | |
109 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, WM8994_SYSCLK_MCLK2, |
110 | MCLK2_RATE, SND_SOC_CLOCK_IN); |
111 | if (ret < 0) { |
112 | dev_err(card->dev, "Unable to switch to MCLK2: %d\n" , ret); |
113 | return ret; |
114 | } |
115 | |
116 | ret = snd_soc_dai_set_pll(dai: aif1_dai, WM8994_FLL1, source: 0, freq_in: 0, freq_out: 0); |
117 | if (ret < 0) { |
118 | dev_err(card->dev, "Unable to stop FLL1: %d\n" , ret); |
119 | return ret; |
120 | } |
121 | |
122 | priv->fll1_rate = 0; |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static int midas_aif1_hw_params(struct snd_pcm_substream *substream, |
128 | struct snd_pcm_hw_params *params) |
129 | { |
130 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
131 | unsigned int pll_out; |
132 | |
133 | /* AIF1CLK should be at least 3MHz for "optimal performance" */ |
134 | if (params_rate(p: params) == 8000 || params_rate(p: params) == 11025) |
135 | pll_out = params_rate(p: params) * 512; |
136 | else |
137 | pll_out = params_rate(p: params) * 256; |
138 | |
139 | return midas_start_fll1(rtd, rate: pll_out); |
140 | } |
141 | |
142 | static const struct snd_soc_ops midas_aif1_ops = { |
143 | .hw_params = midas_aif1_hw_params, |
144 | }; |
145 | |
146 | /* |
147 | * We only have a single external speaker, so mix stereo data |
148 | * to a single mono stream. |
149 | */ |
150 | static int midas_ext_spkmode(struct snd_soc_dapm_widget *w, |
151 | struct snd_kcontrol *kcontrol, int event) |
152 | { |
153 | struct snd_soc_component *codec = snd_soc_dapm_to_component(dapm: w->dapm); |
154 | int ret = 0; |
155 | |
156 | switch (event) { |
157 | case SND_SOC_DAPM_PRE_PMU: |
158 | ret = snd_soc_component_update_bits(component: codec, WM8994_SPKOUT_MIXERS, |
159 | WM8994_SPKMIXR_TO_SPKOUTL_MASK, |
160 | WM8994_SPKMIXR_TO_SPKOUTL); |
161 | break; |
162 | case SND_SOC_DAPM_POST_PMD: |
163 | ret = snd_soc_component_update_bits(component: codec, WM8994_SPKOUT_MIXERS, |
164 | WM8994_SPKMIXR_TO_SPKOUTL_MASK, |
165 | val: 0); |
166 | break; |
167 | } |
168 | |
169 | return ret; |
170 | } |
171 | |
172 | static int midas_mic_bias(struct snd_soc_dapm_widget *w, |
173 | struct snd_kcontrol *kcontrol, int event) |
174 | { |
175 | struct snd_soc_card *card = w->dapm->card; |
176 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
177 | |
178 | switch (event) { |
179 | case SND_SOC_DAPM_PRE_PMU: |
180 | return regulator_enable(regulator: priv->reg_mic_bias); |
181 | case SND_SOC_DAPM_POST_PMD: |
182 | return regulator_disable(regulator: priv->reg_mic_bias); |
183 | } |
184 | |
185 | return 0; |
186 | } |
187 | |
188 | static int midas_submic_bias(struct snd_soc_dapm_widget *w, |
189 | struct snd_kcontrol *kcontrol, int event) |
190 | { |
191 | struct snd_soc_card *card = w->dapm->card; |
192 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
193 | |
194 | switch (event) { |
195 | case SND_SOC_DAPM_PRE_PMU: |
196 | return regulator_enable(regulator: priv->reg_submic_bias); |
197 | case SND_SOC_DAPM_POST_PMD: |
198 | return regulator_disable(regulator: priv->reg_submic_bias); |
199 | } |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static int midas_fm_set(struct snd_soc_dapm_widget *w, |
205 | struct snd_kcontrol *kcontrol, int event) |
206 | { |
207 | struct snd_soc_card *card = w->dapm->card; |
208 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
209 | |
210 | if (!priv->gpio_fm_sel) |
211 | return 0; |
212 | |
213 | switch (event) { |
214 | case SND_SOC_DAPM_PRE_PMU: |
215 | gpiod_set_value_cansleep(desc: priv->gpio_fm_sel, value: 1); |
216 | break; |
217 | case SND_SOC_DAPM_POST_PMD: |
218 | gpiod_set_value_cansleep(desc: priv->gpio_fm_sel, value: 0); |
219 | break; |
220 | } |
221 | |
222 | return 0; |
223 | } |
224 | |
225 | static int midas_line_set(struct snd_soc_dapm_widget *w, |
226 | struct snd_kcontrol *kcontrol, int event) |
227 | { |
228 | struct snd_soc_card *card = w->dapm->card; |
229 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
230 | |
231 | if (!priv->gpio_lineout_sel) |
232 | return 0; |
233 | |
234 | switch (event) { |
235 | case SND_SOC_DAPM_PRE_PMU: |
236 | gpiod_set_value_cansleep(desc: priv->gpio_lineout_sel, value: 1); |
237 | break; |
238 | case SND_SOC_DAPM_POST_PMD: |
239 | gpiod_set_value_cansleep(desc: priv->gpio_lineout_sel, value: 0); |
240 | break; |
241 | } |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static const struct snd_kcontrol_new midas_controls[] = { |
247 | SOC_DAPM_PIN_SWITCH("HP" ), |
248 | |
249 | SOC_DAPM_PIN_SWITCH("SPK" ), |
250 | SOC_DAPM_PIN_SWITCH("RCV" ), |
251 | |
252 | SOC_DAPM_PIN_SWITCH("LINE" ), |
253 | SOC_DAPM_PIN_SWITCH("HDMI" ), |
254 | |
255 | SOC_DAPM_PIN_SWITCH("Main Mic" ), |
256 | SOC_DAPM_PIN_SWITCH("Sub Mic" ), |
257 | SOC_DAPM_PIN_SWITCH("Headset Mic" ), |
258 | |
259 | SOC_DAPM_PIN_SWITCH("FM In" ), |
260 | }; |
261 | |
262 | static const struct snd_soc_dapm_widget midas_dapm_widgets[] = { |
263 | SND_SOC_DAPM_HP("HP" , NULL), |
264 | |
265 | SND_SOC_DAPM_SPK("SPK" , midas_ext_spkmode), |
266 | SND_SOC_DAPM_SPK("RCV" , NULL), |
267 | |
268 | /* FIXME: toggle MAX77693 on i9300/i9305 */ |
269 | SND_SOC_DAPM_LINE("LINE" , midas_line_set), |
270 | SND_SOC_DAPM_LINE("HDMI" , NULL), |
271 | SND_SOC_DAPM_LINE("FM In" , midas_fm_set), |
272 | |
273 | SND_SOC_DAPM_HP("Headphone" , NULL), |
274 | SND_SOC_DAPM_MIC("Headset Mic" , NULL), |
275 | SND_SOC_DAPM_MIC("Main Mic" , midas_mic_bias), |
276 | SND_SOC_DAPM_MIC("Sub Mic" , midas_submic_bias), |
277 | }; |
278 | |
279 | static int midas_set_bias_level(struct snd_soc_card *card, |
280 | struct snd_soc_dapm_context *dapm, |
281 | enum snd_soc_bias_level level) |
282 | { |
283 | struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, |
284 | dai_link: &card->dai_link[0]); |
285 | struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
286 | |
287 | if (dapm->dev != aif1_dai->dev) |
288 | return 0; |
289 | |
290 | switch (level) { |
291 | case SND_SOC_BIAS_STANDBY: |
292 | return midas_stop_fll1(rtd); |
293 | case SND_SOC_BIAS_PREPARE: |
294 | return midas_start_fll1(rtd, rate: 0); |
295 | default: |
296 | break; |
297 | } |
298 | |
299 | return 0; |
300 | } |
301 | |
302 | static int midas_late_probe(struct snd_soc_card *card) |
303 | { |
304 | struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, |
305 | dai_link: &card->dai_link[0]); |
306 | struct snd_soc_dai *aif1_dai = snd_soc_rtd_to_codec(rtd, 0); |
307 | struct midas_priv *priv = snd_soc_card_get_drvdata(card); |
308 | int ret; |
309 | |
310 | /* Use MCLK2 as SYSCLK for boot */ |
311 | ret = snd_soc_dai_set_sysclk(dai: aif1_dai, WM8994_SYSCLK_MCLK2, MCLK2_RATE, |
312 | SND_SOC_CLOCK_IN); |
313 | if (ret < 0) { |
314 | dev_err(aif1_dai->dev, "Failed to switch to MCLK2: %d\n" , ret); |
315 | return ret; |
316 | } |
317 | |
318 | ret = snd_soc_card_jack_new_pins(card, id: "Headset" , |
319 | type: SND_JACK_HEADSET | SND_JACK_MECHANICAL | |
320 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | |
321 | SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5, |
322 | jack: &priv->headset_jack, |
323 | pins: headset_jack_pins, |
324 | ARRAY_SIZE(headset_jack_pins)); |
325 | if (ret) |
326 | return ret; |
327 | |
328 | wm8958_mic_detect(component: aif1_dai->component, jack: &priv->headset_jack, |
329 | NULL, NULL, NULL, NULL); |
330 | return 0; |
331 | } |
332 | |
333 | static struct snd_soc_dai_driver midas_ext_dai[] = { |
334 | { |
335 | .name = "Voice call" , |
336 | .playback = { |
337 | .channels_min = 1, |
338 | .channels_max = 2, |
339 | .rate_min = 8000, |
340 | .rate_max = 16000, |
341 | .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), |
342 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
343 | }, |
344 | .capture = { |
345 | .channels_min = 1, |
346 | .channels_max = 2, |
347 | .rate_min = 8000, |
348 | .rate_max = 16000, |
349 | .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), |
350 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
351 | }, |
352 | }, |
353 | { |
354 | .name = "Bluetooth" , |
355 | .playback = { |
356 | .channels_min = 1, |
357 | .channels_max = 2, |
358 | .rate_min = 8000, |
359 | .rate_max = 16000, |
360 | .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), |
361 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
362 | }, |
363 | .capture = { |
364 | .channels_min = 1, |
365 | .channels_max = 2, |
366 | .rate_min = 8000, |
367 | .rate_max = 16000, |
368 | .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), |
369 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
370 | }, |
371 | }, |
372 | }; |
373 | |
374 | static const struct snd_soc_component_driver midas_component = { |
375 | .name = "midas-audio" , |
376 | }; |
377 | |
378 | SND_SOC_DAILINK_DEFS(wm1811_hifi, |
379 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
380 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1" )), |
381 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
382 | |
383 | SND_SOC_DAILINK_DEFS(wm1811_voice, |
384 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
385 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif2" )), |
386 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
387 | |
388 | SND_SOC_DAILINK_DEFS(wm1811_bt, |
389 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
390 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif3" )), |
391 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
392 | |
393 | static struct snd_soc_dai_link midas_dai[] = { |
394 | { |
395 | .name = "WM8994 AIF1" , |
396 | .stream_name = "HiFi Primary" , |
397 | .ops = &midas_aif1_ops, |
398 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
399 | SND_SOC_DAIFMT_CBM_CFM, |
400 | SND_SOC_DAILINK_REG(wm1811_hifi), |
401 | }, { |
402 | .name = "WM1811 Voice" , |
403 | .stream_name = "Voice call" , |
404 | .ignore_suspend = 1, |
405 | SND_SOC_DAILINK_REG(wm1811_voice), |
406 | }, { |
407 | .name = "WM1811 BT" , |
408 | .stream_name = "Bluetooth" , |
409 | .ignore_suspend = 1, |
410 | SND_SOC_DAILINK_REG(wm1811_bt), |
411 | }, |
412 | }; |
413 | |
414 | static struct snd_soc_card midas_card = { |
415 | .name = "Midas WM1811" , |
416 | .owner = THIS_MODULE, |
417 | |
418 | .dai_link = midas_dai, |
419 | .num_links = ARRAY_SIZE(midas_dai), |
420 | .controls = midas_controls, |
421 | .num_controls = ARRAY_SIZE(midas_controls), |
422 | .dapm_widgets = midas_dapm_widgets, |
423 | .num_dapm_widgets = ARRAY_SIZE(midas_dapm_widgets), |
424 | |
425 | .set_bias_level = midas_set_bias_level, |
426 | .late_probe = midas_late_probe, |
427 | }; |
428 | |
429 | static int midas_probe(struct platform_device *pdev) |
430 | { |
431 | struct device_node *cpu_dai_node = NULL, *codec_dai_node = NULL; |
432 | struct device_node *cpu = NULL, *codec = NULL; |
433 | struct snd_soc_card *card = &midas_card; |
434 | struct device *dev = &pdev->dev; |
435 | static struct snd_soc_dai_link *dai_link; |
436 | struct midas_priv *priv; |
437 | int ret, i; |
438 | |
439 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
440 | if (!priv) |
441 | return -ENOMEM; |
442 | |
443 | snd_soc_card_set_drvdata(card, data: priv); |
444 | card->dev = dev; |
445 | |
446 | priv->reg_mic_bias = devm_regulator_get(dev, id: "mic-bias" ); |
447 | if (IS_ERR(ptr: priv->reg_mic_bias)) { |
448 | dev_err(dev, "Failed to get mic bias regulator\n" ); |
449 | return PTR_ERR(ptr: priv->reg_mic_bias); |
450 | } |
451 | |
452 | priv->reg_submic_bias = devm_regulator_get(dev, id: "submic-bias" ); |
453 | if (IS_ERR(ptr: priv->reg_submic_bias)) { |
454 | dev_err(dev, "Failed to get submic bias regulator\n" ); |
455 | return PTR_ERR(ptr: priv->reg_submic_bias); |
456 | } |
457 | |
458 | priv->gpio_fm_sel = devm_gpiod_get_optional(dev, con_id: "fm-sel" , flags: GPIOD_OUT_HIGH); |
459 | if (IS_ERR(ptr: priv->gpio_fm_sel)) { |
460 | dev_err(dev, "Failed to get FM selection GPIO\n" ); |
461 | return PTR_ERR(ptr: priv->gpio_fm_sel); |
462 | } |
463 | |
464 | priv->gpio_lineout_sel = devm_gpiod_get_optional(dev, con_id: "lineout-sel" , |
465 | flags: GPIOD_OUT_HIGH); |
466 | if (IS_ERR(ptr: priv->gpio_lineout_sel)) { |
467 | dev_err(dev, "Failed to get line out selection GPIO\n" ); |
468 | return PTR_ERR(ptr: priv->gpio_lineout_sel); |
469 | } |
470 | |
471 | ret = snd_soc_of_parse_card_name(card, propname: "model" ); |
472 | if (ret < 0) { |
473 | dev_err(dev, "Card name is not specified\n" ); |
474 | return ret; |
475 | } |
476 | |
477 | ret = snd_soc_of_parse_audio_routing(card, propname: "audio-routing" ); |
478 | if (ret < 0) { |
479 | /* Backwards compatible way */ |
480 | ret = snd_soc_of_parse_audio_routing(card, propname: "samsung,audio-routing" ); |
481 | if (ret < 0) { |
482 | dev_err(dev, "Audio routing invalid/unspecified\n" ); |
483 | return ret; |
484 | } |
485 | } |
486 | |
487 | cpu = of_get_child_by_name(node: dev->of_node, name: "cpu" ); |
488 | if (!cpu) |
489 | return -EINVAL; |
490 | |
491 | codec = of_get_child_by_name(node: dev->of_node, name: "codec" ); |
492 | if (!codec) { |
493 | of_node_put(node: cpu); |
494 | return -EINVAL; |
495 | } |
496 | |
497 | cpu_dai_node = of_parse_phandle(np: cpu, phandle_name: "sound-dai" , index: 0); |
498 | of_node_put(node: cpu); |
499 | if (!cpu_dai_node) { |
500 | dev_err(dev, "parsing cpu/sound-dai failed\n" ); |
501 | of_node_put(node: codec); |
502 | return -EINVAL; |
503 | } |
504 | |
505 | codec_dai_node = of_parse_phandle(np: codec, phandle_name: "sound-dai" , index: 0); |
506 | of_node_put(node: codec); |
507 | if (!codec_dai_node) { |
508 | dev_err(dev, "audio-codec property invalid/missing\n" ); |
509 | ret = -EINVAL; |
510 | goto put_cpu_dai_node; |
511 | } |
512 | |
513 | for_each_card_prelinks(card, i, dai_link) { |
514 | dai_link->codecs->of_node = codec_dai_node; |
515 | dai_link->cpus->of_node = cpu_dai_node; |
516 | dai_link->platforms->of_node = cpu_dai_node; |
517 | } |
518 | |
519 | ret = devm_snd_soc_register_component(dev, component_driver: &midas_component, |
520 | dai_drv: midas_ext_dai, ARRAY_SIZE(midas_ext_dai)); |
521 | if (ret < 0) { |
522 | dev_err(dev, "Failed to register component: %d\n" , ret); |
523 | goto put_codec_dai_node; |
524 | } |
525 | |
526 | ret = devm_snd_soc_register_card(dev, card); |
527 | if (ret < 0) { |
528 | dev_err(dev, "Failed to register card: %d\n" , ret); |
529 | goto put_codec_dai_node; |
530 | } |
531 | |
532 | return 0; |
533 | |
534 | put_codec_dai_node: |
535 | of_node_put(node: codec_dai_node); |
536 | put_cpu_dai_node: |
537 | of_node_put(node: cpu_dai_node); |
538 | return ret; |
539 | } |
540 | |
541 | static const struct of_device_id midas_of_match[] = { |
542 | { .compatible = "samsung,midas-audio" }, |
543 | { }, |
544 | }; |
545 | MODULE_DEVICE_TABLE(of, midas_of_match); |
546 | |
547 | static struct platform_driver midas_driver = { |
548 | .driver = { |
549 | .name = "midas-audio" , |
550 | .of_match_table = midas_of_match, |
551 | .pm = &snd_soc_pm_ops, |
552 | }, |
553 | .probe = midas_probe, |
554 | }; |
555 | module_platform_driver(midas_driver); |
556 | |
557 | MODULE_AUTHOR("Simon Shields <simon@lineageos.org>" ); |
558 | MODULE_DESCRIPTION("ASoC support for Midas" ); |
559 | MODULE_LICENSE("GPL v2" ); |
560 | |