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