1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // ASoC audio graph sound card support |
4 | // |
5 | // Copyright (C) 2016 Renesas Solutions Corp. |
6 | // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
7 | // |
8 | // based on ${LINUX}/sound/soc/generic/simple-card.c |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/device.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_graph.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/string.h> |
18 | #include <sound/graph_card.h> |
19 | |
20 | #define DPCM_SELECTABLE 1 |
21 | |
22 | static int graph_outdrv_event(struct snd_soc_dapm_widget *w, |
23 | struct snd_kcontrol *kcontrol, |
24 | int event) |
25 | { |
26 | struct snd_soc_dapm_context *dapm = w->dapm; |
27 | struct simple_util_priv *priv = snd_soc_card_get_drvdata(card: dapm->card); |
28 | |
29 | switch (event) { |
30 | case SND_SOC_DAPM_POST_PMU: |
31 | gpiod_set_value_cansleep(desc: priv->pa_gpio, value: 1); |
32 | break; |
33 | case SND_SOC_DAPM_PRE_PMD: |
34 | gpiod_set_value_cansleep(desc: priv->pa_gpio, value: 0); |
35 | break; |
36 | default: |
37 | return -EINVAL; |
38 | } |
39 | |
40 | return 0; |
41 | } |
42 | |
43 | static const struct snd_soc_dapm_widget graph_dapm_widgets[] = { |
44 | SND_SOC_DAPM_OUT_DRV_E("Amplifier" , SND_SOC_NOPM, |
45 | 0, 0, NULL, 0, graph_outdrv_event, |
46 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
47 | }; |
48 | |
49 | static const struct snd_soc_ops graph_ops = { |
50 | .startup = simple_util_startup, |
51 | .shutdown = simple_util_shutdown, |
52 | .hw_params = simple_util_hw_params, |
53 | }; |
54 | |
55 | static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc) |
56 | { |
57 | struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc); |
58 | |
59 | if (dai && (dai->component->driver->pcm_construct || |
60 | (dai->driver->ops && dai->driver->ops->pcm_new))) |
61 | return true; |
62 | |
63 | return false; |
64 | } |
65 | |
66 | static void graph_parse_convert(struct device *dev, |
67 | struct device_node *ep, |
68 | struct simple_util_data *adata) |
69 | { |
70 | struct device_node *top = dev->of_node; |
71 | struct device_node *port = of_get_parent(node: ep); |
72 | struct device_node *ports = of_get_parent(node: port); |
73 | struct device_node *node = of_graph_get_port_parent(node: ep); |
74 | |
75 | simple_util_parse_convert(np: top, NULL, data: adata); |
76 | if (of_node_name_eq(np: ports, name: "ports" )) |
77 | simple_util_parse_convert(np: ports, NULL, data: adata); |
78 | simple_util_parse_convert(np: port, NULL, data: adata); |
79 | simple_util_parse_convert(np: ep, NULL, data: adata); |
80 | |
81 | of_node_put(node: port); |
82 | of_node_put(node: ports); |
83 | of_node_put(node); |
84 | } |
85 | |
86 | static void graph_parse_mclk_fs(struct device_node *top, |
87 | struct device_node *ep, |
88 | struct simple_dai_props *props) |
89 | { |
90 | struct device_node *port = of_get_parent(node: ep); |
91 | struct device_node *ports = of_get_parent(node: port); |
92 | |
93 | of_property_read_u32(np: top, propname: "mclk-fs" , out_value: &props->mclk_fs); |
94 | if (of_node_name_eq(np: ports, name: "ports" )) |
95 | of_property_read_u32(np: ports, propname: "mclk-fs" , out_value: &props->mclk_fs); |
96 | of_property_read_u32(np: port, propname: "mclk-fs" , out_value: &props->mclk_fs); |
97 | of_property_read_u32(np: ep, propname: "mclk-fs" , out_value: &props->mclk_fs); |
98 | |
99 | of_node_put(node: port); |
100 | of_node_put(node: ports); |
101 | } |
102 | |
103 | static int graph_parse_node(struct simple_util_priv *priv, |
104 | struct device_node *ep, |
105 | struct link_info *li, |
106 | int *cpu) |
107 | { |
108 | struct device *dev = simple_priv_to_dev(priv); |
109 | struct device_node *top = dev->of_node; |
110 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
111 | struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); |
112 | struct snd_soc_dai_link_component *dlc; |
113 | struct simple_util_dai *dai; |
114 | int ret; |
115 | |
116 | if (cpu) { |
117 | dlc = snd_soc_link_to_cpu(link: dai_link, n: 0); |
118 | dai = simple_props_to_dai_cpu(dai_props, 0); |
119 | } else { |
120 | dlc = snd_soc_link_to_codec(link: dai_link, n: 0); |
121 | dai = simple_props_to_dai_codec(dai_props, 0); |
122 | } |
123 | |
124 | graph_parse_mclk_fs(top, ep, props: dai_props); |
125 | |
126 | ret = graph_util_parse_dai(dev, ep, dlc, is_single_link: cpu); |
127 | if (ret < 0) |
128 | return ret; |
129 | |
130 | ret = simple_util_parse_tdm(ep, dai); |
131 | if (ret < 0) |
132 | return ret; |
133 | |
134 | ret = simple_util_parse_clk(dev, node: ep, simple_dai: dai, dlc); |
135 | if (ret < 0) |
136 | return ret; |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | static int graph_link_init(struct simple_util_priv *priv, |
142 | struct device_node *cpu_ep, |
143 | struct device_node *codec_ep, |
144 | struct link_info *li, |
145 | char *name) |
146 | { |
147 | struct device *dev = simple_priv_to_dev(priv); |
148 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
149 | int ret; |
150 | |
151 | ret = simple_util_parse_daifmt(dev, node: cpu_ep, codec: codec_ep, |
152 | NULL, retfmt: &dai_link->dai_fmt); |
153 | if (ret < 0) |
154 | return ret; |
155 | |
156 | dai_link->init = simple_util_dai_init; |
157 | dai_link->ops = &graph_ops; |
158 | if (priv->ops) |
159 | dai_link->ops = priv->ops; |
160 | |
161 | return simple_util_set_dailink_name(dev, dai_link, fmt: name); |
162 | } |
163 | |
164 | static int graph_dai_link_of_dpcm(struct simple_util_priv *priv, |
165 | struct device_node *cpu_ep, |
166 | struct device_node *codec_ep, |
167 | struct link_info *li) |
168 | { |
169 | struct device *dev = simple_priv_to_dev(priv); |
170 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
171 | struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); |
172 | struct device_node *top = dev->of_node; |
173 | struct device_node *ep = li->cpu ? cpu_ep : codec_ep; |
174 | char dai_name[64]; |
175 | int ret; |
176 | |
177 | dev_dbg(dev, "link_of DPCM (%pOF)\n" , ep); |
178 | |
179 | if (li->cpu) { |
180 | struct snd_soc_card *card = simple_priv_to_card(priv); |
181 | struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(link: dai_link, n: 0); |
182 | struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(link: dai_link, n: 0); |
183 | int is_single_links = 0; |
184 | |
185 | /* Codec is dummy */ |
186 | |
187 | /* FE settings */ |
188 | dai_link->dynamic = 1; |
189 | dai_link->dpcm_merged_format = 1; |
190 | |
191 | ret = graph_parse_node(priv, ep: cpu_ep, li, cpu: &is_single_links); |
192 | if (ret) |
193 | return ret; |
194 | |
195 | snprintf(buf: dai_name, size: sizeof(dai_name), |
196 | fmt: "fe.%pOFP.%s" , cpus->of_node, cpus->dai_name); |
197 | /* |
198 | * In BE<->BE connections it is not required to create |
199 | * PCM devices at CPU end of the dai link and thus 'no_pcm' |
200 | * flag needs to be set. It is useful when there are many |
201 | * BE components and some of these have to be connected to |
202 | * form a valid audio path. |
203 | * |
204 | * For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where |
205 | * there are 'n' BE components in the path. |
206 | */ |
207 | if (card->component_chaining && !soc_component_is_pcm(dlc: cpus)) { |
208 | dai_link->no_pcm = 1; |
209 | dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup; |
210 | } |
211 | |
212 | simple_util_canonicalize_cpu(cpus, is_single_links); |
213 | simple_util_canonicalize_platform(platforms, cpus); |
214 | } else { |
215 | struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, 0); |
216 | struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(link: dai_link, n: 0); |
217 | struct device_node *port; |
218 | struct device_node *ports; |
219 | |
220 | /* CPU is dummy */ |
221 | |
222 | /* BE settings */ |
223 | dai_link->no_pcm = 1; |
224 | dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup; |
225 | |
226 | ret = graph_parse_node(priv, ep: codec_ep, li, NULL); |
227 | if (ret < 0) |
228 | return ret; |
229 | |
230 | snprintf(buf: dai_name, size: sizeof(dai_name), |
231 | fmt: "be.%pOFP.%s" , codecs->of_node, codecs->dai_name); |
232 | |
233 | /* check "prefix" from top node */ |
234 | port = of_get_parent(node: ep); |
235 | ports = of_get_parent(node: port); |
236 | snd_soc_of_parse_node_prefix(np: top, codec_conf: cconf, of_node: codecs->of_node, |
237 | propname: "prefix" ); |
238 | if (of_node_name_eq(np: ports, name: "ports" )) |
239 | snd_soc_of_parse_node_prefix(np: ports, codec_conf: cconf, of_node: codecs->of_node, propname: "prefix" ); |
240 | snd_soc_of_parse_node_prefix(np: port, codec_conf: cconf, of_node: codecs->of_node, |
241 | propname: "prefix" ); |
242 | |
243 | of_node_put(node: ports); |
244 | of_node_put(node: port); |
245 | } |
246 | |
247 | graph_parse_convert(dev, ep, adata: &dai_props->adata); |
248 | |
249 | snd_soc_dai_link_set_capabilities(dai_link); |
250 | |
251 | ret = graph_link_init(priv, cpu_ep, codec_ep, li, name: dai_name); |
252 | |
253 | li->link++; |
254 | |
255 | return ret; |
256 | } |
257 | |
258 | static int graph_dai_link_of(struct simple_util_priv *priv, |
259 | struct device_node *cpu_ep, |
260 | struct device_node *codec_ep, |
261 | struct link_info *li) |
262 | { |
263 | struct device *dev = simple_priv_to_dev(priv); |
264 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
265 | struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(link: dai_link, n: 0); |
266 | struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(link: dai_link, n: 0); |
267 | struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(link: dai_link, n: 0); |
268 | char dai_name[64]; |
269 | int ret, is_single_links = 0; |
270 | |
271 | dev_dbg(dev, "link_of (%pOF)\n" , cpu_ep); |
272 | |
273 | ret = graph_parse_node(priv, ep: cpu_ep, li, cpu: &is_single_links); |
274 | if (ret < 0) |
275 | return ret; |
276 | |
277 | ret = graph_parse_node(priv, ep: codec_ep, li, NULL); |
278 | if (ret < 0) |
279 | return ret; |
280 | |
281 | snprintf(buf: dai_name, size: sizeof(dai_name), |
282 | fmt: "%s-%s" , cpus->dai_name, codecs->dai_name); |
283 | |
284 | simple_util_canonicalize_cpu(cpus, is_single_links); |
285 | simple_util_canonicalize_platform(platforms, cpus); |
286 | |
287 | ret = graph_link_init(priv, cpu_ep, codec_ep, li, name: dai_name); |
288 | if (ret < 0) |
289 | return ret; |
290 | |
291 | li->link++; |
292 | |
293 | return 0; |
294 | } |
295 | |
296 | static inline bool parse_as_dpcm_link(struct simple_util_priv *priv, |
297 | struct device_node *codec_port, |
298 | struct simple_util_data *adata) |
299 | { |
300 | if (priv->force_dpcm) |
301 | return true; |
302 | |
303 | if (!priv->dpcm_selectable) |
304 | return false; |
305 | |
306 | /* |
307 | * It is DPCM |
308 | * if Codec port has many endpoints, |
309 | * or has convert-xxx property |
310 | */ |
311 | if ((of_get_child_count(np: codec_port) > 1) || |
312 | simple_util_is_convert_required(data: adata)) |
313 | return true; |
314 | |
315 | return false; |
316 | } |
317 | |
318 | static int __graph_for_each_link(struct simple_util_priv *priv, |
319 | struct link_info *li, |
320 | int (*func_noml)(struct simple_util_priv *priv, |
321 | struct device_node *cpu_ep, |
322 | struct device_node *codec_ep, |
323 | struct link_info *li), |
324 | int (*func_dpcm)(struct simple_util_priv *priv, |
325 | struct device_node *cpu_ep, |
326 | struct device_node *codec_ep, |
327 | struct link_info *li)) |
328 | { |
329 | struct of_phandle_iterator it; |
330 | struct device *dev = simple_priv_to_dev(priv); |
331 | struct device_node *node = dev->of_node; |
332 | struct device_node *cpu_port; |
333 | struct device_node *cpu_ep; |
334 | struct device_node *codec_ep; |
335 | struct device_node *codec_port; |
336 | struct device_node *codec_port_old = NULL; |
337 | struct simple_util_data adata; |
338 | int rc, ret = 0; |
339 | |
340 | /* loop for all listed CPU port */ |
341 | of_for_each_phandle(&it, rc, node, "dais" , NULL, 0) { |
342 | cpu_port = it.node; |
343 | cpu_ep = NULL; |
344 | |
345 | /* loop for all CPU endpoint */ |
346 | while (1) { |
347 | cpu_ep = of_get_next_child(node: cpu_port, prev: cpu_ep); |
348 | if (!cpu_ep) |
349 | break; |
350 | |
351 | /* get codec */ |
352 | codec_ep = of_graph_get_remote_endpoint(node: cpu_ep); |
353 | codec_port = of_get_parent(node: codec_ep); |
354 | |
355 | /* get convert-xxx property */ |
356 | memset(&adata, 0, sizeof(adata)); |
357 | graph_parse_convert(dev, ep: codec_ep, adata: &adata); |
358 | graph_parse_convert(dev, ep: cpu_ep, adata: &adata); |
359 | |
360 | /* check if link requires DPCM parsing */ |
361 | if (parse_as_dpcm_link(priv, codec_port, adata: &adata)) { |
362 | /* |
363 | * Codec endpoint can be NULL for pluggable audio HW. |
364 | * Platform DT can populate the Codec endpoint depending on the |
365 | * plugged HW. |
366 | */ |
367 | /* Do it all CPU endpoint, and 1st Codec endpoint */ |
368 | if (li->cpu || |
369 | ((codec_port_old != codec_port) && codec_ep)) |
370 | ret = func_dpcm(priv, cpu_ep, codec_ep, li); |
371 | /* else normal sound */ |
372 | } else { |
373 | if (li->cpu) |
374 | ret = func_noml(priv, cpu_ep, codec_ep, li); |
375 | } |
376 | |
377 | of_node_put(node: codec_ep); |
378 | of_node_put(node: codec_port); |
379 | |
380 | if (ret < 0) { |
381 | of_node_put(node: cpu_ep); |
382 | return ret; |
383 | } |
384 | |
385 | codec_port_old = codec_port; |
386 | } |
387 | } |
388 | |
389 | return 0; |
390 | } |
391 | |
392 | static int graph_for_each_link(struct simple_util_priv *priv, |
393 | struct link_info *li, |
394 | int (*func_noml)(struct simple_util_priv *priv, |
395 | struct device_node *cpu_ep, |
396 | struct device_node *codec_ep, |
397 | struct link_info *li), |
398 | int (*func_dpcm)(struct simple_util_priv *priv, |
399 | struct device_node *cpu_ep, |
400 | struct device_node *codec_ep, |
401 | struct link_info *li)) |
402 | { |
403 | int ret; |
404 | /* |
405 | * Detect all CPU first, and Detect all Codec 2nd. |
406 | * |
407 | * In Normal sound case, all DAIs are detected |
408 | * as "CPU-Codec". |
409 | * |
410 | * In DPCM sound case, |
411 | * all CPUs are detected as "CPU-dummy", and |
412 | * all Codecs are detected as "dummy-Codec". |
413 | * To avoid random sub-device numbering, |
414 | * detect "dummy-Codec" in last; |
415 | */ |
416 | for (li->cpu = 1; li->cpu >= 0; li->cpu--) { |
417 | ret = __graph_for_each_link(priv, li, func_noml, func_dpcm); |
418 | if (ret < 0) |
419 | break; |
420 | } |
421 | |
422 | return ret; |
423 | } |
424 | |
425 | static int graph_count_noml(struct simple_util_priv *priv, |
426 | struct device_node *cpu_ep, |
427 | struct device_node *codec_ep, |
428 | struct link_info *li) |
429 | { |
430 | struct device *dev = simple_priv_to_dev(priv); |
431 | |
432 | if (li->link >= SNDRV_MAX_LINKS) { |
433 | dev_err(dev, "too many links\n" ); |
434 | return -EINVAL; |
435 | } |
436 | |
437 | /* |
438 | * DON'T REMOVE platforms |
439 | * see |
440 | * simple-card.c :: simple_count_noml() |
441 | */ |
442 | li->num[li->link].cpus = 1; |
443 | li->num[li->link].platforms = 1; |
444 | |
445 | li->num[li->link].codecs = 1; |
446 | |
447 | li->link += 1; /* 1xCPU-Codec */ |
448 | |
449 | dev_dbg(dev, "Count As Normal\n" ); |
450 | |
451 | return 0; |
452 | } |
453 | |
454 | static int graph_count_dpcm(struct simple_util_priv *priv, |
455 | struct device_node *cpu_ep, |
456 | struct device_node *codec_ep, |
457 | struct link_info *li) |
458 | { |
459 | struct device *dev = simple_priv_to_dev(priv); |
460 | |
461 | if (li->link >= SNDRV_MAX_LINKS) { |
462 | dev_err(dev, "too many links\n" ); |
463 | return -EINVAL; |
464 | } |
465 | |
466 | if (li->cpu) { |
467 | /* |
468 | * DON'T REMOVE platforms |
469 | * see |
470 | * simple-card.c :: simple_count_noml() |
471 | */ |
472 | li->num[li->link].cpus = 1; |
473 | li->num[li->link].platforms = 1; |
474 | |
475 | li->link++; /* 1xCPU-dummy */ |
476 | } else { |
477 | li->num[li->link].codecs = 1; |
478 | |
479 | li->link++; /* 1xdummy-Codec */ |
480 | } |
481 | |
482 | dev_dbg(dev, "Count As DPCM\n" ); |
483 | |
484 | return 0; |
485 | } |
486 | |
487 | static int graph_get_dais_count(struct simple_util_priv *priv, |
488 | struct link_info *li) |
489 | { |
490 | /* |
491 | * link_num : number of links. |
492 | * CPU-Codec / CPU-dummy / dummy-Codec |
493 | * dais_num : number of DAIs |
494 | * ccnf_num : number of codec_conf |
495 | * same number for "dummy-Codec" |
496 | * |
497 | * ex1) |
498 | * CPU0 --- Codec0 link : 5 |
499 | * CPU1 --- Codec1 dais : 7 |
500 | * CPU2 -/ ccnf : 1 |
501 | * CPU3 --- Codec2 |
502 | * |
503 | * => 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec |
504 | * => 7 DAIs = 4xCPU + 3xCodec |
505 | * => 1 ccnf = 1xdummy-Codec |
506 | * |
507 | * ex2) |
508 | * CPU0 --- Codec0 link : 5 |
509 | * CPU1 --- Codec1 dais : 6 |
510 | * CPU2 -/ ccnf : 1 |
511 | * CPU3 -/ |
512 | * |
513 | * => 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec |
514 | * => 6 DAIs = 4xCPU + 2xCodec |
515 | * => 1 ccnf = 1xdummy-Codec |
516 | * |
517 | * ex3) |
518 | * CPU0 --- Codec0 link : 6 |
519 | * CPU1 -/ dais : 6 |
520 | * CPU2 --- Codec1 ccnf : 2 |
521 | * CPU3 -/ |
522 | * |
523 | * => 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec |
524 | * => 6 DAIs = 4xCPU + 2xCodec |
525 | * => 2 ccnf = 2xdummy-Codec |
526 | * |
527 | * ex4) |
528 | * CPU0 --- Codec0 (convert-rate) link : 3 |
529 | * CPU1 --- Codec1 dais : 4 |
530 | * ccnf : 1 |
531 | * |
532 | * => 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec |
533 | * => 4 DAIs = 2xCPU + 2xCodec |
534 | * => 1 ccnf = 1xdummy-Codec |
535 | */ |
536 | return graph_for_each_link(priv, li, |
537 | func_noml: graph_count_noml, |
538 | func_dpcm: graph_count_dpcm); |
539 | } |
540 | |
541 | int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) |
542 | { |
543 | struct snd_soc_card *card = simple_priv_to_card(priv); |
544 | struct link_info *li; |
545 | int ret; |
546 | |
547 | li = devm_kzalloc(dev, size: sizeof(*li), GFP_KERNEL); |
548 | if (!li) |
549 | return -ENOMEM; |
550 | |
551 | card->owner = THIS_MODULE; |
552 | card->dev = dev; |
553 | |
554 | ret = graph_get_dais_count(priv, li); |
555 | if (ret < 0) |
556 | return ret; |
557 | |
558 | if (!li->link) |
559 | return -EINVAL; |
560 | |
561 | ret = simple_util_init_priv(priv, li); |
562 | if (ret < 0) |
563 | return ret; |
564 | |
565 | priv->pa_gpio = devm_gpiod_get_optional(dev, con_id: "pa" , flags: GPIOD_OUT_LOW); |
566 | if (IS_ERR(ptr: priv->pa_gpio)) { |
567 | ret = PTR_ERR(ptr: priv->pa_gpio); |
568 | dev_err(dev, "failed to get amplifier gpio: %d\n" , ret); |
569 | return ret; |
570 | } |
571 | |
572 | ret = simple_util_parse_widgets(card, NULL); |
573 | if (ret < 0) |
574 | return ret; |
575 | |
576 | ret = simple_util_parse_routing(card, NULL); |
577 | if (ret < 0) |
578 | return ret; |
579 | |
580 | memset(li, 0, sizeof(*li)); |
581 | ret = graph_for_each_link(priv, li, |
582 | func_noml: graph_dai_link_of, |
583 | func_dpcm: graph_dai_link_of_dpcm); |
584 | if (ret < 0) |
585 | goto err; |
586 | |
587 | ret = simple_util_parse_card_name(card, NULL); |
588 | if (ret < 0) |
589 | goto err; |
590 | |
591 | snd_soc_card_set_drvdata(card, data: priv); |
592 | |
593 | simple_util_debug_info(priv); |
594 | |
595 | ret = devm_snd_soc_register_card(dev, card); |
596 | if (ret < 0) |
597 | goto err; |
598 | |
599 | devm_kfree(dev, p: li); |
600 | return 0; |
601 | |
602 | err: |
603 | simple_util_clean_reference(card); |
604 | |
605 | return dev_err_probe(dev, err: ret, fmt: "parse error\n" ); |
606 | } |
607 | EXPORT_SYMBOL_GPL(audio_graph_parse_of); |
608 | |
609 | static int graph_probe(struct platform_device *pdev) |
610 | { |
611 | struct simple_util_priv *priv; |
612 | struct device *dev = &pdev->dev; |
613 | struct snd_soc_card *card; |
614 | |
615 | /* Allocate the private data and the DAI link array */ |
616 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
617 | if (!priv) |
618 | return -ENOMEM; |
619 | |
620 | card = simple_priv_to_card(priv); |
621 | card->dapm_widgets = graph_dapm_widgets; |
622 | card->num_dapm_widgets = ARRAY_SIZE(graph_dapm_widgets); |
623 | card->probe = graph_util_card_probe; |
624 | |
625 | if (of_device_get_match_data(dev)) |
626 | priv->dpcm_selectable = 1; |
627 | |
628 | return audio_graph_parse_of(priv, dev); |
629 | } |
630 | |
631 | static const struct of_device_id graph_of_match[] = { |
632 | { .compatible = "audio-graph-card" , }, |
633 | { .compatible = "audio-graph-scu-card" , |
634 | .data = (void *)DPCM_SELECTABLE }, |
635 | {}, |
636 | }; |
637 | MODULE_DEVICE_TABLE(of, graph_of_match); |
638 | |
639 | static struct platform_driver graph_card = { |
640 | .driver = { |
641 | .name = "asoc-audio-graph-card" , |
642 | .pm = &snd_soc_pm_ops, |
643 | .of_match_table = graph_of_match, |
644 | }, |
645 | .probe = graph_probe, |
646 | .remove_new = simple_util_remove, |
647 | }; |
648 | module_platform_driver(graph_card); |
649 | |
650 | MODULE_ALIAS("platform:asoc-audio-graph-card" ); |
651 | MODULE_LICENSE("GPL v2" ); |
652 | MODULE_DESCRIPTION("ASoC Audio Graph Sound Card" ); |
653 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>" ); |
654 | |