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