blob: d0543a53764e9377a30c419e56d0c7e09c349e16 [file] [log] [blame]
Fabio Estevamaa624a02018-07-24 09:48:30 -03001// 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>
Nicolin Chen708b4352014-07-30 19:27:38 +08008
9#include <linux/clk.h>
10#include <linux/i2c.h>
11#include <linux/module.h>
12#include <linux/of_platform.h>
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +020013#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
14#include <sound/ac97_codec.h>
15#endif
Nicolin Chen708b4352014-07-30 19:27:38 +080016#include <sound/pcm_params.h>
17#include <sound/soc.h>
18
19#include "fsl_esai.h"
20#include "fsl_sai.h"
21#include "imx-audmux.h"
22
23#include "../codecs/sgtl5000.h"
24#include "../codecs/wm8962.h"
Zidan Wang50e0ee02015-08-14 19:11:09 +080025#include "../codecs/wm8960.h"
Nicolin Chen708b4352014-07-30 19:27:38 +080026
Felipe F. Tonello57e756d2016-01-29 11:01:00 +000027#define CS427x_SYSCLK_MCLK 0
28
Nicolin Chen708b4352014-07-30 19:27:38 +080029#define RX 0
30#define TX 1
31
32/* Default DAI format without Master and Slave flag */
33#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF)
34
35/**
36 * CODEC private data
37 *
38 * @mclk_freq: Clock rate of MCLK
39 * @mclk_id: MCLK (or main clock) id for set_sysclk()
40 * @fll_id: FLL (or secordary clock) id for set_sysclk()
41 * @pll_id: PLL id for set_pll()
42 */
43struct codec_priv {
44 unsigned long mclk_freq;
45 u32 mclk_id;
46 u32 fll_id;
47 u32 pll_id;
48};
49
50/**
51 * CPU private data
52 *
53 * @sysclk_freq[2]: SYSCLK rates for set_sysclk()
54 * @sysclk_dir[2]: SYSCLK directions for set_sysclk()
55 * @sysclk_id[2]: SYSCLK ids for set_sysclk()
Nicolin Chencb3fc1f2014-10-24 16:48:13 -070056 * @slot_width: Slot width of each frame
Nicolin Chen708b4352014-07-30 19:27:38 +080057 *
58 * Note: [1] for tx and [0] for rx
59 */
60struct cpu_priv {
61 unsigned long sysclk_freq[2];
62 u32 sysclk_dir[2];
63 u32 sysclk_id[2];
Nicolin Chencb3fc1f2014-10-24 16:48:13 -070064 u32 slot_width;
Nicolin Chen708b4352014-07-30 19:27:38 +080065};
66
67/**
68 * Freescale Generic ASOC card private data
69 *
70 * @dai_link[3]: DAI link structure including normal one and DPCM link
71 * @pdev: platform device pointer
72 * @codec_priv: CODEC private data
73 * @cpu_priv: CPU private data
74 * @card: ASoC card structure
75 * @sample_rate: Current sample rate
76 * @sample_format: Current sample format
77 * @asrc_rate: ASRC sample rate used by Back-Ends
78 * @asrc_format: ASRC sample format used by Back-Ends
79 * @dai_fmt: DAI format between CPU and CODEC
80 * @name: Card name
81 */
82
83struct fsl_asoc_card_priv {
84 struct snd_soc_dai_link dai_link[3];
85 struct platform_device *pdev;
86 struct codec_priv codec_priv;
87 struct cpu_priv cpu_priv;
88 struct snd_soc_card card;
89 u32 sample_rate;
Fabio Estevamfc734c242018-02-11 19:53:18 -020090 snd_pcm_format_t sample_format;
Nicolin Chen708b4352014-07-30 19:27:38 +080091 u32 asrc_rate;
Fabio Estevamfc734c242018-02-11 19:53:18 -020092 snd_pcm_format_t asrc_format;
Nicolin Chen708b4352014-07-30 19:27:38 +080093 u32 dai_fmt;
94 char name[32];
95};
96
97/**
98 * This dapm route map exsits for DPCM link only.
99 * The other routes shall go through Device Tree.
Nicolin Chen089dfaf2016-01-30 23:07:00 -0800100 *
101 * Note: keep all ASRC routes in the second half
102 * to drop them easily for non-ASRC cases.
Nicolin Chen708b4352014-07-30 19:27:38 +0800103 */
104static const struct snd_soc_dapm_route audio_map[] = {
Nicolin Chen089dfaf2016-01-30 23:07:00 -0800105 /* 1st half -- Normal DAPM routes */
Nicolin Chen708b4352014-07-30 19:27:38 +0800106 {"Playback", NULL, "CPU-Playback"},
Nicolin Chen708b4352014-07-30 19:27:38 +0800107 {"CPU-Capture", NULL, "Capture"},
Nicolin Chen089dfaf2016-01-30 23:07:00 -0800108 /* 2nd half -- ASRC DAPM routes */
109 {"CPU-Playback", NULL, "ASRC-Playback"},
110 {"ASRC-Capture", NULL, "CPU-Capture"},
Nicolin Chen708b4352014-07-30 19:27:38 +0800111};
112
Maciej S. Szmigiero25e5ef92015-12-20 21:34:29 +0100113static const struct snd_soc_dapm_route audio_map_ac97[] = {
Nicolin Chen089dfaf2016-01-30 23:07:00 -0800114 /* 1st half -- Normal DAPM routes */
Maciej S. Szmigiero25e5ef92015-12-20 21:34:29 +0100115 {"Playback", NULL, "AC97 Playback"},
Maciej S. Szmigiero25e5ef92015-12-20 21:34:29 +0100116 {"AC97 Capture", NULL, "Capture"},
Nicolin Chen089dfaf2016-01-30 23:07:00 -0800117 /* 2nd half -- ASRC DAPM routes */
118 {"AC97 Playback", NULL, "ASRC-Playback"},
119 {"ASRC-Capture", NULL, "AC97 Capture"},
Maciej S. Szmigiero25e5ef92015-12-20 21:34:29 +0100120};
121
Shengjiu Wang039652a2020-06-17 12:48:25 +0800122static const struct snd_soc_dapm_route audio_map_tx[] = {
123 /* 1st half -- Normal DAPM routes */
124 {"Playback", NULL, "CPU-Playback"},
125 /* 2nd half -- ASRC DAPM routes */
126 {"CPU-Playback", NULL, "ASRC-Playback"},
127};
128
Nicolin Chen708b4352014-07-30 19:27:38 +0800129/* Add all possible widgets into here without being redundant */
130static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
131 SND_SOC_DAPM_LINE("Line Out Jack", NULL),
132 SND_SOC_DAPM_LINE("Line In Jack", NULL),
133 SND_SOC_DAPM_HP("Headphone Jack", NULL),
134 SND_SOC_DAPM_SPK("Ext Spk", NULL),
135 SND_SOC_DAPM_MIC("Mic Jack", NULL),
136 SND_SOC_DAPM_MIC("AMIC", NULL),
137 SND_SOC_DAPM_MIC("DMIC", NULL),
138};
139
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200140static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv)
141{
142 return priv->dai_fmt == SND_SOC_DAIFMT_AC97;
143}
144
Nicolin Chen708b4352014-07-30 19:27:38 +0800145static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
146 struct snd_pcm_hw_params *params)
147{
148 struct snd_soc_pcm_runtime *rtd = substream->private_data;
149 struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
150 bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
151 struct cpu_priv *cpu_priv = &priv->cpu_priv;
152 struct device *dev = rtd->card->dev;
153 int ret;
154
155 priv->sample_rate = params_rate(params);
156 priv->sample_format = params_format(params);
157
Nicolin Chen41282922014-10-24 16:48:11 -0700158 /*
159 * If codec-dai is DAI Master and all configurations are already in the
160 * set_bias_level(), bypass the remaining settings in hw_params().
161 * Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS.
162 */
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200163 if ((priv->card.set_bias_level &&
164 priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) ||
165 fsl_asoc_card_is_ac97(priv))
Nicolin Chen708b4352014-07-30 19:27:38 +0800166 return 0;
167
168 /* Specific configurations of DAIs starts from here */
Kuninori Morimoto17198ae2020-03-23 14:18:30 +0900169 ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), cpu_priv->sysclk_id[tx],
Nicolin Chen708b4352014-07-30 19:27:38 +0800170 cpu_priv->sysclk_freq[tx],
171 cpu_priv->sysclk_dir[tx]);
Nicolin Chen758a3b02017-09-07 22:27:33 -0700172 if (ret && ret != -ENOTSUPP) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800173 dev_err(dev, "failed to set sysclk for cpu dai\n");
174 return ret;
175 }
176
Nicolin Chencb3fc1f2014-10-24 16:48:13 -0700177 if (cpu_priv->slot_width) {
Kuninori Morimoto17198ae2020-03-23 14:18:30 +0900178 ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2,
Nicolin Chencb3fc1f2014-10-24 16:48:13 -0700179 cpu_priv->slot_width);
Nicolin Chen758a3b02017-09-07 22:27:33 -0700180 if (ret && ret != -ENOTSUPP) {
Nicolin Chencb3fc1f2014-10-24 16:48:13 -0700181 dev_err(dev, "failed to set TDM slot for cpu dai\n");
182 return ret;
183 }
184 }
185
Nicolin Chen708b4352014-07-30 19:27:38 +0800186 return 0;
187}
188
Julia Lawallddba7fa2016-10-15 16:55:50 +0200189static const struct snd_soc_ops fsl_asoc_card_ops = {
Nicolin Chen708b4352014-07-30 19:27:38 +0800190 .hw_params = fsl_asoc_card_hw_params,
191};
192
193static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
194 struct snd_pcm_hw_params *params)
195{
196 struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
197 struct snd_interval *rate;
198 struct snd_mask *mask;
199
200 rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
201 rate->max = rate->min = priv->asrc_rate;
202
203 mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
204 snd_mask_none(mask);
Takashi Iwaiebc22af2018-07-25 23:17:20 +0200205 snd_mask_set_format(mask, priv->asrc_format);
Nicolin Chen708b4352014-07-30 19:27:38 +0800206
207 return 0;
208}
209
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900210SND_SOC_DAILINK_DEFS(hifi,
211 DAILINK_COMP_ARRAY(COMP_EMPTY()),
Kuninori Morimoto9998d3e2019-06-28 10:47:18 +0900212 DAILINK_COMP_ARRAY(COMP_EMPTY()),
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900213 DAILINK_COMP_ARRAY(COMP_EMPTY()));
214
215SND_SOC_DAILINK_DEFS(hifi_fe,
216 DAILINK_COMP_ARRAY(COMP_EMPTY()),
Kuninori Morimoto9998d3e2019-06-28 10:47:18 +0900217 DAILINK_COMP_ARRAY(COMP_DUMMY()),
218 DAILINK_COMP_ARRAY(COMP_EMPTY()));
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900219
220SND_SOC_DAILINK_DEFS(hifi_be,
221 DAILINK_COMP_ARRAY(COMP_EMPTY()),
222 DAILINK_COMP_ARRAY(COMP_EMPTY()),
223 DAILINK_COMP_ARRAY(COMP_DUMMY()));
224
Nicolin Chen708b4352014-07-30 19:27:38 +0800225static struct snd_soc_dai_link fsl_asoc_card_dai[] = {
226 /* Default ASoC DAI Link*/
227 {
228 .name = "HiFi",
229 .stream_name = "HiFi",
230 .ops = &fsl_asoc_card_ops,
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900231 SND_SOC_DAILINK_REG(hifi),
Nicolin Chen708b4352014-07-30 19:27:38 +0800232 },
233 /* DPCM Link between Front-End and Back-End (Optional) */
234 {
235 .name = "HiFi-ASRC-FE",
236 .stream_name = "HiFi-ASRC-FE",
Nicolin Chen708b4352014-07-30 19:27:38 +0800237 .dpcm_playback = 1,
238 .dpcm_capture = 1,
239 .dynamic = 1,
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900240 SND_SOC_DAILINK_REG(hifi_fe),
Nicolin Chen708b4352014-07-30 19:27:38 +0800241 },
242 {
243 .name = "HiFi-ASRC-BE",
244 .stream_name = "HiFi-ASRC-BE",
Nicolin Chen708b4352014-07-30 19:27:38 +0800245 .be_hw_params_fixup = be_hw_params_fixup,
246 .ops = &fsl_asoc_card_ops,
247 .dpcm_playback = 1,
248 .dpcm_capture = 1,
249 .no_pcm = 1,
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900250 SND_SOC_DAILINK_REG(hifi_be),
Nicolin Chen708b4352014-07-30 19:27:38 +0800251 },
252};
253
254static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card,
255 struct snd_soc_dapm_context *dapm,
256 enum snd_soc_bias_level level)
257{
258 struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
Mengdong Lin50159202015-11-18 02:34:01 -0500259 struct snd_soc_pcm_runtime *rtd;
260 struct snd_soc_dai *codec_dai;
Nicolin Chen708b4352014-07-30 19:27:38 +0800261 struct codec_priv *codec_priv = &priv->codec_priv;
262 struct device *dev = card->dev;
263 unsigned int pll_out;
264 int ret;
265
Kuninori Morimoto44681892019-12-10 09:34:08 +0900266 rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
Kuninori Morimoto17198ae2020-03-23 14:18:30 +0900267 codec_dai = asoc_rtd_to_codec(rtd, 0);
Nicolin Chen708b4352014-07-30 19:27:38 +0800268 if (dapm->dev != codec_dai->dev)
269 return 0;
270
271 switch (level) {
272 case SND_SOC_BIAS_PREPARE:
273 if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
274 break;
275
276 if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE)
277 pll_out = priv->sample_rate * 384;
278 else
279 pll_out = priv->sample_rate * 256;
280
281 ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id,
282 codec_priv->mclk_id,
283 codec_priv->mclk_freq, pll_out);
284 if (ret) {
285 dev_err(dev, "failed to start FLL: %d\n", ret);
286 return ret;
287 }
288
289 ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->fll_id,
290 pll_out, SND_SOC_CLOCK_IN);
Nicolin Chen758a3b02017-09-07 22:27:33 -0700291 if (ret && ret != -ENOTSUPP) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800292 dev_err(dev, "failed to set SYSCLK: %d\n", ret);
293 return ret;
294 }
295 break;
296
297 case SND_SOC_BIAS_STANDBY:
298 if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
299 break;
300
301 ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
302 codec_priv->mclk_freq,
303 SND_SOC_CLOCK_IN);
Nicolin Chen758a3b02017-09-07 22:27:33 -0700304 if (ret && ret != -ENOTSUPP) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800305 dev_err(dev, "failed to switch away from FLL: %d\n", ret);
306 return ret;
307 }
308
309 ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0);
310 if (ret) {
311 dev_err(dev, "failed to stop FLL: %d\n", ret);
312 return ret;
313 }
314 break;
315
316 default:
317 break;
318 }
319
320 return 0;
321}
322
323static int fsl_asoc_card_audmux_init(struct device_node *np,
324 struct fsl_asoc_card_priv *priv)
325{
326 struct device *dev = &priv->pdev->dev;
327 u32 int_ptcr = 0, ext_ptcr = 0;
328 int int_port, ext_port;
329 int ret;
330
331 ret = of_property_read_u32(np, "mux-int-port", &int_port);
332 if (ret) {
333 dev_err(dev, "mux-int-port missing or invalid\n");
334 return ret;
335 }
336 ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
337 if (ret) {
338 dev_err(dev, "mux-ext-port missing or invalid\n");
339 return ret;
340 }
341
342 /*
343 * The port numbering in the hardware manual starts at 1, while
344 * the AUDMUX API expects it starts at 0.
345 */
346 int_port--;
347 ext_port--;
348
349 /*
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200350 * Use asynchronous mode (6 wires) for all cases except AC97.
Nicolin Chen708b4352014-07-30 19:27:38 +0800351 * If only 4 wires are needed, just set SSI into
352 * synchronous mode and enable 4 PADs in IOMUX.
353 */
354 switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
355 case SND_SOC_DAIFMT_CBM_CFM:
356 int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
357 IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
358 IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
359 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
360 IMX_AUDMUX_V2_PTCR_RFSDIR |
361 IMX_AUDMUX_V2_PTCR_RCLKDIR |
362 IMX_AUDMUX_V2_PTCR_TFSDIR |
363 IMX_AUDMUX_V2_PTCR_TCLKDIR;
364 break;
365 case SND_SOC_DAIFMT_CBM_CFS:
366 int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
367 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
368 IMX_AUDMUX_V2_PTCR_RCLKDIR |
369 IMX_AUDMUX_V2_PTCR_TCLKDIR;
370 ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
371 IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
372 IMX_AUDMUX_V2_PTCR_RFSDIR |
373 IMX_AUDMUX_V2_PTCR_TFSDIR;
374 break;
375 case SND_SOC_DAIFMT_CBS_CFM:
376 int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
377 IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
378 IMX_AUDMUX_V2_PTCR_RFSDIR |
379 IMX_AUDMUX_V2_PTCR_TFSDIR;
380 ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
381 IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
382 IMX_AUDMUX_V2_PTCR_RCLKDIR |
383 IMX_AUDMUX_V2_PTCR_TCLKDIR;
384 break;
385 case SND_SOC_DAIFMT_CBS_CFS:
386 ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
387 IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
388 IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
389 IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
390 IMX_AUDMUX_V2_PTCR_RFSDIR |
391 IMX_AUDMUX_V2_PTCR_RCLKDIR |
392 IMX_AUDMUX_V2_PTCR_TFSDIR |
393 IMX_AUDMUX_V2_PTCR_TCLKDIR;
394 break;
395 default:
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200396 if (!fsl_asoc_card_is_ac97(priv))
397 return -EINVAL;
398 }
399
400 if (fsl_asoc_card_is_ac97(priv)) {
401 int_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
402 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
403 IMX_AUDMUX_V2_PTCR_TCLKDIR;
404 ext_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
405 IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
406 IMX_AUDMUX_V2_PTCR_TFSDIR;
Nicolin Chen708b4352014-07-30 19:27:38 +0800407 }
408
409 /* Asynchronous mode can not be set along with RCLKDIR */
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200410 if (!fsl_asoc_card_is_ac97(priv)) {
411 unsigned int pdcr =
412 IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port);
413
414 ret = imx_audmux_v2_configure_port(int_port, 0,
415 pdcr);
416 if (ret) {
417 dev_err(dev, "audmux internal port setup failed\n");
418 return ret;
419 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800420 }
421
422 ret = imx_audmux_v2_configure_port(int_port, int_ptcr,
423 IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
424 if (ret) {
425 dev_err(dev, "audmux internal port setup failed\n");
426 return ret;
427 }
428
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200429 if (!fsl_asoc_card_is_ac97(priv)) {
430 unsigned int pdcr =
431 IMX_AUDMUX_V2_PDCR_RXDSEL(int_port);
432
433 ret = imx_audmux_v2_configure_port(ext_port, 0,
434 pdcr);
435 if (ret) {
436 dev_err(dev, "audmux external port setup failed\n");
437 return ret;
438 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800439 }
440
441 ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr,
442 IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
443 if (ret) {
444 dev_err(dev, "audmux external port setup failed\n");
445 return ret;
446 }
447
448 return 0;
449}
450
451static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
452{
453 struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
Vinod Koulabd657b2015-11-20 22:45:20 +0530454 struct snd_soc_pcm_runtime *rtd = list_first_entry(
455 &card->rtd_list, struct snd_soc_pcm_runtime, list);
Kuninori Morimoto17198ae2020-03-23 14:18:30 +0900456 struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
Nicolin Chen708b4352014-07-30 19:27:38 +0800457 struct codec_priv *codec_priv = &priv->codec_priv;
458 struct device *dev = card->dev;
459 int ret;
460
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200461 if (fsl_asoc_card_is_ac97(priv)) {
462#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
Kuninori Morimoto17198ae2020-03-23 14:18:30 +0900463 struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
Kuninori Morimoto845f80c2017-12-05 04:23:21 +0000464 struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200465
466 /*
467 * Use slots 3/4 for S/PDIF so SSI won't try to enable
468 * other slots and send some samples there
469 * due to SLOTREQ bits for S/PDIF received from codec
470 */
471 snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
472 AC97_EA_SPSA_SLOT_MASK, AC97_EA_SPSA_3_4);
473#endif
474
475 return 0;
476 }
477
Nicolin Chen708b4352014-07-30 19:27:38 +0800478 ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
479 codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
Nicolin Chen758a3b02017-09-07 22:27:33 -0700480 if (ret && ret != -ENOTSUPP) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800481 dev_err(dev, "failed to set sysclk in %s\n", __func__);
482 return ret;
483 }
484
485 return 0;
486}
487
488static int fsl_asoc_card_probe(struct platform_device *pdev)
489{
490 struct device_node *cpu_np, *codec_np, *asrc_np;
491 struct device_node *np = pdev->dev.of_node;
492 struct platform_device *asrc_pdev = NULL;
493 struct platform_device *cpu_pdev;
494 struct fsl_asoc_card_priv *priv;
Shengjiu Wang039652a2020-06-17 12:48:25 +0800495 struct device *codec_dev = NULL;
Nicolin Chen114bb132015-08-12 13:06:12 -0700496 const char *codec_dai_name;
Shengjiu Wang039652a2020-06-17 12:48:25 +0800497 const char *codec_dev_name;
Nicolin Chen708b4352014-07-30 19:27:38 +0800498 u32 width;
499 int ret;
500
501 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
502 if (!priv)
503 return -ENOMEM;
504
505 cpu_np = of_parse_phandle(np, "audio-cpu", 0);
506 /* Give a chance to old DT binding */
507 if (!cpu_np)
508 cpu_np = of_parse_phandle(np, "ssi-controller", 0);
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200509 if (!cpu_np) {
510 dev_err(&pdev->dev, "CPU phandle missing or invalid\n");
Nicolin Chen708b4352014-07-30 19:27:38 +0800511 ret = -EINVAL;
512 goto fail;
513 }
514
515 cpu_pdev = of_find_device_by_node(cpu_np);
516 if (!cpu_pdev) {
517 dev_err(&pdev->dev, "failed to find CPU DAI device\n");
518 ret = -EINVAL;
519 goto fail;
520 }
521
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200522 codec_np = of_parse_phandle(np, "audio-codec", 0);
Shengjiu Wang039652a2020-06-17 12:48:25 +0800523 if (codec_np) {
524 struct platform_device *codec_pdev;
525 struct i2c_client *codec_i2c;
526
527 codec_i2c = of_find_i2c_device_by_node(codec_np);
528 if (codec_i2c) {
529 codec_dev = &codec_i2c->dev;
530 codec_dev_name = codec_i2c->name;
531 }
532 if (!codec_dev) {
533 codec_pdev = of_find_device_by_node(codec_np);
534 if (codec_pdev) {
535 codec_dev = &codec_pdev->dev;
536 codec_dev_name = codec_pdev->name;
537 }
538 }
539 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800540
541 asrc_np = of_parse_phandle(np, "audio-asrc", 0);
542 if (asrc_np)
543 asrc_pdev = of_find_device_by_node(asrc_np);
544
545 /* Get the MCLK rate only, and leave it controlled by CODEC drivers */
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200546 if (codec_dev) {
Shengjiu Wang039652a2020-06-17 12:48:25 +0800547 struct clk *codec_clk = clk_get(codec_dev, NULL);
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200548
549 if (!IS_ERR(codec_clk)) {
550 priv->codec_priv.mclk_freq = clk_get_rate(codec_clk);
551 clk_put(codec_clk);
552 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800553 }
554
555 /* Default sample rate and format, will be updated in hw_params() */
556 priv->sample_rate = 44100;
557 priv->sample_format = SNDRV_PCM_FORMAT_S16_LE;
558
559 /* Assign a default DAI format, and allow each card to overwrite it */
560 priv->dai_fmt = DAI_FMT_BASE;
561
Shengjiu Wang039652a2020-06-17 12:48:25 +0800562 memcpy(priv->dai_link, fsl_asoc_card_dai,
563 sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link));
564
565 priv->card.dapm_routes = audio_map;
566 priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
Nicolin Chen708b4352014-07-30 19:27:38 +0800567 /* Diversify the card configurations */
568 if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
Nicolin Chen114bb132015-08-12 13:06:12 -0700569 codec_dai_name = "cs42888";
Nicolin Chen708b4352014-07-30 19:27:38 +0800570 priv->card.set_bias_level = NULL;
571 priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq;
572 priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq;
573 priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT;
574 priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT;
Nicolin Chencb3fc1f2014-10-24 16:48:13 -0700575 priv->cpu_priv.slot_width = 32;
Nicolin Chen708b4352014-07-30 19:27:38 +0800576 priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
Felipe F. Tonello57e756d2016-01-29 11:01:00 +0000577 } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) {
578 codec_dai_name = "cs4271-hifi";
579 priv->codec_priv.mclk_id = CS427x_SYSCLK_MCLK;
580 priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
Nicolin Chen708b4352014-07-30 19:27:38 +0800581 } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) {
Nicolin Chen114bb132015-08-12 13:06:12 -0700582 codec_dai_name = "sgtl5000";
Nicolin Chen708b4352014-07-30 19:27:38 +0800583 priv->codec_priv.mclk_id = SGTL5000_SYSCLK;
584 priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
585 } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) {
Nicolin Chen114bb132015-08-12 13:06:12 -0700586 codec_dai_name = "wm8962";
Nicolin Chen708b4352014-07-30 19:27:38 +0800587 priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
588 priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK;
589 priv->codec_priv.fll_id = WM8962_SYSCLK_FLL;
590 priv->codec_priv.pll_id = WM8962_FLL;
591 priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
Zidan Wang50e0ee02015-08-14 19:11:09 +0800592 } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) {
593 codec_dai_name = "wm8960-hifi";
594 priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
595 priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO;
596 priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO;
597 priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200598 } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) {
599 codec_dai_name = "ac97-hifi";
600 priv->card.set_bias_level = NULL;
601 priv->dai_fmt = SND_SOC_DAIFMT_AC97;
Shengjiu Wang039652a2020-06-17 12:48:25 +0800602 priv->card.dapm_routes = audio_map_ac97;
603 priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97);
604 } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) {
605 codec_dai_name = "fsl-mqs-dai";
606 priv->card.set_bias_level = NULL;
607 priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J |
608 SND_SOC_DAIFMT_CBS_CFS |
609 SND_SOC_DAIFMT_NB_NF;
610 priv->dai_link[1].dpcm_capture = 0;
611 priv->dai_link[2].dpcm_capture = 0;
612 priv->card.dapm_routes = audio_map_tx;
613 priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx);
Nicolin Chen708b4352014-07-30 19:27:38 +0800614 } else {
615 dev_err(&pdev->dev, "unknown Device Tree compatible\n");
Maciej S. Szmigiero6bd3c6f2015-08-31 17:07:12 +0200616 ret = -EINVAL;
617 goto asrc_fail;
Nicolin Chen708b4352014-07-30 19:27:38 +0800618 }
619
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200620 if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) {
621 dev_err(&pdev->dev, "failed to find codec device\n");
Shengjiu Wange396dec2020-06-04 14:25:30 +0800622 ret = -EPROBE_DEFER;
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200623 goto asrc_fail;
Nicolin Chen708b4352014-07-30 19:27:38 +0800624 }
625
626 /* Common settings for corresponding Freescale CPU DAI driver */
Rob Herring1d52a742018-12-05 13:50:49 -0600627 if (of_node_name_eq(cpu_np, "ssi")) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800628 /* Only SSI needs to configure AUDMUX */
629 ret = fsl_asoc_card_audmux_init(np, priv);
630 if (ret) {
631 dev_err(&pdev->dev, "failed to init audmux\n");
Shengjiu Wang5f376712014-08-18 16:38:39 +0800632 goto asrc_fail;
Nicolin Chen708b4352014-07-30 19:27:38 +0800633 }
Rob Herring1d52a742018-12-05 13:50:49 -0600634 } else if (of_node_name_eq(cpu_np, "esai")) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800635 priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL;
636 priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL;
Rob Herring1d52a742018-12-05 13:50:49 -0600637 } else if (of_node_name_eq(cpu_np, "sai")) {
Nicolin Chen708b4352014-07-30 19:27:38 +0800638 priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
639 priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
640 }
641
Nicolin Chen708b4352014-07-30 19:27:38 +0800642 /* Initialize sound card */
643 priv->pdev = pdev;
644 priv->card.dev = &pdev->dev;
Shengjiu Wang039652a2020-06-17 12:48:25 +0800645 ret = snd_soc_of_parse_card_name(&priv->card, "model");
646 if (ret) {
647 snprintf(priv->name, sizeof(priv->name), "%s-audio",
648 fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name);
649 priv->card.name = priv->name;
650 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800651 priv->card.dai_link = priv->dai_link;
Nicolin Chen708b4352014-07-30 19:27:38 +0800652 priv->card.late_probe = fsl_asoc_card_late_probe;
Nicolin Chen708b4352014-07-30 19:27:38 +0800653 priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets;
654 priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets);
655
Nicolin Chen089dfaf2016-01-30 23:07:00 -0800656 /* Drop the second half of DAPM routes -- ASRC */
657 if (!asrc_pdev)
658 priv->card.num_dapm_routes /= 2;
659
Shengjiu Wang039652a2020-06-17 12:48:25 +0800660 if (of_property_read_bool(np, "audio-routing")) {
661 ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing");
662 if (ret) {
663 dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret);
664 goto asrc_fail;
665 }
Nicolin Chen31858782015-02-14 17:22:50 -0800666 }
667
Nicolin Chen708b4352014-07-30 19:27:38 +0800668 /* Normal DAI Link */
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900669 priv->dai_link[0].cpus->of_node = cpu_np;
670 priv->dai_link[0].codecs->dai_name = codec_dai_name;
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200671
672 if (!fsl_asoc_card_is_ac97(priv))
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900673 priv->dai_link[0].codecs->of_node = codec_np;
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200674 else {
675 u32 idx;
676
677 ret = of_property_read_u32(cpu_np, "cell-index", &idx);
678 if (ret) {
679 dev_err(&pdev->dev,
680 "cannot get CPU index property\n");
681 goto asrc_fail;
682 }
683
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900684 priv->dai_link[0].codecs->name =
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200685 devm_kasprintf(&pdev->dev, GFP_KERNEL,
686 "ac97-codec.%u",
687 (unsigned int)idx);
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900688 if (!priv->dai_link[0].codecs->name) {
Arvind Yadav7add71b2017-09-21 10:50:03 +0530689 ret = -ENOMEM;
690 goto asrc_fail;
691 }
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200692 }
693
Kuninori Morimoto9998d3e2019-06-28 10:47:18 +0900694 priv->dai_link[0].platforms->of_node = cpu_np;
Nicolin Chen708b4352014-07-30 19:27:38 +0800695 priv->dai_link[0].dai_fmt = priv->dai_fmt;
696 priv->card.num_links = 1;
697
698 if (asrc_pdev) {
699 /* DPCM DAI Links only if ASRC exsits */
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900700 priv->dai_link[1].cpus->of_node = asrc_np;
Kuninori Morimoto9998d3e2019-06-28 10:47:18 +0900701 priv->dai_link[1].platforms->of_node = asrc_np;
Kuninori Morimoto893f1952019-06-06 13:15:11 +0900702 priv->dai_link[2].codecs->dai_name = codec_dai_name;
703 priv->dai_link[2].codecs->of_node = codec_np;
704 priv->dai_link[2].codecs->name =
705 priv->dai_link[0].codecs->name;
706 priv->dai_link[2].cpus->of_node = cpu_np;
Nicolin Chen708b4352014-07-30 19:27:38 +0800707 priv->dai_link[2].dai_fmt = priv->dai_fmt;
708 priv->card.num_links = 3;
709
710 ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
711 &priv->asrc_rate);
712 if (ret) {
713 dev_err(&pdev->dev, "failed to get output rate\n");
714 ret = -EINVAL;
Shengjiu Wang5f376712014-08-18 16:38:39 +0800715 goto asrc_fail;
Nicolin Chen708b4352014-07-30 19:27:38 +0800716 }
717
Shengjiu Wang859e3642020-04-16 20:25:33 +0800718 ret = of_property_read_u32(asrc_np, "fsl,asrc-format",
719 &priv->asrc_format);
Nicolin Chen708b4352014-07-30 19:27:38 +0800720 if (ret) {
Shengjiu Wang859e3642020-04-16 20:25:33 +0800721 /* Fallback to old binding; translate to asrc_format */
722 ret = of_property_read_u32(asrc_np, "fsl,asrc-width",
723 &width);
724 if (ret) {
725 dev_err(&pdev->dev,
726 "failed to decide output format\n");
727 goto asrc_fail;
728 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800729
Shengjiu Wang859e3642020-04-16 20:25:33 +0800730 if (width == 24)
731 priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
732 else
733 priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
734 }
Nicolin Chen708b4352014-07-30 19:27:38 +0800735 }
736
737 /* Finish card registering */
738 platform_set_drvdata(pdev, priv);
739 snd_soc_card_set_drvdata(&priv->card, priv);
740
741 ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
Lucas Stache5d619e2017-08-18 18:33:35 +0200742 if (ret && ret != -EPROBE_DEFER)
Nicolin Chen708b4352014-07-30 19:27:38 +0800743 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
744
Shengjiu Wang5f376712014-08-18 16:38:39 +0800745asrc_fail:
746 of_node_put(asrc_np);
Nicolin Chen708b4352014-07-30 19:27:38 +0800747 of_node_put(codec_np);
wen yang11907e9d2019-02-02 14:53:16 +0000748 put_device(&cpu_pdev->dev);
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200749fail:
Nicolin Chen708b4352014-07-30 19:27:38 +0800750 of_node_put(cpu_np);
751
752 return ret;
753}
754
755static const struct of_device_id fsl_asoc_card_dt_ids[] = {
Maciej S. Szmigiero50760ca2015-09-19 02:00:25 +0200756 { .compatible = "fsl,imx-audio-ac97", },
Nicolin Chen708b4352014-07-30 19:27:38 +0800757 { .compatible = "fsl,imx-audio-cs42888", },
Felipe F. Tonello57e756d2016-01-29 11:01:00 +0000758 { .compatible = "fsl,imx-audio-cs427x", },
Nicolin Chen708b4352014-07-30 19:27:38 +0800759 { .compatible = "fsl,imx-audio-sgtl5000", },
760 { .compatible = "fsl,imx-audio-wm8962", },
Zidan Wang50e0ee02015-08-14 19:11:09 +0800761 { .compatible = "fsl,imx-audio-wm8960", },
Shengjiu Wang039652a2020-06-17 12:48:25 +0800762 { .compatible = "fsl,imx-audio-mqs", },
Nicolin Chen708b4352014-07-30 19:27:38 +0800763 {}
764};
Luis de Bethencourt5226f232015-09-03 12:57:47 +0200765MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids);
Nicolin Chen708b4352014-07-30 19:27:38 +0800766
767static struct platform_driver fsl_asoc_card_driver = {
768 .probe = fsl_asoc_card_probe,
769 .driver = {
770 .name = "fsl-asoc-card",
771 .pm = &snd_soc_pm_ops,
772 .of_match_table = fsl_asoc_card_dt_ids,
773 },
774};
775module_platform_driver(fsl_asoc_card_driver);
776
777MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC");
778MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
779MODULE_ALIAS("platform:fsl-asoc-card");
780MODULE_LICENSE("GPL");