]>
Commit | Line | Data |
---|---|---|
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 | ||
12 | int 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 | } | |
40 | EXPORT_SYMBOL_GPL(meson_card_i2s_set_sysclk); | |
41 | ||
42 | int 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 | |
66 | err_ldata: | |
67 | kfree(links); | |
68 | err_links: | |
69 | dev_err(priv->card.dev, "failed to allocate links\n"); | |
70 | return -ENOMEM; | |
71 | ||
aa9c3b72 JB |
72 | } |
73 | EXPORT_SYMBOL_GPL(meson_card_reallocate_links); | |
74 | ||
75 | int 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 | } | |
97 | EXPORT_SYMBOL_GPL(meson_card_parse_dai); | |
98 | ||
99 | static 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 | ||
115 | unsigned 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 | } | |
140 | EXPORT_SYMBOL_GPL(meson_card_parse_daifmt); | |
141 | ||
142 | int 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 | } | |
185 | EXPORT_SYMBOL_GPL(meson_card_set_be_link); | |
186 | ||
187 | int 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 | } | |
215 | EXPORT_SYMBOL_GPL(meson_card_set_fe_link); | |
216 | ||
217 | static 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 | ||
248 | static 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 | ||
261 | static 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 | ||
292 | static 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 | ||
318 | int 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 | ||
374 | out_err: | |
375 | meson_card_clean_references(priv); | |
376 | return ret; | |
377 | } | |
378 | EXPORT_SYMBOL_GPL(meson_card_probe); | |
379 | ||
380 | int 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 | } | |
388 | EXPORT_SYMBOL_GPL(meson_card_remove); | |
389 | ||
390 | MODULE_DESCRIPTION("Amlogic Sound Card Utils"); | |
391 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); | |
392 | MODULE_LICENSE("GPL v2"); |