]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blame - sound/soc/meson/meson-card-utils.c
ASoC: meson: fix memory leak of links if allocation of ldata fails
[mirror_ubuntu-jammy-kernel.git] / sound / soc / meson / meson-card-utils.c
CommitLineData
aa9c3b72
JB
1// SPDX-License-Identifier: GPL-2.0
2//
3// Copyright (c) 2020 BayLibre, SAS.
4// Author: Jerome Brunet <jbrunet@baylibre.com>
5
6#include <linux/module.h>
7#include <linux/of_platform.h>
8#include <sound/soc.h>
9
10#include "meson-card.h"
11
12int meson_card_i2s_set_sysclk(struct snd_pcm_substream *substream,
13 struct snd_pcm_hw_params *params,
14 unsigned int mclk_fs)
15{
16 struct snd_soc_pcm_runtime *rtd = substream->private_data;
17 struct snd_soc_dai *codec_dai;
18 unsigned int mclk;
19 int ret, i;
20
21 if (!mclk_fs)
22 return 0;
23
24 mclk = params_rate(params) * mclk_fs;
25
b5c52f58 26 for_each_rtd_codec_dais(rtd, i, codec_dai) {
aa9c3b72
JB
27 ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
28 SND_SOC_CLOCK_IN);
29 if (ret && ret != -ENOTSUPP)
30 return ret;
31 }
32
385a5c60 33 ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), 0, mclk,
aa9c3b72
JB
34 SND_SOC_CLOCK_OUT);
35 if (ret && ret != -ENOTSUPP)
36 return ret;
37
38 return 0;
39}
40EXPORT_SYMBOL_GPL(meson_card_i2s_set_sysclk);
41
42int meson_card_reallocate_links(struct snd_soc_card *card,
43 unsigned int num_links)
44{
45 struct meson_card *priv = snd_soc_card_get_drvdata(card);
46 struct snd_soc_dai_link *links;
47 void **ldata;
48
49 links = krealloc(priv->card.dai_link,
50 num_links * sizeof(*priv->card.dai_link),
51 GFP_KERNEL | __GFP_ZERO);
6e801dc4
CIK
52 if (!links)
53 goto err_links;
54
aa9c3b72
JB
55 ldata = krealloc(priv->link_data,
56 num_links * sizeof(*priv->link_data),
57 GFP_KERNEL | __GFP_ZERO);
6e801dc4
CIK
58 if (!ldata)
59 goto err_ldata;
aa9c3b72
JB
60
61 priv->card.dai_link = links;
62 priv->link_data = ldata;
63 priv->card.num_links = num_links;
64 return 0;
6e801dc4
CIK
65
66err_ldata:
67 kfree(links);
68err_links:
69 dev_err(priv->card.dev, "failed to allocate links\n");
70 return -ENOMEM;
71
aa9c3b72
JB
72}
73EXPORT_SYMBOL_GPL(meson_card_reallocate_links);
74
75int meson_card_parse_dai(struct snd_soc_card *card,
76 struct device_node *node,
77 struct device_node **dai_of_node,
78 const char **dai_name)
79{
80 struct of_phandle_args args;
81 int ret;
82
83 if (!dai_name || !dai_of_node || !node)
84 return -EINVAL;
85
86 ret = of_parse_phandle_with_args(node, "sound-dai",
87 "#sound-dai-cells", 0, &args);
88 if (ret) {
89 if (ret != -EPROBE_DEFER)
90 dev_err(card->dev, "can't parse dai %d\n", ret);
91 return ret;
92 }
93 *dai_of_node = args.np;
94
95 return snd_soc_get_dai_name(&args, dai_name);
96}
97EXPORT_SYMBOL_GPL(meson_card_parse_dai);
98
99static int meson_card_set_link_name(struct snd_soc_card *card,
100 struct snd_soc_dai_link *link,
101 struct device_node *node,
102 const char *prefix)
103{
104 char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s",
105 prefix, node->full_name);
106 if (!name)
107 return -ENOMEM;
108
109 link->name = name;
110 link->stream_name = name;
111
112 return 0;
113}
114
115unsigned int meson_card_parse_daifmt(struct device_node *node,
116 struct device_node *cpu_node)
117{
118 struct device_node *bitclkmaster = NULL;
119 struct device_node *framemaster = NULL;
120 unsigned int daifmt;
121
122 daifmt = snd_soc_of_parse_daifmt(node, DT_PREFIX,
123 &bitclkmaster, &framemaster);
124 daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
125
126 /* If no master is provided, default to cpu master */
127 if (!bitclkmaster || bitclkmaster == cpu_node) {
128 daifmt |= (!framemaster || framemaster == cpu_node) ?
129 SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM;
130 } else {
131 daifmt |= (!framemaster || framemaster == cpu_node) ?
132 SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM;
133 }
134
135 of_node_put(bitclkmaster);
136 of_node_put(framemaster);
137
138 return daifmt;
139}
140EXPORT_SYMBOL_GPL(meson_card_parse_daifmt);
141
142int meson_card_set_be_link(struct snd_soc_card *card,
143 struct snd_soc_dai_link *link,
144 struct device_node *node)
145{
146 struct snd_soc_dai_link_component *codec;
147 struct device_node *np;
148 int ret, num_codecs;
149
150 link->no_pcm = 1;
151 link->dpcm_playback = 1;
152 link->dpcm_capture = 1;
153
154 num_codecs = of_get_child_count(node);
155 if (!num_codecs) {
156 dev_err(card->dev, "be link %s has no codec\n",
157 node->full_name);
158 return -EINVAL;
159 }
160
161 codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL);
162 if (!codec)
163 return -ENOMEM;
164
165 link->codecs = codec;
166 link->num_codecs = num_codecs;
167
168 for_each_child_of_node(node, np) {
169 ret = meson_card_parse_dai(card, np, &codec->of_node,
170 &codec->dai_name);
171 if (ret) {
172 of_node_put(np);
173 return ret;
174 }
175
176 codec++;
177 }
178
179 ret = meson_card_set_link_name(card, link, node, "be");
180 if (ret)
181 dev_err(card->dev, "error setting %pOFn link name\n", np);
182
183 return ret;
184}
185EXPORT_SYMBOL_GPL(meson_card_set_be_link);
186
187int meson_card_set_fe_link(struct snd_soc_card *card,
188 struct snd_soc_dai_link *link,
189 struct device_node *node,
190 bool is_playback)
191{
192 struct snd_soc_dai_link_component *codec;
193
194 codec = devm_kzalloc(card->dev, sizeof(*codec), GFP_KERNEL);
195 if (!codec)
196 return -ENOMEM;
197
198 link->codecs = codec;
199 link->num_codecs = 1;
200
201 link->dynamic = 1;
202 link->dpcm_merged_format = 1;
203 link->dpcm_merged_chan = 1;
204 link->dpcm_merged_rate = 1;
205 link->codecs->dai_name = "snd-soc-dummy-dai";
206 link->codecs->name = "snd-soc-dummy";
207
208 if (is_playback)
209 link->dpcm_playback = 1;
210 else
211 link->dpcm_capture = 1;
212
213 return meson_card_set_link_name(card, link, node, "fe");
214}
215EXPORT_SYMBOL_GPL(meson_card_set_fe_link);
216
217static int meson_card_add_links(struct snd_soc_card *card)
218{
219 struct meson_card *priv = snd_soc_card_get_drvdata(card);
220 struct device_node *node = card->dev->of_node;
221 struct device_node *np;
222 int num, i, ret;
223
224 num = of_get_child_count(node);
225 if (!num) {
226 dev_err(card->dev, "card has no links\n");
227 return -EINVAL;
228 }
229
230 ret = meson_card_reallocate_links(card, num);
231 if (ret)
232 return ret;
233
234 i = 0;
235 for_each_child_of_node(node, np) {
236 ret = priv->match_data->add_link(card, np, &i);
237 if (ret) {
238 of_node_put(np);
239 return ret;
240 }
241
242 i++;
243 }
244
245 return 0;
246}
247
248static int meson_card_parse_of_optional(struct snd_soc_card *card,
249 const char *propname,
250 int (*func)(struct snd_soc_card *c,
251 const char *p))
252{
253 /* If property is not provided, don't fail ... */
254 if (!of_property_read_bool(card->dev->of_node, propname))
255 return 0;
256
257 /* ... but do fail if it is provided and the parsing fails */
258 return func(card, propname);
259}
260
261static int meson_card_add_aux_devices(struct snd_soc_card *card)
262{
263 struct device_node *node = card->dev->of_node;
264 struct snd_soc_aux_dev *aux;
265 int num, i;
266
267 num = of_count_phandle_with_args(node, "audio-aux-devs", NULL);
268 if (num == -ENOENT) {
269 return 0;
270 } else if (num < 0) {
271 dev_err(card->dev, "error getting auxiliary devices: %d\n",
272 num);
273 return num;
274 }
275
276 aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL);
277 if (!aux)
278 return -ENOMEM;
279 card->aux_dev = aux;
280 card->num_aux_devs = num;
281
282 for_each_card_pre_auxs(card, i, aux) {
283 aux->dlc.of_node =
284 of_parse_phandle(node, "audio-aux-devs", i);
285 if (!aux->dlc.of_node)
286 return -EINVAL;
287 }
288
289 return 0;
290}
291
292static void meson_card_clean_references(struct meson_card *priv)
293{
294 struct snd_soc_card *card = &priv->card;
295 struct snd_soc_dai_link *link;
296 struct snd_soc_dai_link_component *codec;
297 struct snd_soc_aux_dev *aux;
298 int i, j;
299
300 if (card->dai_link) {
301 for_each_card_prelinks(card, i, link) {
302 if (link->cpus)
303 of_node_put(link->cpus->of_node);
304 for_each_link_codecs(link, j, codec)
305 of_node_put(codec->of_node);
306 }
307 }
308
309 if (card->aux_dev) {
310 for_each_card_pre_auxs(card, i, aux)
311 of_node_put(aux->dlc.of_node);
312 }
313
314 kfree(card->dai_link);
315 kfree(priv->link_data);
316}
317
318int meson_card_probe(struct platform_device *pdev)
319{
320 const struct meson_card_match_data *data;
321 struct device *dev = &pdev->dev;
322 struct meson_card *priv;
323 int ret;
324
325 data = of_device_get_match_data(dev);
326 if (!data) {
327 dev_err(dev, "failed to match device\n");
328 return -ENODEV;
329 }
330
331 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
332 if (!priv)
333 return -ENOMEM;
334
335 platform_set_drvdata(pdev, priv);
336 snd_soc_card_set_drvdata(&priv->card, priv);
337
338 priv->card.owner = THIS_MODULE;
339 priv->card.dev = dev;
340 priv->match_data = data;
341
342 ret = snd_soc_of_parse_card_name(&priv->card, "model");
343 if (ret < 0)
344 return ret;
345
346 ret = meson_card_parse_of_optional(&priv->card, "audio-routing",
347 snd_soc_of_parse_audio_routing);
348 if (ret) {
349 dev_err(dev, "error while parsing routing\n");
350 return ret;
351 }
352
353 ret = meson_card_parse_of_optional(&priv->card, "audio-widgets",
354 snd_soc_of_parse_audio_simple_widgets);
355 if (ret) {
356 dev_err(dev, "error while parsing widgets\n");
357 return ret;
358 }
359
360 ret = meson_card_add_links(&priv->card);
361 if (ret)
362 goto out_err;
363
364 ret = meson_card_add_aux_devices(&priv->card);
365 if (ret)
366 goto out_err;
367
368 ret = devm_snd_soc_register_card(dev, &priv->card);
369 if (ret)
370 goto out_err;
371
372 return 0;
373
374out_err:
375 meson_card_clean_references(priv);
376 return ret;
377}
378EXPORT_SYMBOL_GPL(meson_card_probe);
379
380int meson_card_remove(struct platform_device *pdev)
381{
382 struct meson_card *priv = platform_get_drvdata(pdev);
383
384 meson_card_clean_references(priv);
385
386 return 0;
387}
388EXPORT_SYMBOL_GPL(meson_card_remove);
389
390MODULE_DESCRIPTION("Amlogic Sound Card Utils");
391MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
392MODULE_LICENSE("GPL v2");