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