1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright 2017 NXP
4 *
5 * The code contained herein is licensed under the GNU General Public
6 * License. You may obtain a copy of the GNU General Public License
7 * Version 2 or later at the following locations:
8 *
9 * https://www.opensource.org/licenses/gpl-license.html
10 * https://www.gnu.org/copyleft/gpl.html
11 */
12
13 #include <linux/module.h>
14 #include <linux/of_platform.h>
15 #include <linux/clk.h>
16 #include <sound/soc.h>
17 #include <sound/soc-dapm.h>
18 #include "fsl_sai.h"
19 #include "fsl_audmix.h"
20
21 struct imx_audmix {
22 struct platform_device *pdev;
23 struct snd_soc_card card;
24 struct platform_device *audmix_pdev;
25 struct platform_device *out_pdev;
26 int num_dai;
27 struct snd_soc_dai_link *dai;
28 int num_dai_conf;
29 struct snd_soc_codec_conf *dai_conf;
30 int num_dapm_routes;
31 struct snd_soc_dapm_route *dapm_routes;
32 };
33
imx_audmix_fe_startup(struct snd_pcm_substream * substream)34 static int imx_audmix_fe_startup(struct snd_pcm_substream *substream)
35 {
36 struct snd_pcm_runtime *runtime = substream->runtime;
37 int ret;
38
39 ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
40 1, 8);
41 if (ret < 0)
42 return ret;
43
44 return snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT,
45 FSL_AUDMIX_FORMATS);
46 }
47
imx_audmix_fe_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)48 static int imx_audmix_fe_hw_params(struct snd_pcm_substream *substream,
49 struct snd_pcm_hw_params *params)
50 {
51 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
52 struct device *dev = rtd->card->dev;
53 bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
54 unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF;
55 u32 channels = params_channels(params);
56 int ret, dir;
57
58 /* For playback the AUDMIX is consumer, and for record is provider */
59 fmt |= tx ? SND_SOC_DAIFMT_BP_FP : SND_SOC_DAIFMT_BC_FC;
60 dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
61
62 /* set DAI configuration */
63 ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0), fmt);
64 if (ret) {
65 dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
66 return ret;
67 }
68
69 ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(rtd, 0), FSL_SAI_CLK_MAST1, 0, dir);
70 if (ret) {
71 dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
72 return ret;
73 }
74
75 /*
76 * Per datasheet, AUDMIX expects 8 slots and 32 bits
77 * for every slot in TDM mode.
78 */
79 ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), BIT(channels) - 1,
80 BIT(channels) - 1, 8, 32);
81 if (ret)
82 dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
83
84 return ret;
85 }
86
imx_audmix_be_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)87 static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream,
88 struct snd_pcm_hw_params *params)
89 {
90 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
91 struct device *dev = rtd->card->dev;
92 bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
93 unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF;
94 int ret;
95
96 if (!tx)
97 return 0;
98
99 /* For playback the AUDMIX is consumer */
100 fmt |= SND_SOC_DAIFMT_BC_FC;
101
102 /* set AUDMIX DAI configuration */
103 ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0), fmt);
104 if (ret)
105 dev_err(dev, "failed to set AUDMIX DAI fmt: %d\n", ret);
106
107 return ret;
108 }
109
110 static const struct snd_soc_ops imx_audmix_fe_ops = {
111 .startup = imx_audmix_fe_startup,
112 .hw_params = imx_audmix_fe_hw_params,
113 };
114
115 static const struct snd_soc_ops imx_audmix_be_ops = {
116 .hw_params = imx_audmix_be_hw_params,
117 };
118
119 static const char *name[][3] = {
120 {"HiFi-AUDMIX-FE-0", "HiFi-AUDMIX-FE-1", "HiFi-AUDMIX-FE-2"},
121 {"sai-tx", "sai-tx", "sai-rx"},
122 {"AUDMIX-Playback-0", "AUDMIX-Playback-1", "SAI-Capture"},
123 {"SAI-Playback", "SAI-Playback", "AUDMIX-Capture-0"},
124 };
125
imx_audmix_probe(struct platform_device * pdev)126 static int imx_audmix_probe(struct platform_device *pdev)
127 {
128 struct device_node *np = pdev->dev.of_node;
129 struct device_node *audmix_np = NULL, *out_cpu_np = NULL;
130 struct platform_device *audmix_pdev = NULL;
131 struct platform_device *cpu_pdev;
132 struct of_phandle_args args;
133 struct imx_audmix *priv;
134 int i, num_dai, ret;
135 const char *fe_name_pref = "HiFi-AUDMIX-FE-";
136 char *be_name, *dai_name;
137
138 if (pdev->dev.parent) {
139 audmix_np = pdev->dev.parent->of_node;
140 } else {
141 dev_err(&pdev->dev, "Missing parent device.\n");
142 return -EINVAL;
143 }
144
145 if (!audmix_np) {
146 dev_err(&pdev->dev, "Missing DT node for parent device.\n");
147 return -EINVAL;
148 }
149
150 audmix_pdev = of_find_device_by_node(audmix_np);
151 if (!audmix_pdev) {
152 dev_err(&pdev->dev, "Missing AUDMIX platform device for %s\n",
153 np->full_name);
154 return -EINVAL;
155 }
156 put_device(&audmix_pdev->dev);
157
158 num_dai = of_count_phandle_with_args(audmix_np, "dais", NULL);
159 if (num_dai != FSL_AUDMIX_MAX_DAIS) {
160 dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n",
161 audmix_np->full_name);
162 return -EINVAL;
163 }
164
165 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
166 if (!priv)
167 return -ENOMEM;
168
169 num_dai += 1;
170 priv->num_dai = 2 * num_dai;
171 priv->dai = devm_kcalloc(&pdev->dev, priv->num_dai,
172 sizeof(struct snd_soc_dai_link), GFP_KERNEL);
173 if (!priv->dai)
174 return -ENOMEM;
175
176 priv->num_dai_conf = num_dai;
177 priv->dai_conf = devm_kcalloc(&pdev->dev, priv->num_dai_conf,
178 sizeof(struct snd_soc_codec_conf),
179 GFP_KERNEL);
180 if (!priv->dai_conf)
181 return -ENOMEM;
182
183 priv->num_dapm_routes = num_dai;
184 priv->dapm_routes = devm_kcalloc(&pdev->dev, priv->num_dapm_routes,
185 sizeof(struct snd_soc_dapm_route),
186 GFP_KERNEL);
187 if (!priv->dapm_routes)
188 return -ENOMEM;
189
190 for (i = 0; i < num_dai; i++) {
191 struct snd_soc_dai_link_component *dlc;
192
193 /* for CPU x 2 */
194 dlc = devm_kcalloc(&pdev->dev, 2, sizeof(*dlc), GFP_KERNEL);
195 if (!dlc)
196 return -ENOMEM;
197
198 if (i == num_dai - 1)
199 ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, 0,
200 &args);
201 else
202 ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, i,
203 &args);
204 if (ret < 0) {
205 dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n");
206 return ret;
207 }
208
209 cpu_pdev = of_find_device_by_node(args.np);
210 if (!cpu_pdev) {
211 dev_err(&pdev->dev, "failed to find SAI platform device\n");
212 return -EINVAL;
213 }
214 put_device(&cpu_pdev->dev);
215
216 dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s",
217 fe_name_pref, args.np->full_name);
218 if (!dai_name)
219 return -ENOMEM;
220
221 dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name);
222
223 if (i == num_dai - 1)
224 out_cpu_np = args.np;
225
226 /*
227 * CPU == Platform
228 * platform is using soc-generic-dmaengine-pcm
229 */
230 priv->dai[i].cpus =
231 priv->dai[i].platforms = &dlc[0];
232 priv->dai[i].codecs = &snd_soc_dummy_dlc;
233
234 priv->dai[i].num_cpus = 1;
235 priv->dai[i].num_codecs = 1;
236 priv->dai[i].num_platforms = 1;
237 priv->dai[i].name = name[0][i];
238 priv->dai[i].stream_name = "HiFi-AUDMIX-FE";
239 priv->dai[i].cpus->of_node = args.np;
240 priv->dai[i].cpus->dai_name = name[1][i];
241
242 priv->dai[i].dynamic = 1;
243 if (i == num_dai - 1)
244 priv->dai[i].capture_only = 1;
245 else
246 priv->dai[i].playback_only = 1;
247 priv->dai[i].ignore_pmdown_time = 1;
248 priv->dai[i].ops = &imx_audmix_fe_ops;
249
250 /* Add AUDMIX Backend */
251 be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
252 "audmix-%d", i);
253 if (!be_name)
254 return -ENOMEM;
255
256 priv->dai[num_dai + i].cpus = &dlc[1];
257 priv->dai[num_dai + i].codecs = &snd_soc_dummy_dlc;
258
259 priv->dai[num_dai + i].num_cpus = 1;
260 priv->dai[num_dai + i].num_codecs = 1;
261
262 priv->dai[num_dai + i].name = be_name;
263 priv->dai[num_dai + i].cpus->of_node = audmix_np;
264 priv->dai[num_dai + i].cpus->dai_name = be_name;
265 priv->dai[num_dai + i].no_pcm = 1;
266 if (i == num_dai - 1)
267 priv->dai[num_dai + i].capture_only = 1;
268 else
269 priv->dai[num_dai + i].playback_only = 1;
270 priv->dai[num_dai + i].ignore_pmdown_time = 1;
271 priv->dai[num_dai + i].ops = &imx_audmix_be_ops;
272
273 priv->dai_conf[i].dlc.of_node = args.np;
274 priv->dai_conf[i].name_prefix = dai_name;
275
276 if (i == num_dai - 1) {
277 priv->dapm_routes[i].sink =
278 devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
279 dai_name, name[2][i]);
280 if (!priv->dapm_routes[i].sink)
281 return -ENOMEM;
282
283 priv->dapm_routes[i].source = name[3][i];
284 } else {
285 priv->dapm_routes[i].source =
286 devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
287 dai_name, name[3][i]);
288 if (!priv->dapm_routes[i].source)
289 return -ENOMEM;
290
291 priv->dapm_routes[i].sink = name[2][i];
292 }
293 }
294
295 cpu_pdev = of_find_device_by_node(out_cpu_np);
296 if (!cpu_pdev) {
297 dev_err(&pdev->dev, "failed to find SAI platform device\n");
298 return -EINVAL;
299 }
300 put_device(&cpu_pdev->dev);
301
302 priv->audmix_pdev = audmix_pdev;
303 priv->out_pdev = cpu_pdev;
304
305 priv->card.dai_link = priv->dai;
306 priv->card.num_links = priv->num_dai;
307 priv->card.codec_conf = priv->dai_conf;
308 priv->card.num_configs = priv->num_dai_conf;
309 priv->card.dapm_routes = priv->dapm_routes;
310 priv->card.num_dapm_routes = priv->num_dapm_routes;
311 priv->card.dev = &pdev->dev;
312 priv->card.owner = THIS_MODULE;
313 priv->card.name = "imx-audmix";
314
315 platform_set_drvdata(pdev, &priv->card);
316 snd_soc_card_set_drvdata(&priv->card, priv);
317
318 ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
319 if (ret) {
320 dev_err(&pdev->dev, "snd_soc_register_card failed\n");
321 return ret;
322 }
323
324 return ret;
325 }
326
327 static struct platform_driver imx_audmix_driver = {
328 .probe = imx_audmix_probe,
329 .driver = {
330 .name = "imx-audmix",
331 .pm = &snd_soc_pm_ops,
332 },
333 };
334 module_platform_driver(imx_audmix_driver);
335
336 MODULE_DESCRIPTION("NXP AUDMIX ASoC machine driver");
337 MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
338 MODULE_ALIAS("platform:imx-audmix");
339 MODULE_LICENSE("GPL v2");
340