]>
Commit | Line | Data |
---|---|---|
abd3147e | 1 | /* |
29a43aa9 | 2 | * simple-card-utils.c |
abd3147e KM |
3 | * |
4 | * Copyright (c) 2016 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
bb6fc620 | 10 | #include <linux/clk.h> |
1f85e118 | 11 | #include <linux/module.h> |
abd3147e | 12 | #include <linux/of.h> |
1689333f | 13 | #include <linux/of_graph.h> |
abd3147e KM |
14 | #include <sound/simple_card_utils.h> |
15 | ||
13bb1cc0 KM |
16 | void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, |
17 | struct snd_pcm_hw_params *params) | |
18 | { | |
19 | struct snd_interval *rate = hw_param_interval(params, | |
20 | SNDRV_PCM_HW_PARAM_RATE); | |
21 | struct snd_interval *channels = hw_param_interval(params, | |
22 | SNDRV_PCM_HW_PARAM_CHANNELS); | |
23 | ||
24 | if (data->convert_rate) | |
25 | rate->min = | |
26 | rate->max = data->convert_rate; | |
27 | ||
28 | if (data->convert_channels) | |
29 | channels->min = | |
30 | channels->max = data->convert_channels; | |
31 | } | |
32 | EXPORT_SYMBOL_GPL(asoc_simple_card_convert_fixup); | |
33 | ||
34 | void asoc_simple_card_parse_convert(struct device *dev, char *prefix, | |
35 | struct asoc_simple_card_data *data) | |
36 | { | |
37 | struct device_node *np = dev->of_node; | |
38 | char prop[128]; | |
39 | ||
40 | if (!prefix) | |
41 | prefix = ""; | |
42 | ||
43 | /* sampling rate convert */ | |
44 | snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-rate"); | |
45 | of_property_read_u32(np, prop, &data->convert_rate); | |
46 | ||
47 | /* channels transfer */ | |
48 | snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-channels"); | |
49 | of_property_read_u32(np, prop, &data->convert_channels); | |
50 | ||
51 | dev_dbg(dev, "convert_rate %d\n", data->convert_rate); | |
52 | dev_dbg(dev, "convert_channels %d\n", data->convert_channels); | |
53 | } | |
54 | EXPORT_SYMBOL_GPL(asoc_simple_card_parse_convert); | |
55 | ||
abd3147e KM |
56 | int asoc_simple_card_parse_daifmt(struct device *dev, |
57 | struct device_node *node, | |
58 | struct device_node *codec, | |
59 | char *prefix, | |
60 | unsigned int *retfmt) | |
61 | { | |
62 | struct device_node *bitclkmaster = NULL; | |
63 | struct device_node *framemaster = NULL; | |
abd3147e KM |
64 | unsigned int daifmt; |
65 | ||
66 | daifmt = snd_soc_of_parse_daifmt(node, prefix, | |
67 | &bitclkmaster, &framemaster); | |
68 | daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; | |
69 | ||
155b8f3a | 70 | if (!bitclkmaster && !framemaster) { |
abd3147e KM |
71 | /* |
72 | * No dai-link level and master setting was not found from | |
73 | * sound node level, revert back to legacy DT parsing and | |
74 | * take the settings from codec node. | |
75 | */ | |
76 | dev_dbg(dev, "Revert to legacy daifmt parsing\n"); | |
77 | ||
78 | daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | | |
79 | (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); | |
80 | } else { | |
81 | if (codec == bitclkmaster) | |
82 | daifmt |= (codec == framemaster) ? | |
83 | SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; | |
84 | else | |
85 | daifmt |= (codec == framemaster) ? | |
86 | SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; | |
87 | } | |
88 | ||
89 | of_node_put(bitclkmaster); | |
90 | of_node_put(framemaster); | |
91 | ||
92 | *retfmt = daifmt; | |
93 | ||
aaad9c13 KM |
94 | dev_dbg(dev, "format : %04x\n", daifmt); |
95 | ||
abd3147e KM |
96 | return 0; |
97 | } | |
98 | EXPORT_SYMBOL_GPL(asoc_simple_card_parse_daifmt); | |
1db3312e KM |
99 | |
100 | int asoc_simple_card_set_dailink_name(struct device *dev, | |
101 | struct snd_soc_dai_link *dai_link, | |
102 | const char *fmt, ...) | |
103 | { | |
104 | va_list ap; | |
105 | char *name = NULL; | |
106 | int ret = -ENOMEM; | |
107 | ||
108 | va_start(ap, fmt); | |
109 | name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap); | |
110 | va_end(ap); | |
111 | ||
112 | if (name) { | |
113 | ret = 0; | |
114 | ||
115 | dai_link->name = name; | |
116 | dai_link->stream_name = name; | |
4579771e KM |
117 | |
118 | dev_dbg(dev, "name : %s\n", name); | |
1db3312e KM |
119 | } |
120 | ||
121 | return ret; | |
122 | } | |
123 | EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name); | |
fc55c9b5 KM |
124 | |
125 | int asoc_simple_card_parse_card_name(struct snd_soc_card *card, | |
126 | char *prefix) | |
127 | { | |
fc55c9b5 KM |
128 | int ret; |
129 | ||
dedfaa1e KM |
130 | if (!prefix) |
131 | prefix = ""; | |
fc55c9b5 KM |
132 | |
133 | /* Parse the card name from DT */ | |
dedfaa1e | 134 | ret = snd_soc_of_parse_card_name(card, "label"); |
1b4a56cd | 135 | if (ret < 0 || !card->name) { |
dedfaa1e KM |
136 | char prop[128]; |
137 | ||
138 | snprintf(prop, sizeof(prop), "%sname", prefix); | |
139 | ret = snd_soc_of_parse_card_name(card, prop); | |
140 | if (ret < 0) | |
141 | return ret; | |
142 | } | |
fc55c9b5 KM |
143 | |
144 | if (!card->name && card->dai_link) | |
145 | card->name = card->dai_link->name; | |
146 | ||
d4dbcb63 KM |
147 | dev_dbg(card->dev, "Card Name: %s\n", card->name ? card->name : ""); |
148 | ||
fc55c9b5 KM |
149 | return 0; |
150 | } | |
151 | EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); | |
1f85e118 | 152 | |
891caea4 KM |
153 | static void asoc_simple_card_clk_register(struct asoc_simple_dai *dai, |
154 | struct clk *clk) | |
155 | { | |
156 | dai->clk = clk; | |
157 | } | |
158 | ||
159 | int asoc_simple_card_clk_enable(struct asoc_simple_dai *dai) | |
160 | { | |
161 | return clk_prepare_enable(dai->clk); | |
162 | } | |
63a5f592 | 163 | EXPORT_SYMBOL_GPL(asoc_simple_card_clk_enable); |
891caea4 KM |
164 | |
165 | void asoc_simple_card_clk_disable(struct asoc_simple_dai *dai) | |
166 | { | |
167 | clk_disable_unprepare(dai->clk); | |
168 | } | |
63a5f592 | 169 | EXPORT_SYMBOL_GPL(asoc_simple_card_clk_disable); |
891caea4 | 170 | |
e984fd61 KM |
171 | int asoc_simple_card_parse_clk(struct device *dev, |
172 | struct device_node *node, | |
bb6fc620 | 173 | struct device_node *dai_of_node, |
8e166382 KM |
174 | struct asoc_simple_dai *simple_dai, |
175 | const char *name) | |
bb6fc620 KM |
176 | { |
177 | struct clk *clk; | |
178 | u32 val; | |
179 | ||
180 | /* | |
181 | * Parse dai->sysclk come from "clocks = <&xxx>" | |
182 | * (if system has common clock) | |
183 | * or "system-clock-frequency = <xxx>" | |
184 | * or device's module clock. | |
185 | */ | |
e984fd61 | 186 | clk = devm_get_clk_from_child(dev, node, NULL); |
bb6fc620 KM |
187 | if (!IS_ERR(clk)) { |
188 | simple_dai->sysclk = clk_get_rate(clk); | |
891caea4 KM |
189 | |
190 | asoc_simple_card_clk_register(simple_dai, clk); | |
bb6fc620 KM |
191 | } else if (!of_property_read_u32(node, "system-clock-frequency", &val)) { |
192 | simple_dai->sysclk = val; | |
193 | } else { | |
e984fd61 | 194 | clk = devm_get_clk_from_child(dev, dai_of_node, NULL); |
bb6fc620 KM |
195 | if (!IS_ERR(clk)) |
196 | simple_dai->sysclk = clk_get_rate(clk); | |
197 | } | |
198 | ||
8e166382 KM |
199 | dev_dbg(dev, "%s : sysclk = %d\n", name, simple_dai->sysclk); |
200 | ||
bb6fc620 KM |
201 | return 0; |
202 | } | |
203 | EXPORT_SYMBOL_GPL(asoc_simple_card_parse_clk); | |
204 | ||
ae30a694 KM |
205 | int asoc_simple_card_parse_dai(struct device_node *node, |
206 | struct device_node **dai_of_node, | |
207 | const char **dai_name, | |
208 | const char *list_name, | |
209 | const char *cells_name, | |
210 | int *is_single_link) | |
211 | { | |
212 | struct of_phandle_args args; | |
213 | int ret; | |
214 | ||
215 | if (!node) | |
216 | return 0; | |
217 | ||
218 | /* | |
219 | * Get node via "sound-dai = <&phandle port>" | |
220 | * it will be used as xxx_of_node on soc_bind_dai_link() | |
221 | */ | |
222 | ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args); | |
223 | if (ret) | |
224 | return ret; | |
225 | ||
226 | /* Get dai->name */ | |
227 | if (dai_name) { | |
228 | ret = snd_soc_of_get_dai_name(node, dai_name); | |
229 | if (ret < 0) | |
230 | return ret; | |
231 | } | |
232 | ||
233 | *dai_of_node = args.np; | |
234 | ||
235 | if (is_single_link) | |
236 | *is_single_link = !args.args_count; | |
237 | ||
238 | return 0; | |
239 | } | |
240 | EXPORT_SYMBOL_GPL(asoc_simple_card_parse_dai); | |
241 | ||
1689333f KM |
242 | static int asoc_simple_card_get_dai_id(struct device_node *ep) |
243 | { | |
244 | struct device_node *node; | |
245 | struct device_node *endpoint; | |
246 | int i, id; | |
73b17f1a KM |
247 | int ret; |
248 | ||
249 | ret = snd_soc_get_dai_id(ep); | |
250 | if (ret != -ENOTSUPP) | |
251 | return ret; | |
1689333f KM |
252 | |
253 | node = of_graph_get_port_parent(ep); | |
254 | ||
73b17f1a KM |
255 | /* |
256 | * Non HDMI sound case, counting port/endpoint on its DT | |
257 | * is enough. Let's count it. | |
258 | */ | |
1689333f KM |
259 | i = 0; |
260 | id = -1; | |
261 | for_each_endpoint_of_node(node, endpoint) { | |
262 | if (endpoint == ep) | |
263 | id = i; | |
264 | i++; | |
265 | } | |
c0a480d1 TL |
266 | |
267 | of_node_put(node); | |
268 | ||
1689333f KM |
269 | if (id < 0) |
270 | return -ENODEV; | |
271 | ||
272 | return id; | |
273 | } | |
274 | ||
275 | int asoc_simple_card_parse_graph_dai(struct device_node *ep, | |
276 | struct device_node **dai_of_node, | |
277 | const char **dai_name) | |
278 | { | |
279 | struct device_node *node; | |
280 | struct of_phandle_args args; | |
281 | int ret; | |
282 | ||
283 | if (!ep) | |
284 | return 0; | |
285 | if (!dai_name) | |
286 | return 0; | |
287 | ||
1689333f KM |
288 | node = of_graph_get_port_parent(ep); |
289 | ||
290 | /* Get dai->name */ | |
291 | args.np = node; | |
292 | args.args[0] = asoc_simple_card_get_dai_id(ep); | |
293 | args.args_count = (of_graph_get_endpoint_count(node) > 1); | |
294 | ||
295 | ret = snd_soc_get_dai_name(&args, dai_name); | |
296 | if (ret < 0) | |
297 | return ret; | |
298 | ||
299 | *dai_of_node = node; | |
300 | ||
301 | return 0; | |
302 | } | |
303 | EXPORT_SYMBOL_GPL(asoc_simple_card_parse_graph_dai); | |
304 | ||
21ba62f8 KM |
305 | int asoc_simple_card_init_dai(struct snd_soc_dai *dai, |
306 | struct asoc_simple_dai *simple_dai) | |
307 | { | |
308 | int ret; | |
309 | ||
310 | if (simple_dai->sysclk) { | |
311 | ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk, 0); | |
312 | if (ret && ret != -ENOTSUPP) { | |
313 | dev_err(dai->dev, "simple-card: set_sysclk error\n"); | |
314 | return ret; | |
315 | } | |
316 | } | |
317 | ||
318 | if (simple_dai->slots) { | |
319 | ret = snd_soc_dai_set_tdm_slot(dai, | |
320 | simple_dai->tx_slot_mask, | |
321 | simple_dai->rx_slot_mask, | |
322 | simple_dai->slots, | |
323 | simple_dai->slot_width); | |
324 | if (ret && ret != -ENOTSUPP) { | |
325 | dev_err(dai->dev, "simple-card: set_tdm_slot error\n"); | |
326 | return ret; | |
327 | } | |
328 | } | |
329 | ||
330 | return 0; | |
331 | } | |
332 | EXPORT_SYMBOL_GPL(asoc_simple_card_init_dai); | |
333 | ||
c262c9ab KM |
334 | int asoc_simple_card_canonicalize_dailink(struct snd_soc_dai_link *dai_link) |
335 | { | |
c262c9ab KM |
336 | /* Assumes platform == cpu */ |
337 | if (!dai_link->platform_of_node) | |
338 | dai_link->platform_of_node = dai_link->cpu_of_node; | |
339 | ||
340 | return 0; | |
341 | } | |
342 | EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_dailink); | |
343 | ||
983cebd6 KM |
344 | void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, |
345 | int is_single_links) | |
346 | { | |
347 | /* | |
348 | * In soc_bind_dai_link() will check cpu name after | |
349 | * of_node matching if dai_link has cpu_dai_name. | |
350 | * but, it will never match if name was created by | |
351 | * fmt_single_name() remove cpu_dai_name if cpu_args | |
352 | * was 0. See: | |
353 | * fmt_single_name() | |
354 | * fmt_multiple_name() | |
355 | */ | |
356 | if (is_single_links) | |
357 | dai_link->cpu_dai_name = NULL; | |
358 | } | |
359 | EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_cpu); | |
360 | ||
0f4e0711 KM |
361 | int asoc_simple_card_clean_reference(struct snd_soc_card *card) |
362 | { | |
363 | struct snd_soc_dai_link *dai_link; | |
364 | int num_links; | |
365 | ||
366 | for (num_links = 0, dai_link = card->dai_link; | |
367 | num_links < card->num_links; | |
368 | num_links++, dai_link++) { | |
369 | of_node_put(dai_link->cpu_of_node); | |
370 | of_node_put(dai_link->codec_of_node); | |
371 | } | |
372 | return 0; | |
373 | } | |
374 | EXPORT_SYMBOL_GPL(asoc_simple_card_clean_reference); | |
375 | ||
3296d078 KM |
376 | int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, |
377 | char *prefix, | |
378 | int optional) | |
379 | { | |
380 | struct device_node *node = card->dev->of_node; | |
381 | char prop[128]; | |
382 | ||
383 | if (!prefix) | |
384 | prefix = ""; | |
385 | ||
386 | snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); | |
387 | ||
388 | if (!of_property_read_bool(node, prop)) { | |
389 | if (optional) | |
390 | return 0; | |
391 | return -EINVAL; | |
392 | } | |
393 | ||
394 | return snd_soc_of_parse_audio_routing(card, prop); | |
395 | } | |
396 | EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_routing); | |
397 | ||
b31f11d0 KM |
398 | int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, |
399 | char *prefix) | |
400 | { | |
401 | struct device_node *node = card->dev->of_node; | |
402 | char prop[128]; | |
403 | ||
404 | if (!prefix) | |
405 | prefix = ""; | |
406 | ||
407 | snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); | |
408 | ||
409 | if (of_property_read_bool(node, prop)) | |
410 | return snd_soc_of_parse_audio_simple_widgets(card, prop); | |
411 | ||
412 | /* no widgets is not error */ | |
413 | return 0; | |
414 | } | |
415 | EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_widgets); | |
416 | ||
1f85e118 KM |
417 | /* Module information */ |
418 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | |
419 | MODULE_DESCRIPTION("ALSA SoC Simple Card Utils"); | |
420 | MODULE_LICENSE("GPL v2"); |