1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Freescale Generic ASoC Sound Card driver with ASRC |
4 | // |
5 | // Copyright (C) 2014 Freescale Semiconductor, Inc. |
6 | // |
7 | // Author: Nicolin Chen <nicoleotsuka@gmail.com> |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of_platform.h> |
13 | #if IS_ENABLED(CONFIG_SND_AC97_CODEC) |
14 | #include <sound/ac97_codec.h> |
15 | #endif |
16 | #include <sound/pcm_params.h> |
17 | #include <sound/soc.h> |
18 | #include <sound/jack.h> |
19 | #include <sound/simple_card_utils.h> |
20 | |
21 | #include "fsl_esai.h" |
22 | #include "fsl_sai.h" |
23 | #include "imx-audmux.h" |
24 | |
25 | #include "../codecs/sgtl5000.h" |
26 | #include "../codecs/wm8962.h" |
27 | #include "../codecs/wm8960.h" |
28 | #include "../codecs/wm8994.h" |
29 | #include "../codecs/tlv320aic31xx.h" |
30 | #include "../codecs/nau8822.h" |
31 | |
32 | #define DRIVER_NAME "fsl-asoc-card" |
33 | |
34 | #define CS427x_SYSCLK_MCLK 0 |
35 | |
36 | #define RX 0 |
37 | #define TX 1 |
38 | |
39 | /* Default DAI format without Master and Slave flag */ |
40 | #define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF) |
41 | |
42 | /** |
43 | * struct codec_priv - CODEC private data |
44 | * @mclk: Main clock of the CODEC |
45 | * @mclk_freq: Clock rate of MCLK |
46 | * @free_freq: Clock rate of MCLK for hw_free() |
47 | * @mclk_id: MCLK (or main clock) id for set_sysclk() |
48 | * @fll_id: FLL (or secordary clock) id for set_sysclk() |
49 | * @pll_id: PLL id for set_pll() |
50 | */ |
51 | struct codec_priv { |
52 | struct clk *mclk; |
53 | unsigned long mclk_freq; |
54 | unsigned long free_freq; |
55 | u32 mclk_id; |
56 | int fll_id; |
57 | int pll_id; |
58 | }; |
59 | |
60 | /** |
61 | * struct cpu_priv - CPU private data |
62 | * @sysclk_freq: SYSCLK rates for set_sysclk() |
63 | * @sysclk_dir: SYSCLK directions for set_sysclk() |
64 | * @sysclk_id: SYSCLK ids for set_sysclk() |
65 | * @slot_width: Slot width of each frame |
66 | * @slot_num: Number of slots of each frame |
67 | * |
68 | * Note: [1] for tx and [0] for rx |
69 | */ |
70 | struct cpu_priv { |
71 | unsigned long sysclk_freq[2]; |
72 | u32 sysclk_dir[2]; |
73 | u32 sysclk_id[2]; |
74 | u32 slot_width; |
75 | u32 slot_num; |
76 | }; |
77 | |
78 | /** |
79 | * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data |
80 | * @dai_link: DAI link structure including normal one and DPCM link |
81 | * @hp_jack: Headphone Jack structure |
82 | * @mic_jack: Microphone Jack structure |
83 | * @pdev: platform device pointer |
84 | * @codec_priv: CODEC private data |
85 | * @cpu_priv: CPU private data |
86 | * @card: ASoC card structure |
87 | * @streams: Mask of current active streams |
88 | * @sample_rate: Current sample rate |
89 | * @sample_format: Current sample format |
90 | * @asrc_rate: ASRC sample rate used by Back-Ends |
91 | * @asrc_format: ASRC sample format used by Back-Ends |
92 | * @dai_fmt: DAI format between CPU and CODEC |
93 | * @name: Card name |
94 | */ |
95 | |
96 | struct fsl_asoc_card_priv { |
97 | struct snd_soc_dai_link dai_link[3]; |
98 | struct simple_util_jack hp_jack; |
99 | struct simple_util_jack mic_jack; |
100 | struct platform_device *pdev; |
101 | struct codec_priv codec_priv; |
102 | struct cpu_priv cpu_priv; |
103 | struct snd_soc_card card; |
104 | u8 streams; |
105 | u32 sample_rate; |
106 | snd_pcm_format_t sample_format; |
107 | u32 asrc_rate; |
108 | snd_pcm_format_t asrc_format; |
109 | u32 dai_fmt; |
110 | char name[32]; |
111 | }; |
112 | |
113 | /* |
114 | * This dapm route map exists for DPCM link only. |
115 | * The other routes shall go through Device Tree. |
116 | * |
117 | * Note: keep all ASRC routes in the second half |
118 | * to drop them easily for non-ASRC cases. |
119 | */ |
120 | static const struct snd_soc_dapm_route audio_map[] = { |
121 | /* 1st half -- Normal DAPM routes */ |
122 | {"Playback" , NULL, "CPU-Playback" }, |
123 | {"CPU-Capture" , NULL, "Capture" }, |
124 | /* 2nd half -- ASRC DAPM routes */ |
125 | {"CPU-Playback" , NULL, "ASRC-Playback" }, |
126 | {"ASRC-Capture" , NULL, "CPU-Capture" }, |
127 | }; |
128 | |
129 | static const struct snd_soc_dapm_route audio_map_ac97[] = { |
130 | /* 1st half -- Normal DAPM routes */ |
131 | {"AC97 Playback" , NULL, "CPU AC97 Playback" }, |
132 | {"CPU AC97 Capture" , NULL, "AC97 Capture" }, |
133 | /* 2nd half -- ASRC DAPM routes */ |
134 | {"CPU AC97 Playback" , NULL, "ASRC-Playback" }, |
135 | {"ASRC-Capture" , NULL, "CPU AC97 Capture" }, |
136 | }; |
137 | |
138 | static const struct snd_soc_dapm_route audio_map_tx[] = { |
139 | /* 1st half -- Normal DAPM routes */ |
140 | {"Playback" , NULL, "CPU-Playback" }, |
141 | /* 2nd half -- ASRC DAPM routes */ |
142 | {"CPU-Playback" , NULL, "ASRC-Playback" }, |
143 | }; |
144 | |
145 | static const struct snd_soc_dapm_route audio_map_rx[] = { |
146 | /* 1st half -- Normal DAPM routes */ |
147 | {"CPU-Capture" , NULL, "Capture" }, |
148 | /* 2nd half -- ASRC DAPM routes */ |
149 | {"ASRC-Capture" , NULL, "CPU-Capture" }, |
150 | }; |
151 | |
152 | /* Add all possible widgets into here without being redundant */ |
153 | static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = { |
154 | SND_SOC_DAPM_LINE("Line Out Jack" , NULL), |
155 | SND_SOC_DAPM_LINE("Line In Jack" , NULL), |
156 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
157 | SND_SOC_DAPM_SPK("Ext Spk" , NULL), |
158 | SND_SOC_DAPM_MIC("Mic Jack" , NULL), |
159 | SND_SOC_DAPM_MIC("AMIC" , NULL), |
160 | SND_SOC_DAPM_MIC("DMIC" , NULL), |
161 | }; |
162 | |
163 | static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv) |
164 | { |
165 | return priv->dai_fmt == SND_SOC_DAIFMT_AC97; |
166 | } |
167 | |
168 | static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, |
169 | struct snd_pcm_hw_params *params) |
170 | { |
171 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
172 | struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
173 | bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; |
174 | struct codec_priv *codec_priv = &priv->codec_priv; |
175 | struct cpu_priv *cpu_priv = &priv->cpu_priv; |
176 | struct device *dev = rtd->card->dev; |
177 | unsigned int pll_out; |
178 | int ret; |
179 | |
180 | priv->sample_rate = params_rate(p: params); |
181 | priv->sample_format = params_format(p: params); |
182 | priv->streams |= BIT(substream->stream); |
183 | |
184 | if (fsl_asoc_card_is_ac97(priv)) |
185 | return 0; |
186 | |
187 | /* Specific configurations of DAIs starts from here */ |
188 | ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(rtd, 0), clk_id: cpu_priv->sysclk_id[tx], |
189 | freq: cpu_priv->sysclk_freq[tx], |
190 | dir: cpu_priv->sysclk_dir[tx]); |
191 | if (ret && ret != -ENOTSUPP) { |
192 | dev_err(dev, "failed to set sysclk for cpu dai\n" ); |
193 | goto fail; |
194 | } |
195 | |
196 | if (cpu_priv->slot_width) { |
197 | if (!cpu_priv->slot_num) |
198 | cpu_priv->slot_num = 2; |
199 | |
200 | ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), tx_mask: 0x3, rx_mask: 0x3, |
201 | slots: cpu_priv->slot_num, |
202 | slot_width: cpu_priv->slot_width); |
203 | if (ret && ret != -ENOTSUPP) { |
204 | dev_err(dev, "failed to set TDM slot for cpu dai\n" ); |
205 | goto fail; |
206 | } |
207 | } |
208 | |
209 | /* Specific configuration for PLL */ |
210 | if (codec_priv->pll_id >= 0 && codec_priv->fll_id >= 0) { |
211 | if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE) |
212 | pll_out = priv->sample_rate * 384; |
213 | else |
214 | pll_out = priv->sample_rate * 256; |
215 | |
216 | ret = snd_soc_dai_set_pll(snd_soc_rtd_to_codec(rtd, 0), |
217 | pll_id: codec_priv->pll_id, |
218 | source: codec_priv->mclk_id, |
219 | freq_in: codec_priv->mclk_freq, freq_out: pll_out); |
220 | if (ret) { |
221 | dev_err(dev, "failed to start FLL: %d\n" , ret); |
222 | goto fail; |
223 | } |
224 | |
225 | ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), |
226 | clk_id: codec_priv->fll_id, |
227 | freq: pll_out, SND_SOC_CLOCK_IN); |
228 | |
229 | if (ret && ret != -ENOTSUPP) { |
230 | dev_err(dev, "failed to set SYSCLK: %d\n" , ret); |
231 | goto fail; |
232 | } |
233 | } |
234 | |
235 | return 0; |
236 | |
237 | fail: |
238 | priv->streams &= ~BIT(substream->stream); |
239 | return ret; |
240 | } |
241 | |
242 | static int fsl_asoc_card_hw_free(struct snd_pcm_substream *substream) |
243 | { |
244 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
245 | struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
246 | struct codec_priv *codec_priv = &priv->codec_priv; |
247 | struct device *dev = rtd->card->dev; |
248 | int ret; |
249 | |
250 | priv->streams &= ~BIT(substream->stream); |
251 | |
252 | if (!priv->streams && codec_priv->pll_id >= 0 && codec_priv->fll_id >= 0) { |
253 | /* Force freq to be free_freq to avoid error message in codec */ |
254 | ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), |
255 | clk_id: codec_priv->mclk_id, |
256 | freq: codec_priv->free_freq, |
257 | SND_SOC_CLOCK_IN); |
258 | if (ret) { |
259 | dev_err(dev, "failed to switch away from FLL: %d\n" , ret); |
260 | return ret; |
261 | } |
262 | |
263 | ret = snd_soc_dai_set_pll(snd_soc_rtd_to_codec(rtd, 0), |
264 | pll_id: codec_priv->pll_id, source: 0, freq_in: 0, freq_out: 0); |
265 | if (ret && ret != -ENOTSUPP) { |
266 | dev_err(dev, "failed to stop FLL: %d\n" , ret); |
267 | return ret; |
268 | } |
269 | } |
270 | |
271 | return 0; |
272 | } |
273 | |
274 | static const struct snd_soc_ops fsl_asoc_card_ops = { |
275 | .hw_params = fsl_asoc_card_hw_params, |
276 | .hw_free = fsl_asoc_card_hw_free, |
277 | }; |
278 | |
279 | static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, |
280 | struct snd_pcm_hw_params *params) |
281 | { |
282 | struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
283 | struct snd_interval *rate; |
284 | struct snd_mask *mask; |
285 | |
286 | rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); |
287 | rate->max = rate->min = priv->asrc_rate; |
288 | |
289 | mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); |
290 | snd_mask_none(mask); |
291 | snd_mask_set_format(mask, format: priv->asrc_format); |
292 | |
293 | return 0; |
294 | } |
295 | |
296 | SND_SOC_DAILINK_DEFS(hifi, |
297 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
298 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
299 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
300 | |
301 | SND_SOC_DAILINK_DEFS(hifi_fe, |
302 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
303 | DAILINK_COMP_ARRAY(COMP_DUMMY()), |
304 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
305 | |
306 | SND_SOC_DAILINK_DEFS(hifi_be, |
307 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
308 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
309 | |
310 | static const struct snd_soc_dai_link fsl_asoc_card_dai[] = { |
311 | /* Default ASoC DAI Link*/ |
312 | { |
313 | .name = "HiFi" , |
314 | .stream_name = "HiFi" , |
315 | .ops = &fsl_asoc_card_ops, |
316 | SND_SOC_DAILINK_REG(hifi), |
317 | }, |
318 | /* DPCM Link between Front-End and Back-End (Optional) */ |
319 | { |
320 | .name = "HiFi-ASRC-FE" , |
321 | .stream_name = "HiFi-ASRC-FE" , |
322 | .dpcm_playback = 1, |
323 | .dpcm_capture = 1, |
324 | .dynamic = 1, |
325 | SND_SOC_DAILINK_REG(hifi_fe), |
326 | }, |
327 | { |
328 | .name = "HiFi-ASRC-BE" , |
329 | .stream_name = "HiFi-ASRC-BE" , |
330 | .be_hw_params_fixup = be_hw_params_fixup, |
331 | .ops = &fsl_asoc_card_ops, |
332 | .dpcm_playback = 1, |
333 | .dpcm_capture = 1, |
334 | .no_pcm = 1, |
335 | SND_SOC_DAILINK_REG(hifi_be), |
336 | }, |
337 | }; |
338 | |
339 | static int fsl_asoc_card_audmux_init(struct device_node *np, |
340 | struct fsl_asoc_card_priv *priv) |
341 | { |
342 | struct device *dev = &priv->pdev->dev; |
343 | u32 int_ptcr = 0, ext_ptcr = 0; |
344 | int int_port, ext_port; |
345 | int ret; |
346 | |
347 | ret = of_property_read_u32(np, propname: "mux-int-port" , out_value: &int_port); |
348 | if (ret) { |
349 | dev_err(dev, "mux-int-port missing or invalid\n" ); |
350 | return ret; |
351 | } |
352 | ret = of_property_read_u32(np, propname: "mux-ext-port" , out_value: &ext_port); |
353 | if (ret) { |
354 | dev_err(dev, "mux-ext-port missing or invalid\n" ); |
355 | return ret; |
356 | } |
357 | |
358 | /* |
359 | * The port numbering in the hardware manual starts at 1, while |
360 | * the AUDMUX API expects it starts at 0. |
361 | */ |
362 | int_port--; |
363 | ext_port--; |
364 | |
365 | /* |
366 | * Use asynchronous mode (6 wires) for all cases except AC97. |
367 | * If only 4 wires are needed, just set SSI into |
368 | * synchronous mode and enable 4 PADs in IOMUX. |
369 | */ |
370 | switch (priv->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
371 | case SND_SOC_DAIFMT_CBP_CFP: |
372 | int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | |
373 | IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | |
374 | IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | |
375 | IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | |
376 | IMX_AUDMUX_V2_PTCR_RFSDIR | |
377 | IMX_AUDMUX_V2_PTCR_RCLKDIR | |
378 | IMX_AUDMUX_V2_PTCR_TFSDIR | |
379 | IMX_AUDMUX_V2_PTCR_TCLKDIR; |
380 | break; |
381 | case SND_SOC_DAIFMT_CBP_CFC: |
382 | int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | |
383 | IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | |
384 | IMX_AUDMUX_V2_PTCR_RCLKDIR | |
385 | IMX_AUDMUX_V2_PTCR_TCLKDIR; |
386 | ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | |
387 | IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | |
388 | IMX_AUDMUX_V2_PTCR_RFSDIR | |
389 | IMX_AUDMUX_V2_PTCR_TFSDIR; |
390 | break; |
391 | case SND_SOC_DAIFMT_CBC_CFP: |
392 | int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | |
393 | IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | |
394 | IMX_AUDMUX_V2_PTCR_RFSDIR | |
395 | IMX_AUDMUX_V2_PTCR_TFSDIR; |
396 | ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | |
397 | IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | |
398 | IMX_AUDMUX_V2_PTCR_RCLKDIR | |
399 | IMX_AUDMUX_V2_PTCR_TCLKDIR; |
400 | break; |
401 | case SND_SOC_DAIFMT_CBC_CFC: |
402 | ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | |
403 | IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | |
404 | IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | |
405 | IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | |
406 | IMX_AUDMUX_V2_PTCR_RFSDIR | |
407 | IMX_AUDMUX_V2_PTCR_RCLKDIR | |
408 | IMX_AUDMUX_V2_PTCR_TFSDIR | |
409 | IMX_AUDMUX_V2_PTCR_TCLKDIR; |
410 | break; |
411 | default: |
412 | if (!fsl_asoc_card_is_ac97(priv)) |
413 | return -EINVAL; |
414 | } |
415 | |
416 | if (fsl_asoc_card_is_ac97(priv)) { |
417 | int_ptcr = IMX_AUDMUX_V2_PTCR_SYN | |
418 | IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | |
419 | IMX_AUDMUX_V2_PTCR_TCLKDIR; |
420 | ext_ptcr = IMX_AUDMUX_V2_PTCR_SYN | |
421 | IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | |
422 | IMX_AUDMUX_V2_PTCR_TFSDIR; |
423 | } |
424 | |
425 | /* Asynchronous mode can not be set along with RCLKDIR */ |
426 | if (!fsl_asoc_card_is_ac97(priv)) { |
427 | unsigned int pdcr = |
428 | IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port); |
429 | |
430 | ret = imx_audmux_v2_configure_port(port: int_port, ptcr: 0, |
431 | pdcr); |
432 | if (ret) { |
433 | dev_err(dev, "audmux internal port setup failed\n" ); |
434 | return ret; |
435 | } |
436 | } |
437 | |
438 | ret = imx_audmux_v2_configure_port(port: int_port, ptcr: int_ptcr, |
439 | IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); |
440 | if (ret) { |
441 | dev_err(dev, "audmux internal port setup failed\n" ); |
442 | return ret; |
443 | } |
444 | |
445 | if (!fsl_asoc_card_is_ac97(priv)) { |
446 | unsigned int pdcr = |
447 | IMX_AUDMUX_V2_PDCR_RXDSEL(int_port); |
448 | |
449 | ret = imx_audmux_v2_configure_port(port: ext_port, ptcr: 0, |
450 | pdcr); |
451 | if (ret) { |
452 | dev_err(dev, "audmux external port setup failed\n" ); |
453 | return ret; |
454 | } |
455 | } |
456 | |
457 | ret = imx_audmux_v2_configure_port(port: ext_port, ptcr: ext_ptcr, |
458 | IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); |
459 | if (ret) { |
460 | dev_err(dev, "audmux external port setup failed\n" ); |
461 | return ret; |
462 | } |
463 | |
464 | return 0; |
465 | } |
466 | |
467 | static int hp_jack_event(struct notifier_block *nb, unsigned long event, |
468 | void *data) |
469 | { |
470 | struct snd_soc_jack *jack = (struct snd_soc_jack *)data; |
471 | struct snd_soc_dapm_context *dapm = &jack->card->dapm; |
472 | |
473 | if (event & SND_JACK_HEADPHONE) |
474 | /* Disable speaker if headphone is plugged in */ |
475 | return snd_soc_dapm_disable_pin(dapm, pin: "Ext Spk" ); |
476 | else |
477 | return snd_soc_dapm_enable_pin(dapm, pin: "Ext Spk" ); |
478 | } |
479 | |
480 | static struct notifier_block hp_jack_nb = { |
481 | .notifier_call = hp_jack_event, |
482 | }; |
483 | |
484 | static int mic_jack_event(struct notifier_block *nb, unsigned long event, |
485 | void *data) |
486 | { |
487 | struct snd_soc_jack *jack = (struct snd_soc_jack *)data; |
488 | struct snd_soc_dapm_context *dapm = &jack->card->dapm; |
489 | |
490 | if (event & SND_JACK_MICROPHONE) |
491 | /* Disable dmic if microphone is plugged in */ |
492 | return snd_soc_dapm_disable_pin(dapm, pin: "DMIC" ); |
493 | else |
494 | return snd_soc_dapm_enable_pin(dapm, pin: "DMIC" ); |
495 | } |
496 | |
497 | static struct notifier_block mic_jack_nb = { |
498 | .notifier_call = mic_jack_event, |
499 | }; |
500 | |
501 | static int fsl_asoc_card_late_probe(struct snd_soc_card *card) |
502 | { |
503 | struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); |
504 | struct snd_soc_pcm_runtime *rtd = list_first_entry( |
505 | &card->rtd_list, struct snd_soc_pcm_runtime, list); |
506 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
507 | struct codec_priv *codec_priv = &priv->codec_priv; |
508 | struct device *dev = card->dev; |
509 | int ret; |
510 | |
511 | if (fsl_asoc_card_is_ac97(priv)) { |
512 | #if IS_ENABLED(CONFIG_SND_AC97_CODEC) |
513 | struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; |
514 | struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(c: component); |
515 | |
516 | /* |
517 | * Use slots 3/4 for S/PDIF so SSI won't try to enable |
518 | * other slots and send some samples there |
519 | * due to SLOTREQ bits for S/PDIF received from codec |
520 | */ |
521 | snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, |
522 | AC97_EA_SPSA_SLOT_MASK, AC97_EA_SPSA_3_4); |
523 | #endif |
524 | |
525 | return 0; |
526 | } |
527 | |
528 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: codec_priv->mclk_id, |
529 | freq: codec_priv->mclk_freq, SND_SOC_CLOCK_IN); |
530 | if (ret && ret != -ENOTSUPP) { |
531 | dev_err(dev, "failed to set sysclk in %s\n" , __func__); |
532 | return ret; |
533 | } |
534 | |
535 | if (!IS_ERR_OR_NULL(ptr: codec_priv->mclk)) |
536 | clk_prepare_enable(clk: codec_priv->mclk); |
537 | |
538 | return 0; |
539 | } |
540 | |
541 | static int fsl_asoc_card_probe(struct platform_device *pdev) |
542 | { |
543 | struct device_node *cpu_np, *codec_np, *asrc_np; |
544 | struct device_node *np = pdev->dev.of_node; |
545 | struct platform_device *asrc_pdev = NULL; |
546 | struct device_node *bitclkprovider = NULL; |
547 | struct device_node *frameprovider = NULL; |
548 | struct platform_device *cpu_pdev; |
549 | struct fsl_asoc_card_priv *priv; |
550 | struct device *codec_dev = NULL; |
551 | const char *codec_dai_name; |
552 | const char *codec_dev_name; |
553 | u32 asrc_fmt = 0; |
554 | u32 width; |
555 | int ret; |
556 | |
557 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
558 | if (!priv) |
559 | return -ENOMEM; |
560 | |
561 | cpu_np = of_parse_phandle(np, phandle_name: "audio-cpu" , index: 0); |
562 | /* Give a chance to old DT binding */ |
563 | if (!cpu_np) |
564 | cpu_np = of_parse_phandle(np, phandle_name: "ssi-controller" , index: 0); |
565 | if (!cpu_np) { |
566 | dev_err(&pdev->dev, "CPU phandle missing or invalid\n" ); |
567 | ret = -EINVAL; |
568 | goto fail; |
569 | } |
570 | |
571 | cpu_pdev = of_find_device_by_node(np: cpu_np); |
572 | if (!cpu_pdev) { |
573 | dev_err(&pdev->dev, "failed to find CPU DAI device\n" ); |
574 | ret = -EINVAL; |
575 | goto fail; |
576 | } |
577 | |
578 | codec_np = of_parse_phandle(np, phandle_name: "audio-codec" , index: 0); |
579 | if (codec_np) { |
580 | struct platform_device *codec_pdev; |
581 | struct i2c_client *codec_i2c; |
582 | |
583 | codec_i2c = of_find_i2c_device_by_node(node: codec_np); |
584 | if (codec_i2c) { |
585 | codec_dev = &codec_i2c->dev; |
586 | codec_dev_name = codec_i2c->name; |
587 | } |
588 | if (!codec_dev) { |
589 | codec_pdev = of_find_device_by_node(np: codec_np); |
590 | if (codec_pdev) { |
591 | codec_dev = &codec_pdev->dev; |
592 | codec_dev_name = codec_pdev->name; |
593 | } |
594 | } |
595 | } |
596 | |
597 | asrc_np = of_parse_phandle(np, phandle_name: "audio-asrc" , index: 0); |
598 | if (asrc_np) |
599 | asrc_pdev = of_find_device_by_node(np: asrc_np); |
600 | |
601 | /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ |
602 | if (codec_dev) { |
603 | struct clk *codec_clk = clk_get(dev: codec_dev, NULL); |
604 | |
605 | if (!IS_ERR(ptr: codec_clk)) { |
606 | priv->codec_priv.mclk_freq = clk_get_rate(clk: codec_clk); |
607 | clk_put(clk: codec_clk); |
608 | } |
609 | } |
610 | |
611 | /* Default sample rate and format, will be updated in hw_params() */ |
612 | priv->sample_rate = 44100; |
613 | priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; |
614 | |
615 | /* Assign a default DAI format, and allow each card to overwrite it */ |
616 | priv->dai_fmt = DAI_FMT_BASE; |
617 | |
618 | memcpy(priv->dai_link, fsl_asoc_card_dai, |
619 | sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link)); |
620 | |
621 | priv->card.dapm_routes = audio_map; |
622 | priv->card.num_dapm_routes = ARRAY_SIZE(audio_map); |
623 | priv->card.driver_name = DRIVER_NAME; |
624 | |
625 | priv->codec_priv.fll_id = -1; |
626 | priv->codec_priv.pll_id = -1; |
627 | |
628 | /* Diversify the card configurations */ |
629 | if (of_device_is_compatible(device: np, "fsl,imx-audio-cs42888" )) { |
630 | codec_dai_name = "cs42888" ; |
631 | priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq; |
632 | priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq; |
633 | priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; |
634 | priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; |
635 | priv->cpu_priv.slot_width = 32; |
636 | priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; |
637 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-cs427x" )) { |
638 | codec_dai_name = "cs4271-hifi" ; |
639 | priv->codec_priv.mclk_id = CS427x_SYSCLK_MCLK; |
640 | priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
641 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-sgtl5000" )) { |
642 | codec_dai_name = "sgtl5000" ; |
643 | priv->codec_priv.mclk_id = SGTL5000_SYSCLK; |
644 | priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
645 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-tlv320aic32x4" )) { |
646 | codec_dai_name = "tlv320aic32x4-hifi" ; |
647 | priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
648 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-tlv320aic31xx" )) { |
649 | codec_dai_name = "tlv320dac31xx-hifi" ; |
650 | priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; |
651 | priv->dai_link[1].dpcm_capture = 0; |
652 | priv->dai_link[2].dpcm_capture = 0; |
653 | priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; |
654 | priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; |
655 | priv->card.dapm_routes = audio_map_tx; |
656 | priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); |
657 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-wm8962" )) { |
658 | codec_dai_name = "wm8962" ; |
659 | priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK; |
660 | priv->codec_priv.fll_id = WM8962_SYSCLK_FLL; |
661 | priv->codec_priv.pll_id = WM8962_FLL; |
662 | priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
663 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-wm8960" )) { |
664 | codec_dai_name = "wm8960-hifi" ; |
665 | priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO; |
666 | priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO; |
667 | priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
668 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-ac97" )) { |
669 | codec_dai_name = "ac97-hifi" ; |
670 | priv->dai_fmt = SND_SOC_DAIFMT_AC97; |
671 | priv->card.dapm_routes = audio_map_ac97; |
672 | priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); |
673 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-mqs" )) { |
674 | codec_dai_name = "fsl-mqs-dai" ; |
675 | priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | |
676 | SND_SOC_DAIFMT_CBC_CFC | |
677 | SND_SOC_DAIFMT_NB_NF; |
678 | priv->dai_link[1].dpcm_capture = 0; |
679 | priv->dai_link[2].dpcm_capture = 0; |
680 | priv->card.dapm_routes = audio_map_tx; |
681 | priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); |
682 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-wm8524" )) { |
683 | codec_dai_name = "wm8524-hifi" ; |
684 | priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; |
685 | priv->dai_link[1].dpcm_capture = 0; |
686 | priv->dai_link[2].dpcm_capture = 0; |
687 | priv->cpu_priv.slot_width = 32; |
688 | priv->card.dapm_routes = audio_map_tx; |
689 | priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); |
690 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-si476x" )) { |
691 | codec_dai_name = "si476x-codec" ; |
692 | priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; |
693 | priv->card.dapm_routes = audio_map_rx; |
694 | priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); |
695 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-wm8958" )) { |
696 | codec_dai_name = "wm8994-aif1" ; |
697 | priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
698 | priv->codec_priv.mclk_id = WM8994_FLL_SRC_MCLK1; |
699 | priv->codec_priv.fll_id = WM8994_SYSCLK_FLL1; |
700 | priv->codec_priv.pll_id = WM8994_FLL1; |
701 | priv->codec_priv.free_freq = priv->codec_priv.mclk_freq; |
702 | priv->card.dapm_routes = NULL; |
703 | priv->card.num_dapm_routes = 0; |
704 | } else if (of_device_is_compatible(device: np, "fsl,imx-audio-nau8822" )) { |
705 | codec_dai_name = "nau8822-hifi" ; |
706 | priv->codec_priv.mclk_id = NAU8822_CLK_MCLK; |
707 | priv->codec_priv.fll_id = NAU8822_CLK_PLL; |
708 | priv->codec_priv.pll_id = NAU8822_CLK_PLL; |
709 | priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; |
710 | if (codec_dev) |
711 | priv->codec_priv.mclk = devm_clk_get(dev: codec_dev, NULL); |
712 | } else { |
713 | dev_err(&pdev->dev, "unknown Device Tree compatible\n" ); |
714 | ret = -EINVAL; |
715 | goto asrc_fail; |
716 | } |
717 | |
718 | /* |
719 | * Allow setting mclk-id from the device-tree node. Otherwise, the |
720 | * default value for each card configuration is used. |
721 | */ |
722 | of_property_read_u32(np, propname: "mclk-id" , out_value: &priv->codec_priv.mclk_id); |
723 | |
724 | /* Format info from DT is optional. */ |
725 | snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL, bitclkmaster: &bitclkprovider, framemaster: &frameprovider); |
726 | if (bitclkprovider || frameprovider) { |
727 | unsigned int daifmt = snd_soc_daifmt_parse_format(np, NULL); |
728 | |
729 | if (codec_np == bitclkprovider) |
730 | daifmt |= (codec_np == frameprovider) ? |
731 | SND_SOC_DAIFMT_CBP_CFP : SND_SOC_DAIFMT_CBP_CFC; |
732 | else |
733 | daifmt |= (codec_np == frameprovider) ? |
734 | SND_SOC_DAIFMT_CBC_CFP : SND_SOC_DAIFMT_CBC_CFC; |
735 | |
736 | /* Override dai_fmt with value from DT */ |
737 | priv->dai_fmt = daifmt; |
738 | } |
739 | |
740 | /* Change direction according to format */ |
741 | if (priv->dai_fmt & SND_SOC_DAIFMT_CBP_CFP) { |
742 | priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_IN; |
743 | priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_IN; |
744 | } |
745 | |
746 | of_node_put(node: bitclkprovider); |
747 | of_node_put(node: frameprovider); |
748 | |
749 | if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) { |
750 | dev_dbg(&pdev->dev, "failed to find codec device\n" ); |
751 | ret = -EPROBE_DEFER; |
752 | goto asrc_fail; |
753 | } |
754 | |
755 | /* Common settings for corresponding Freescale CPU DAI driver */ |
756 | if (of_node_name_eq(np: cpu_np, name: "ssi" )) { |
757 | /* Only SSI needs to configure AUDMUX */ |
758 | ret = fsl_asoc_card_audmux_init(np, priv); |
759 | if (ret) { |
760 | dev_err(&pdev->dev, "failed to init audmux\n" ); |
761 | goto asrc_fail; |
762 | } |
763 | } else if (of_node_name_eq(np: cpu_np, name: "esai" )) { |
764 | struct clk *esai_clk = clk_get(dev: &cpu_pdev->dev, id: "extal" ); |
765 | |
766 | if (!IS_ERR(ptr: esai_clk)) { |
767 | priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(clk: esai_clk); |
768 | priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(clk: esai_clk); |
769 | clk_put(clk: esai_clk); |
770 | } else if (PTR_ERR(ptr: esai_clk) == -EPROBE_DEFER) { |
771 | ret = -EPROBE_DEFER; |
772 | goto asrc_fail; |
773 | } |
774 | |
775 | priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; |
776 | priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; |
777 | } else if (of_node_name_eq(np: cpu_np, name: "sai" )) { |
778 | priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; |
779 | priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; |
780 | } |
781 | |
782 | /* Initialize sound card */ |
783 | priv->pdev = pdev; |
784 | priv->card.dev = &pdev->dev; |
785 | priv->card.owner = THIS_MODULE; |
786 | ret = snd_soc_of_parse_card_name(card: &priv->card, propname: "model" ); |
787 | if (ret) { |
788 | snprintf(buf: priv->name, size: sizeof(priv->name), fmt: "%s-audio" , |
789 | fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name); |
790 | priv->card.name = priv->name; |
791 | } |
792 | priv->card.dai_link = priv->dai_link; |
793 | priv->card.late_probe = fsl_asoc_card_late_probe; |
794 | priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; |
795 | priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); |
796 | |
797 | /* Drop the second half of DAPM routes -- ASRC */ |
798 | if (!asrc_pdev) |
799 | priv->card.num_dapm_routes /= 2; |
800 | |
801 | if (of_property_read_bool(np, propname: "audio-routing" )) { |
802 | ret = snd_soc_of_parse_audio_routing(card: &priv->card, propname: "audio-routing" ); |
803 | if (ret) { |
804 | dev_err(&pdev->dev, "failed to parse audio-routing: %d\n" , ret); |
805 | goto asrc_fail; |
806 | } |
807 | } |
808 | |
809 | /* Normal DAI Link */ |
810 | priv->dai_link[0].cpus->of_node = cpu_np; |
811 | priv->dai_link[0].codecs->dai_name = codec_dai_name; |
812 | |
813 | if (!fsl_asoc_card_is_ac97(priv)) |
814 | priv->dai_link[0].codecs->of_node = codec_np; |
815 | else { |
816 | u32 idx; |
817 | |
818 | ret = of_property_read_u32(np: cpu_np, propname: "cell-index" , out_value: &idx); |
819 | if (ret) { |
820 | dev_err(&pdev->dev, |
821 | "cannot get CPU index property\n" ); |
822 | goto asrc_fail; |
823 | } |
824 | |
825 | priv->dai_link[0].codecs->name = |
826 | devm_kasprintf(dev: &pdev->dev, GFP_KERNEL, |
827 | fmt: "ac97-codec.%u" , |
828 | (unsigned int)idx); |
829 | if (!priv->dai_link[0].codecs->name) { |
830 | ret = -ENOMEM; |
831 | goto asrc_fail; |
832 | } |
833 | } |
834 | |
835 | priv->dai_link[0].platforms->of_node = cpu_np; |
836 | priv->dai_link[0].dai_fmt = priv->dai_fmt; |
837 | priv->card.num_links = 1; |
838 | |
839 | if (asrc_pdev) { |
840 | /* DPCM DAI Links only if ASRC exists */ |
841 | priv->dai_link[1].cpus->of_node = asrc_np; |
842 | priv->dai_link[1].platforms->of_node = asrc_np; |
843 | priv->dai_link[2].codecs->dai_name = codec_dai_name; |
844 | priv->dai_link[2].codecs->of_node = codec_np; |
845 | priv->dai_link[2].codecs->name = |
846 | priv->dai_link[0].codecs->name; |
847 | priv->dai_link[2].cpus->of_node = cpu_np; |
848 | priv->dai_link[2].dai_fmt = priv->dai_fmt; |
849 | priv->card.num_links = 3; |
850 | |
851 | ret = of_property_read_u32(np: asrc_np, propname: "fsl,asrc-rate" , |
852 | out_value: &priv->asrc_rate); |
853 | if (ret) { |
854 | dev_err(&pdev->dev, "failed to get output rate\n" ); |
855 | ret = -EINVAL; |
856 | goto asrc_fail; |
857 | } |
858 | |
859 | ret = of_property_read_u32(np: asrc_np, propname: "fsl,asrc-format" , out_value: &asrc_fmt); |
860 | priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; |
861 | if (ret) { |
862 | /* Fallback to old binding; translate to asrc_format */ |
863 | ret = of_property_read_u32(np: asrc_np, propname: "fsl,asrc-width" , |
864 | out_value: &width); |
865 | if (ret) { |
866 | dev_err(&pdev->dev, |
867 | "failed to decide output format\n" ); |
868 | goto asrc_fail; |
869 | } |
870 | |
871 | if (width == 24) |
872 | priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; |
873 | else |
874 | priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; |
875 | } |
876 | } |
877 | |
878 | /* Finish card registering */ |
879 | platform_set_drvdata(pdev, data: priv); |
880 | snd_soc_card_set_drvdata(card: &priv->card, data: priv); |
881 | |
882 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card: &priv->card); |
883 | if (ret) { |
884 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "snd_soc_register_card failed\n" ); |
885 | goto asrc_fail; |
886 | } |
887 | |
888 | /* |
889 | * Properties "hp-det-gpio" and "mic-det-gpio" are optional, and |
890 | * simple_util_init_jack() uses these properties for creating |
891 | * Headphone Jack and Microphone Jack. |
892 | * |
893 | * The notifier is initialized in snd_soc_card_jack_new(), then |
894 | * snd_soc_jack_notifier_register can be called. |
895 | */ |
896 | if (of_property_read_bool(np, propname: "hp-det-gpio" )) { |
897 | ret = simple_util_init_jack(card: &priv->card, sjack: &priv->hp_jack, |
898 | is_hp: 1, NULL, pin: "Headphone Jack" ); |
899 | if (ret) |
900 | goto asrc_fail; |
901 | |
902 | snd_soc_jack_notifier_register(jack: &priv->hp_jack.jack, nb: &hp_jack_nb); |
903 | } |
904 | |
905 | if (of_property_read_bool(np, propname: "mic-det-gpio" )) { |
906 | ret = simple_util_init_jack(card: &priv->card, sjack: &priv->mic_jack, |
907 | is_hp: 0, NULL, pin: "Mic Jack" ); |
908 | if (ret) |
909 | goto asrc_fail; |
910 | |
911 | snd_soc_jack_notifier_register(jack: &priv->mic_jack.jack, nb: &mic_jack_nb); |
912 | } |
913 | |
914 | asrc_fail: |
915 | of_node_put(node: asrc_np); |
916 | of_node_put(node: codec_np); |
917 | put_device(dev: &cpu_pdev->dev); |
918 | fail: |
919 | of_node_put(node: cpu_np); |
920 | |
921 | return ret; |
922 | } |
923 | |
924 | static const struct of_device_id fsl_asoc_card_dt_ids[] = { |
925 | { .compatible = "fsl,imx-audio-ac97" , }, |
926 | { .compatible = "fsl,imx-audio-cs42888" , }, |
927 | { .compatible = "fsl,imx-audio-cs427x" , }, |
928 | { .compatible = "fsl,imx-audio-tlv320aic32x4" , }, |
929 | { .compatible = "fsl,imx-audio-tlv320aic31xx" , }, |
930 | { .compatible = "fsl,imx-audio-sgtl5000" , }, |
931 | { .compatible = "fsl,imx-audio-wm8962" , }, |
932 | { .compatible = "fsl,imx-audio-wm8960" , }, |
933 | { .compatible = "fsl,imx-audio-mqs" , }, |
934 | { .compatible = "fsl,imx-audio-wm8524" , }, |
935 | { .compatible = "fsl,imx-audio-si476x" , }, |
936 | { .compatible = "fsl,imx-audio-wm8958" , }, |
937 | { .compatible = "fsl,imx-audio-nau8822" , }, |
938 | {} |
939 | }; |
940 | MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); |
941 | |
942 | static struct platform_driver fsl_asoc_card_driver = { |
943 | .probe = fsl_asoc_card_probe, |
944 | .driver = { |
945 | .name = DRIVER_NAME, |
946 | .pm = &snd_soc_pm_ops, |
947 | .of_match_table = fsl_asoc_card_dt_ids, |
948 | }, |
949 | }; |
950 | module_platform_driver(fsl_asoc_card_driver); |
951 | |
952 | MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC" ); |
953 | MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>" ); |
954 | MODULE_ALIAS("platform:" DRIVER_NAME); |
955 | MODULE_LICENSE("GPL" ); |
956 | |