xref: /openbmc/linux/sound/soc/fsl/imx-audmix.c (revision 7f904d7e1f3ec7c2de47c024a5a5c30988b54703)
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) {
164  		dev_err(&pdev->dev, "Missing DT node for parent device.\n");
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  	}
174  	put_device(&audmix_pdev->dev);
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++) {
208  		ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, i,
209  						 &args);
210  		if (ret < 0) {
211  			dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n");
212  			return ret;
213  		}
214  
215  		cpu_pdev = of_find_device_by_node(args.np);
216  		if (!cpu_pdev) {
217  			dev_err(&pdev->dev, "failed to find SAI platform device\n");
218  			return -EINVAL;
219  		}
220  		put_device(&cpu_pdev->dev);
221  
222  		dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s",
223  					  fe_name_pref, args.np->full_name + 1);
224  
225  		dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name);
226  
227  		if (i == 0) {
228  			out_cpu_np = args.np;
229  			capture_dai_name =
230  				devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
231  					       dai_name, "CPU-Capture");
232  		}
233  
234  		priv->dai[i].name = dai_name;
235  		priv->dai[i].stream_name = "HiFi-AUDMIX-FE";
236  		priv->dai[i].codec_dai_name = "snd-soc-dummy-dai";
237  		priv->dai[i].codec_name = "snd-soc-dummy";
238  		priv->dai[i].cpu_of_node = args.np;
239  		priv->dai[i].cpu_dai_name = dev_name(&cpu_pdev->dev);
240  		priv->dai[i].platform_of_node = args.np;
241  		priv->dai[i].dynamic = 1;
242  		priv->dai[i].dpcm_playback = 1;
243  		priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0);
244  		priv->dai[i].ignore_pmdown_time = 1;
245  		priv->dai[i].ops = &imx_audmix_fe_ops;
246  
247  		/* Add AUDMIX Backend */
248  		be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
249  					 "audmix-%d", i);
250  		be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL,
251  				       "AUDMIX-Playback-%d", i);
252  		be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL,
253  				       "AUDMIX-Capture-%d", i);
254  
255  		priv->dai[num_dai + i].name = be_name;
256  		priv->dai[num_dai + i].codec_dai_name = "snd-soc-dummy-dai";
257  		priv->dai[num_dai + i].codec_name = "snd-soc-dummy";
258  		priv->dai[num_dai + i].cpu_of_node = audmix_np;
259  		priv->dai[num_dai + i].cpu_dai_name = be_name;
260  		priv->dai[num_dai + i].platform_name = "snd-soc-dummy";
261  		priv->dai[num_dai + i].no_pcm = 1;
262  		priv->dai[num_dai + i].dpcm_playback = 1;
263  		priv->dai[num_dai + i].dpcm_capture  = 1;
264  		priv->dai[num_dai + i].ignore_pmdown_time = 1;
265  		priv->dai[num_dai + i].ops = &imx_audmix_be_ops;
266  
267  		priv->dai_conf[i].of_node = args.np;
268  		priv->dai_conf[i].name_prefix = dai_name;
269  
270  		priv->dapm_routes[i].source =
271  			devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
272  				       dai_name, "CPU-Playback");
273  		priv->dapm_routes[i].sink = be_pb;
274  		priv->dapm_routes[num_dai + i].source   = be_pb;
275  		priv->dapm_routes[num_dai + i].sink     = be_cp;
276  		priv->dapm_routes[2 * num_dai + i].source = be_cp;
277  		priv->dapm_routes[2 * num_dai + i].sink   = capture_dai_name;
278  	}
279  
280  	cpu_pdev = of_find_device_by_node(out_cpu_np);
281  	if (!cpu_pdev) {
282  		dev_err(&pdev->dev, "failed to find SAI platform device\n");
283  		return -EINVAL;
284  	}
285  	put_device(&cpu_pdev->dev);
286  
287  	priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1");
288  	if (IS_ERR(priv->cpu_mclk)) {
289  		ret = PTR_ERR(priv->cpu_mclk);
290  		dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret);
291  		return -EINVAL;
292  	}
293  
294  	priv->audmix_pdev = audmix_pdev;
295  	priv->out_pdev  = cpu_pdev;
296  
297  	priv->card.dai_link = priv->dai;
298  	priv->card.num_links = priv->num_dai;
299  	priv->card.codec_conf = priv->dai_conf;
300  	priv->card.num_configs = priv->num_dai_conf;
301  	priv->card.dapm_routes = priv->dapm_routes;
302  	priv->card.num_dapm_routes = priv->num_dapm_routes;
303  	priv->card.dev = pdev->dev.parent;
304  	priv->card.owner = THIS_MODULE;
305  	priv->card.name = "imx-audmix";
306  
307  	platform_set_drvdata(pdev, &priv->card);
308  	snd_soc_card_set_drvdata(&priv->card, priv);
309  
310  	ret = devm_snd_soc_register_card(pdev->dev.parent, &priv->card);
311  	if (ret) {
312  		dev_err(&pdev->dev, "snd_soc_register_card failed\n");
313  		return ret;
314  	}
315  
316  	return ret;
317  }
318  
319  static struct platform_driver imx_audmix_driver = {
320  	.probe = imx_audmix_probe,
321  	.driver = {
322  		.name = "imx-audmix",
323  		.pm = &snd_soc_pm_ops,
324  	},
325  };
326  module_platform_driver(imx_audmix_driver);
327  
328  MODULE_DESCRIPTION("NXP AUDMIX ASoC machine driver");
329  MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
330  MODULE_ALIAS("platform:imx-audmix");
331  MODULE_LICENSE("GPL v2");
332