1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // ASoC Audio Graph Card2 support |
4 | // |
5 | // Copyright (C) 2020 Renesas Electronics Corp. |
6 | // Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
7 | // |
8 | // based on ${LINUX}/sound/soc/generic/audio-graph-card.c |
9 | #include <linux/clk.h> |
10 | #include <linux/device.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_graph.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/string.h> |
17 | #include <sound/graph_card.h> |
18 | |
19 | /************************************ |
20 | daifmt |
21 | ************************************ |
22 | ports { |
23 | format = "left_j"; |
24 | port@0 { |
25 | bitclock-master; |
26 | sample0: endpoint@0 { |
27 | frame-master; |
28 | }; |
29 | sample1: endpoint@1 { |
30 | format = "i2s"; |
31 | }; |
32 | }; |
33 | ... |
34 | }; |
35 | |
36 | You can set daifmt at ports/port/endpoint. |
37 | It uses *latest* format, and *share* master settings. |
38 | In above case, |
39 | sample0: left_j, bitclock-master, frame-master |
40 | sample1: i2s, bitclock-master |
41 | |
42 | If there was no settings, *Codec* will be |
43 | bitclock/frame provider as default. |
44 | see |
45 | graph_parse_daifmt(). |
46 | |
47 | "format" property is no longer needed on DT if both CPU/Codec drivers are |
48 | supporting snd_soc_dai_ops :: .auto_selectable_formats. |
49 | see |
50 | snd_soc_runtime_get_dai_fmt() |
51 | |
52 | sample driver |
53 | linux/sound/soc/sh/rcar/core.c |
54 | linux/sound/soc/codecs/ak4613.c |
55 | linux/sound/soc/codecs/pcm3168a.c |
56 | linux/sound/soc/soc-utils.c |
57 | linux/sound/soc/generic/test-component.c |
58 | |
59 | ************************************ |
60 | Normal Audio-Graph |
61 | ************************************ |
62 | |
63 | CPU <---> Codec |
64 | |
65 | sound { |
66 | compatible = "audio-graph-card2"; |
67 | links = <&cpu>; |
68 | }; |
69 | |
70 | CPU { |
71 | cpu: port { |
72 | bitclock-master; |
73 | frame-master; |
74 | cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; }; |
75 | }; |
76 | |
77 | Codec { |
78 | port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; }; |
79 | }; |
80 | |
81 | ************************************ |
82 | Multi-CPU/Codec |
83 | ************************************ |
84 | |
85 | It has link connection part (= X,x) and list part (= A,B,a,b). |
86 | "links" is connection part of CPU side (= @). |
87 | |
88 | +----+ +---+ |
89 | CPU1 --|A X| <-@----> |x a|-- Codec1 |
90 | CPU2 --|B | | b|-- Codec2 |
91 | +----+ +---+ |
92 | |
93 | sound { |
94 | compatible = "audio-graph-card2"; |
95 | |
96 | (@) links = <&mcpu>; |
97 | |
98 | multi { |
99 | ports@0 { |
100 | (@) mcpu: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; // (X) to pair |
101 | port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; // (A) Multi Element |
102 | port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; // (B) Multi Element |
103 | }; |
104 | ports@1 { |
105 | port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; // (x) to pair |
106 | port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; // (a) Multi Element |
107 | port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; // (b) Multi Element |
108 | }; |
109 | }; |
110 | }; |
111 | |
112 | CPU { |
113 | ports { |
114 | bitclock-master; |
115 | frame-master; |
116 | port@0 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; }; |
117 | port@1 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; }; |
118 | }; |
119 | }; |
120 | |
121 | Codec { |
122 | ports { |
123 | port@0 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; }; |
124 | port@1 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; }; |
125 | }; |
126 | }; |
127 | |
128 | ************************************ |
129 | DPCM |
130 | ************************************ |
131 | |
132 | DSP |
133 | ************ |
134 | PCM0 <--> * fe0 be0 * <--> DAI0: Codec Headset |
135 | PCM1 <--> * fe1 be1 * <--> DAI1: Codec Speakers |
136 | PCM2 <--> * fe2 be2 * <--> DAI2: MODEM |
137 | PCM3 <--> * fe3 be3 * <--> DAI3: BT |
138 | * be4 * <--> DAI4: DMIC |
139 | * be5 * <--> DAI5: FM |
140 | ************ |
141 | |
142 | sound { |
143 | compatible = "audio-graph-card2"; |
144 | |
145 | // indicate routing |
146 | routing = "xxx Playback", "xxx Playback", |
147 | "xxx Playback", "xxx Playback", |
148 | "xxx Playback", "xxx Playback"; |
149 | |
150 | // indicate all Front-End, Back-End |
151 | links = <&fe0, &fe1, ..., |
152 | &be0, &be1, ...>; |
153 | |
154 | dpcm { |
155 | // Front-End |
156 | ports@0 { |
157 | fe0: port@0 { fe0_ep: endpoint { remote-endpoint = <&pcm0_ep>; }; }; |
158 | fe1: port@1 { fe1_ep: endpoint { remote-endpoint = <&pcm1_ep>; }; }; |
159 | ... |
160 | }; |
161 | // Back-End |
162 | ports@1 { |
163 | be0: port@0 { be0_ep: endpoint { remote-endpoint = <&dai0_ep>; }; }; |
164 | be1: port@1 { be1_ep: endpoint { remote-endpoint = <&dai1_ep>; }; }; |
165 | ... |
166 | }; |
167 | }; |
168 | }; |
169 | |
170 | CPU { |
171 | ports { |
172 | bitclock-master; |
173 | frame-master; |
174 | port@0 { pcm0_ep: endpoint { remote-endpoint = <&fe0_ep>; }; }; |
175 | port@1 { pcm1_ep: endpoint { remote-endpoint = <&fe1_ep>; }; }; |
176 | ... |
177 | }; |
178 | }; |
179 | |
180 | Codec { |
181 | ports { |
182 | port@0 { dai0_ep: endpoint { remote-endpoint = <&be0_ep>; }; }; |
183 | port@1 { dai1_ep: endpoint { remote-endpoint = <&be1_ep>; }; }; |
184 | ... |
185 | }; |
186 | }; |
187 | |
188 | ************************************ |
189 | Codec to Codec |
190 | ************************************ |
191 | |
192 | +--+ |
193 | | |<-- Codec0 <- IN |
194 | | |--> Codec1 -> OUT |
195 | +--+ |
196 | |
197 | sound { |
198 | compatible = "audio-graph-card2"; |
199 | |
200 | routing = "OUT" ,"DAI1 Playback", |
201 | "DAI0 Capture", "IN"; |
202 | |
203 | links = <&c2c>; |
204 | |
205 | codec2codec { |
206 | ports { |
207 | rate = <48000>; |
208 | c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec0_ep>; }; }; |
209 | port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; |
210 | }; |
211 | }; |
212 | |
213 | Codec { |
214 | ports { |
215 | port@0 { |
216 | bitclock-master; |
217 | frame-master; |
218 | codec0_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; }; |
219 | port@1 { codec1_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; }; |
220 | }; |
221 | }; |
222 | |
223 | */ |
224 | |
225 | enum graph_type { |
226 | GRAPH_NORMAL, |
227 | GRAPH_DPCM, |
228 | GRAPH_C2C, |
229 | |
230 | GRAPH_MULTI, /* don't use ! Use this only in __graph_get_type() */ |
231 | }; |
232 | |
233 | #define GRAPH_NODENAME_MULTI "multi" |
234 | #define GRAPH_NODENAME_DPCM "dpcm" |
235 | #define GRAPH_NODENAME_C2C "codec2codec" |
236 | |
237 | #define port_to_endpoint(port) of_get_child_by_name(port, "endpoint") |
238 | |
239 | static enum graph_type __graph_get_type(struct device_node *lnk) |
240 | { |
241 | struct device_node *np, *parent_np; |
242 | enum graph_type ret; |
243 | |
244 | /* |
245 | * target { |
246 | * ports { |
247 | * => lnk: port@0 { ... }; |
248 | * port@1 { ... }; |
249 | * }; |
250 | * }; |
251 | */ |
252 | np = of_get_parent(node: lnk); |
253 | if (of_node_name_eq(np, name: "ports" )) { |
254 | parent_np = of_get_parent(node: np); |
255 | of_node_put(node: np); |
256 | np = parent_np; |
257 | } |
258 | |
259 | if (of_node_name_eq(np, GRAPH_NODENAME_MULTI)) { |
260 | ret = GRAPH_MULTI; |
261 | goto out_put; |
262 | } |
263 | |
264 | if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) { |
265 | ret = GRAPH_DPCM; |
266 | goto out_put; |
267 | } |
268 | |
269 | if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) { |
270 | ret = GRAPH_C2C; |
271 | goto out_put; |
272 | } |
273 | |
274 | ret = GRAPH_NORMAL; |
275 | |
276 | out_put: |
277 | of_node_put(node: np); |
278 | return ret; |
279 | |
280 | } |
281 | |
282 | static enum graph_type graph_get_type(struct simple_util_priv *priv, |
283 | struct device_node *lnk) |
284 | { |
285 | enum graph_type type = __graph_get_type(lnk); |
286 | |
287 | /* GRAPH_MULTI here means GRAPH_NORMAL */ |
288 | if (type == GRAPH_MULTI) |
289 | type = GRAPH_NORMAL; |
290 | |
291 | #ifdef DEBUG |
292 | { |
293 | struct device *dev = simple_priv_to_dev(priv); |
294 | const char *str = "Normal" ; |
295 | |
296 | switch (type) { |
297 | case GRAPH_DPCM: |
298 | if (graph_util_is_ports0(lnk)) |
299 | str = "DPCM Front-End" ; |
300 | else |
301 | str = "DPCM Back-End" ; |
302 | break; |
303 | case GRAPH_C2C: |
304 | str = "Codec2Codec" ; |
305 | break; |
306 | default: |
307 | break; |
308 | } |
309 | |
310 | dev_dbg(dev, "%pOF (%s)" , lnk, str); |
311 | } |
312 | #endif |
313 | return type; |
314 | } |
315 | |
316 | static int graph_lnk_is_multi(struct device_node *lnk) |
317 | { |
318 | return __graph_get_type(lnk) == GRAPH_MULTI; |
319 | } |
320 | |
321 | static struct device_node *graph_get_next_multi_ep(struct device_node **port) |
322 | { |
323 | struct device_node *ports = of_get_parent(node: *port); |
324 | struct device_node *ep = NULL; |
325 | struct device_node *rep = NULL; |
326 | |
327 | /* |
328 | * multi { |
329 | * ports { |
330 | * => lnk: port@0 { ... }; // to pair |
331 | * port@1 { ep { ... = rep0 } }; // Multi Element |
332 | * port@2 { ep { ... = rep1 } }; // Multi Element |
333 | * ... |
334 | * }; |
335 | * }; |
336 | * |
337 | * xxx { |
338 | * port@0 { rep0 }; |
339 | * port@1 { rep1 }; |
340 | * }; |
341 | */ |
342 | do { |
343 | *port = of_get_next_child(node: ports, prev: *port); |
344 | if (!*port) |
345 | break; |
346 | } while (!of_node_name_eq(np: *port, name: "port" )); |
347 | |
348 | if (*port) { |
349 | ep = port_to_endpoint(*port); |
350 | rep = of_graph_get_remote_endpoint(node: ep); |
351 | } |
352 | |
353 | of_node_put(node: ep); |
354 | of_node_put(node: ports); |
355 | |
356 | return rep; |
357 | } |
358 | |
359 | static const struct snd_soc_ops graph_ops = { |
360 | .startup = simple_util_startup, |
361 | .shutdown = simple_util_shutdown, |
362 | .hw_params = simple_util_hw_params, |
363 | }; |
364 | |
365 | static void graph_parse_convert(struct device_node *ep, |
366 | struct simple_dai_props *props) |
367 | { |
368 | struct device_node *port = of_get_parent(node: ep); |
369 | struct device_node *ports = of_get_parent(node: port); |
370 | struct simple_util_data *adata = &props->adata; |
371 | |
372 | if (of_node_name_eq(np: ports, name: "ports" )) |
373 | simple_util_parse_convert(np: ports, NULL, data: adata); |
374 | simple_util_parse_convert(np: port, NULL, data: adata); |
375 | simple_util_parse_convert(np: ep, NULL, data: adata); |
376 | |
377 | of_node_put(node: port); |
378 | of_node_put(node: ports); |
379 | } |
380 | |
381 | static void graph_parse_mclk_fs(struct device_node *ep, |
382 | struct simple_dai_props *props) |
383 | { |
384 | struct device_node *port = of_get_parent(node: ep); |
385 | struct device_node *ports = of_get_parent(node: port); |
386 | |
387 | if (of_node_name_eq(np: ports, name: "ports" )) |
388 | of_property_read_u32(np: ports, propname: "mclk-fs" , out_value: &props->mclk_fs); |
389 | of_property_read_u32(np: port, propname: "mclk-fs" , out_value: &props->mclk_fs); |
390 | of_property_read_u32(np: ep, propname: "mclk-fs" , out_value: &props->mclk_fs); |
391 | |
392 | of_node_put(node: port); |
393 | of_node_put(node: ports); |
394 | } |
395 | |
396 | static int __graph_parse_node(struct simple_util_priv *priv, |
397 | enum graph_type gtype, |
398 | struct device_node *ep, |
399 | struct link_info *li, |
400 | int is_cpu, int idx) |
401 | { |
402 | struct device *dev = simple_priv_to_dev(priv); |
403 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
404 | struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); |
405 | struct snd_soc_dai_link_component *dlc; |
406 | struct simple_util_dai *dai; |
407 | int ret, is_single_links = 0; |
408 | |
409 | if (is_cpu) { |
410 | dlc = snd_soc_link_to_cpu(link: dai_link, n: idx); |
411 | dai = simple_props_to_dai_cpu(dai_props, idx); |
412 | } else { |
413 | dlc = snd_soc_link_to_codec(link: dai_link, n: idx); |
414 | dai = simple_props_to_dai_codec(dai_props, idx); |
415 | } |
416 | |
417 | graph_parse_mclk_fs(ep, props: dai_props); |
418 | |
419 | ret = graph_util_parse_dai(dev, ep, dlc, is_single_link: &is_single_links); |
420 | if (ret < 0) |
421 | return ret; |
422 | |
423 | ret = simple_util_parse_tdm(ep, dai); |
424 | if (ret < 0) |
425 | return ret; |
426 | |
427 | ret = simple_util_parse_tdm_width_map(dev, np: ep, dai); |
428 | if (ret < 0) |
429 | return ret; |
430 | |
431 | ret = simple_util_parse_clk(dev, node: ep, simple_dai: dai, dlc); |
432 | if (ret < 0) |
433 | return ret; |
434 | |
435 | /* |
436 | * set DAI Name |
437 | */ |
438 | if (!dai_link->name) { |
439 | struct snd_soc_dai_link_component *cpus = dlc; |
440 | struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(link: dai_link, n: idx); |
441 | char *cpu_multi = "" ; |
442 | char *codec_multi = "" ; |
443 | |
444 | if (dai_link->num_cpus > 1) |
445 | cpu_multi = "_multi" ; |
446 | if (dai_link->num_codecs > 1) |
447 | codec_multi = "_multi" ; |
448 | |
449 | switch (gtype) { |
450 | case GRAPH_NORMAL: |
451 | /* run is_cpu only. see audio_graph2_link_normal() */ |
452 | if (is_cpu) |
453 | simple_util_set_dailink_name(dev, dai_link, fmt: "%s%s-%s%s" , |
454 | cpus->dai_name, cpu_multi, |
455 | codecs->dai_name, codec_multi); |
456 | break; |
457 | case GRAPH_DPCM: |
458 | if (is_cpu) |
459 | simple_util_set_dailink_name(dev, dai_link, fmt: "fe.%pOFP.%s%s" , |
460 | cpus->of_node, cpus->dai_name, cpu_multi); |
461 | else |
462 | simple_util_set_dailink_name(dev, dai_link, fmt: "be.%pOFP.%s%s" , |
463 | codecs->of_node, codecs->dai_name, codec_multi); |
464 | break; |
465 | case GRAPH_C2C: |
466 | /* run is_cpu only. see audio_graph2_link_c2c() */ |
467 | if (is_cpu) |
468 | simple_util_set_dailink_name(dev, dai_link, fmt: "c2c.%s%s-%s%s" , |
469 | cpus->dai_name, cpu_multi, |
470 | codecs->dai_name, codec_multi); |
471 | break; |
472 | default: |
473 | break; |
474 | } |
475 | } |
476 | |
477 | /* |
478 | * Check "prefix" from top node |
479 | * if DPCM-BE case |
480 | */ |
481 | if (!is_cpu && gtype == GRAPH_DPCM) { |
482 | struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(link: dai_link, n: idx); |
483 | struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, idx); |
484 | struct device_node *rport = of_get_parent(node: ep); |
485 | struct device_node *rports = of_get_parent(node: rport); |
486 | |
487 | if (of_node_name_eq(np: rports, name: "ports" )) |
488 | snd_soc_of_parse_node_prefix(np: rports, codec_conf: cconf, of_node: codecs->of_node, propname: "prefix" ); |
489 | snd_soc_of_parse_node_prefix(np: rport, codec_conf: cconf, of_node: codecs->of_node, propname: "prefix" ); |
490 | |
491 | of_node_put(node: rport); |
492 | of_node_put(node: rports); |
493 | } |
494 | |
495 | if (is_cpu) { |
496 | struct snd_soc_dai_link_component *cpus = dlc; |
497 | struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(link: dai_link, n: idx); |
498 | |
499 | simple_util_canonicalize_cpu(cpus, is_single_links); |
500 | simple_util_canonicalize_platform(platforms, cpus); |
501 | } |
502 | |
503 | return 0; |
504 | } |
505 | |
506 | static int graph_parse_node_multi_nm(struct snd_soc_dai_link *dai_link, |
507 | int *nm_idx, int cpu_idx, |
508 | struct device_node *mcpu_port) |
509 | { |
510 | /* |
511 | * +---+ +---+ |
512 | * | X|<-@------->|x | |
513 | * | | | | |
514 | * cpu0 <--|A 1|<--------->|4 a|-> codec0 |
515 | * cpu1 <--|B 2|<-----+--->|5 b|-> codec1 |
516 | * cpu2 <--|C 3|<----/ +---+ |
517 | * +---+ |
518 | * |
519 | * multi { |
520 | * ports { |
521 | * port@0 { mcpu_top_ep {... = mcodec_ep; }; }; // (X) to pair |
522 | * <mcpu_port> port@1 { mcpu0_ep { ... = cpu0_ep; }; // (A) Multi Element |
523 | * mcpu0_ep_0 { ... = mcodec0_ep_0; }; }; // (1) connected Codec |
524 | * port@2 { mcpu1_ep { ... = cpu1_ep; }; // (B) Multi Element |
525 | * mcpu1_ep_0 { ... = mcodec1_ep_0; }; }; // (2) connected Codec |
526 | * port@3 { mcpu2_ep { ... = cpu2_ep; }; // (C) Multi Element |
527 | * mcpu2_ep_0 { ... = mcodec1_ep_1; }; }; // (3) connected Codec |
528 | * }; |
529 | * |
530 | * ports { |
531 | * port@0 { mcodec_top_ep {... = mcpu_ep; }; }; // (x) to pair |
532 | * <mcodec_port>port@1 { mcodec0_ep { ... = codec0_ep; }; // (a) Multi Element |
533 | * mcodec0_ep_0 { ... = mcpu0_ep_0; }; }; // (4) connected CPU |
534 | * port@2 { mcodec1_ep { ... = codec1_ep; }; // (b) Multi Element |
535 | * mcodec1_ep_0 { ... = mcpu1_ep_0; }; // (5) connected CPU |
536 | * mcodec1_ep_1 { ... = mcpu2_ep_0; }; }; // (5) connected CPU |
537 | * }; |
538 | * }; |
539 | */ |
540 | struct device_node *mcpu_ep = port_to_endpoint(mcpu_port); |
541 | struct device_node *mcpu_ep_n = mcpu_ep; |
542 | struct device_node *mcpu_port_top = of_get_next_child(node: of_get_parent(node: mcpu_port), NULL); |
543 | struct device_node *mcpu_ep_top = port_to_endpoint(mcpu_port_top); |
544 | struct device_node *mcodec_ep_top = of_graph_get_remote_endpoint(node: mcpu_ep_top); |
545 | struct device_node *mcodec_port_top = of_get_parent(node: mcodec_ep_top); |
546 | struct device_node *mcodec_ports = of_get_parent(node: mcodec_port_top); |
547 | int nm_max = max(dai_link->num_cpus, dai_link->num_codecs); |
548 | int ret = -EINVAL; |
549 | |
550 | if (cpu_idx > dai_link->num_cpus) |
551 | goto mcpu_err; |
552 | |
553 | while (1) { |
554 | struct device_node *mcodec_ep_n; |
555 | struct device_node *mcodec_port_i; |
556 | struct device_node *mcodec_port; |
557 | int codec_idx; |
558 | |
559 | if (*nm_idx > nm_max) |
560 | break; |
561 | |
562 | mcpu_ep_n = of_get_next_child(node: mcpu_port, prev: mcpu_ep_n); |
563 | if (!mcpu_ep_n) { |
564 | ret = 0; |
565 | break; |
566 | } |
567 | |
568 | mcodec_ep_n = of_graph_get_remote_endpoint(node: mcpu_ep_n); |
569 | mcodec_port = of_get_parent(node: mcodec_ep_n); |
570 | |
571 | if (mcodec_ports != of_get_parent(node: mcodec_port)) |
572 | goto mcpu_err; |
573 | |
574 | codec_idx = 0; |
575 | mcodec_port_i = of_get_next_child(node: mcodec_ports, NULL); |
576 | while (1) { |
577 | if (codec_idx > dai_link->num_codecs) |
578 | goto mcodec_err; |
579 | |
580 | mcodec_port_i = of_get_next_child(node: mcodec_ports, prev: mcodec_port_i); |
581 | |
582 | if (!mcodec_port_i) |
583 | goto mcodec_err; |
584 | |
585 | if (mcodec_port_i == mcodec_port) |
586 | break; |
587 | |
588 | codec_idx++; |
589 | } |
590 | |
591 | dai_link->ch_maps[*nm_idx].cpu = cpu_idx; |
592 | dai_link->ch_maps[*nm_idx].codec = codec_idx; |
593 | |
594 | (*nm_idx)++; |
595 | |
596 | of_node_put(node: mcodec_port_i); |
597 | mcodec_err: |
598 | of_node_put(node: mcodec_port); |
599 | of_node_put(node: mcpu_ep_n); |
600 | of_node_put(node: mcodec_ep_n); |
601 | } |
602 | mcpu_err: |
603 | of_node_put(node: mcpu_ep); |
604 | of_node_put(node: mcpu_port_top); |
605 | of_node_put(node: mcpu_ep_top); |
606 | of_node_put(node: mcodec_ep_top); |
607 | of_node_put(node: mcodec_port_top); |
608 | of_node_put(node: mcodec_ports); |
609 | |
610 | return ret; |
611 | } |
612 | |
613 | static int graph_parse_node_multi(struct simple_util_priv *priv, |
614 | enum graph_type gtype, |
615 | struct device_node *port, |
616 | struct link_info *li, int is_cpu) |
617 | { |
618 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
619 | struct device *dev = simple_priv_to_dev(priv); |
620 | struct device_node *ep; |
621 | int ret = -ENOMEM; |
622 | int nm_idx = 0; |
623 | int nm_max = max(dai_link->num_cpus, dai_link->num_codecs); |
624 | |
625 | /* |
626 | * create ch_maps if CPU:Codec = N:M |
627 | * DPCM is out of scope |
628 | */ |
629 | if (gtype != GRAPH_DPCM && !dai_link->ch_maps && |
630 | dai_link->num_cpus > 1 && dai_link->num_codecs > 1 && |
631 | dai_link->num_cpus != dai_link->num_codecs) { |
632 | |
633 | dai_link->ch_maps = devm_kcalloc(dev, n: nm_max, |
634 | size: sizeof(struct snd_soc_dai_link_ch_map), GFP_KERNEL); |
635 | if (!dai_link->ch_maps) |
636 | goto multi_err; |
637 | } |
638 | |
639 | for (int idx = 0;; idx++) { |
640 | /* |
641 | * multi { |
642 | * ports { |
643 | * <port> port@0 { ... }; // to pair |
644 | * port@1 { mcpu1_ep { ... = cpu1_ep };}; // Multi Element |
645 | * port@2 { mcpu2_ep { ... = cpu2_ep };}; // Multi Element |
646 | * }; |
647 | * }; |
648 | * |
649 | * cpu { |
650 | * ports { |
651 | * <ep> port@0 { cpu1_ep { ... = mcpu1_ep };}; |
652 | * }; |
653 | * }; |
654 | */ |
655 | ep = graph_get_next_multi_ep(port: &port); |
656 | if (!ep) |
657 | break; |
658 | |
659 | ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, idx); |
660 | of_node_put(node: ep); |
661 | if (ret < 0) |
662 | goto multi_err; |
663 | |
664 | /* CPU:Codec = N:M */ |
665 | if (is_cpu && dai_link->ch_maps) { |
666 | ret = graph_parse_node_multi_nm(dai_link, nm_idx: &nm_idx, cpu_idx: idx, mcpu_port: port); |
667 | if (ret < 0) |
668 | goto multi_err; |
669 | } |
670 | } |
671 | |
672 | if (is_cpu && dai_link->ch_maps && (nm_idx != nm_max)) |
673 | ret = -EINVAL; |
674 | |
675 | multi_err: |
676 | return ret; |
677 | } |
678 | |
679 | static int graph_parse_node_single(struct simple_util_priv *priv, |
680 | enum graph_type gtype, |
681 | struct device_node *port, |
682 | struct link_info *li, int is_cpu) |
683 | { |
684 | struct device_node *ep = port_to_endpoint(port); |
685 | int ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, idx: 0); |
686 | |
687 | of_node_put(node: ep); |
688 | |
689 | return ret; |
690 | } |
691 | |
692 | static int graph_parse_node(struct simple_util_priv *priv, |
693 | enum graph_type gtype, |
694 | struct device_node *port, |
695 | struct link_info *li, int is_cpu) |
696 | { |
697 | if (graph_lnk_is_multi(lnk: port)) |
698 | return graph_parse_node_multi(priv, gtype, port, li, is_cpu); |
699 | else |
700 | return graph_parse_node_single(priv, gtype, port, li, is_cpu); |
701 | } |
702 | |
703 | static void graph_parse_daifmt(struct device_node *node, |
704 | unsigned int *daifmt, unsigned int *bit_frame) |
705 | { |
706 | unsigned int fmt; |
707 | |
708 | /* |
709 | * see also above "daifmt" explanation |
710 | * and samples. |
711 | */ |
712 | |
713 | /* |
714 | * ports { |
715 | * (A) |
716 | * port { |
717 | * (B) |
718 | * endpoint { |
719 | * (C) |
720 | * }; |
721 | * }; |
722 | * }; |
723 | * }; |
724 | */ |
725 | |
726 | /* |
727 | * clock_provider: |
728 | * |
729 | * It can be judged it is provider |
730 | * if (A) or (B) or (C) has bitclock-master / frame-master flag. |
731 | * |
732 | * use "or" |
733 | */ |
734 | *bit_frame |= snd_soc_daifmt_parse_clock_provider_as_bitmap(node, NULL); |
735 | |
736 | #define update_daifmt(name) \ |
737 | if (!(*daifmt & SND_SOC_DAIFMT_##name##_MASK) && \ |
738 | (fmt & SND_SOC_DAIFMT_##name##_MASK)) \ |
739 | *daifmt |= fmt & SND_SOC_DAIFMT_##name##_MASK |
740 | |
741 | /* |
742 | * format |
743 | * |
744 | * This function is called by (C) -> (B) -> (A) order. |
745 | * Set if applicable part was not yet set. |
746 | */ |
747 | fmt = snd_soc_daifmt_parse_format(np: node, NULL); |
748 | update_daifmt(FORMAT); |
749 | update_daifmt(CLOCK); |
750 | update_daifmt(INV); |
751 | } |
752 | |
753 | static void graph_link_init(struct simple_util_priv *priv, |
754 | struct device_node *port, |
755 | struct link_info *li, |
756 | int is_cpu_node) |
757 | { |
758 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
759 | struct device_node *ep; |
760 | struct device_node *ports; |
761 | unsigned int daifmt = 0, daiclk = 0; |
762 | bool playback_only = 0, capture_only = 0; |
763 | unsigned int bit_frame = 0; |
764 | |
765 | if (graph_lnk_is_multi(lnk: port)) { |
766 | of_node_get(node: port); |
767 | ep = graph_get_next_multi_ep(port: &port); |
768 | port = of_get_parent(node: ep); |
769 | } else { |
770 | ep = port_to_endpoint(port); |
771 | } |
772 | |
773 | ports = of_get_parent(node: port); |
774 | |
775 | /* |
776 | * ports { |
777 | * (A) |
778 | * port { |
779 | * (B) |
780 | * endpoint { |
781 | * (C) |
782 | * }; |
783 | * }; |
784 | * }; |
785 | * }; |
786 | */ |
787 | graph_parse_daifmt(node: ep, daifmt: &daifmt, bit_frame: &bit_frame); /* (C) */ |
788 | graph_parse_daifmt(node: port, daifmt: &daifmt, bit_frame: &bit_frame); /* (B) */ |
789 | if (of_node_name_eq(np: ports, name: "ports" )) |
790 | graph_parse_daifmt(node: ports, daifmt: &daifmt, bit_frame: &bit_frame); /* (A) */ |
791 | |
792 | /* |
793 | * convert bit_frame |
794 | * We need to flip clock_provider if it was CPU node, |
795 | * because it is Codec base. |
796 | */ |
797 | daiclk = snd_soc_daifmt_clock_provider_from_bitmap(bit_frame); |
798 | if (is_cpu_node) |
799 | daiclk = snd_soc_daifmt_clock_provider_flipped(dai_fmt: daiclk); |
800 | |
801 | graph_util_parse_link_direction(np: port, is_playback_only: &playback_only, is_capture_only: &capture_only); |
802 | |
803 | dai_link->playback_only = playback_only; |
804 | dai_link->capture_only = capture_only; |
805 | |
806 | dai_link->dai_fmt = daifmt | daiclk; |
807 | dai_link->init = simple_util_dai_init; |
808 | dai_link->ops = &graph_ops; |
809 | if (priv->ops) |
810 | dai_link->ops = priv->ops; |
811 | } |
812 | |
813 | int audio_graph2_link_normal(struct simple_util_priv *priv, |
814 | struct device_node *lnk, |
815 | struct link_info *li) |
816 | { |
817 | struct device_node *cpu_port = lnk; |
818 | struct device_node *cpu_ep = port_to_endpoint(cpu_port); |
819 | struct device_node *codec_port = of_graph_get_remote_port(node: cpu_ep); |
820 | int ret; |
821 | |
822 | /* |
823 | * call Codec first. |
824 | * see |
825 | * __graph_parse_node() :: DAI Naming |
826 | */ |
827 | ret = graph_parse_node(priv, gtype: GRAPH_NORMAL, port: codec_port, li, is_cpu: 0); |
828 | if (ret < 0) |
829 | goto err; |
830 | |
831 | /* |
832 | * call CPU, and set DAI Name |
833 | */ |
834 | ret = graph_parse_node(priv, gtype: GRAPH_NORMAL, port: cpu_port, li, is_cpu: 1); |
835 | if (ret < 0) |
836 | goto err; |
837 | |
838 | graph_link_init(priv, port: cpu_port, li, is_cpu_node: 1); |
839 | err: |
840 | of_node_put(node: codec_port); |
841 | of_node_put(node: cpu_ep); |
842 | |
843 | return ret; |
844 | } |
845 | EXPORT_SYMBOL_GPL(audio_graph2_link_normal); |
846 | |
847 | int audio_graph2_link_dpcm(struct simple_util_priv *priv, |
848 | struct device_node *lnk, |
849 | struct link_info *li) |
850 | { |
851 | struct device_node *ep = port_to_endpoint(lnk); |
852 | struct device_node *rep = of_graph_get_remote_endpoint(node: ep); |
853 | struct device_node *rport = of_graph_get_remote_port(node: ep); |
854 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
855 | struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); |
856 | int is_cpu = graph_util_is_ports0(port: lnk); |
857 | int ret; |
858 | |
859 | if (is_cpu) { |
860 | /* |
861 | * dpcm { |
862 | * // Front-End |
863 | * ports@0 { |
864 | * => lnk: port@0 { ep: { ... = rep }; }; |
865 | * ... |
866 | * }; |
867 | * // Back-End |
868 | * ports@0 { |
869 | * ... |
870 | * }; |
871 | * }; |
872 | * |
873 | * CPU { |
874 | * rports: ports { |
875 | * rport: port@0 { rep: { ... = ep } }; |
876 | * } |
877 | * } |
878 | */ |
879 | /* |
880 | * setup CPU here, Codec is already set as dummy. |
881 | * see |
882 | * simple_util_init_priv() |
883 | */ |
884 | dai_link->dynamic = 1; |
885 | dai_link->dpcm_merged_format = 1; |
886 | |
887 | ret = graph_parse_node(priv, gtype: GRAPH_DPCM, port: rport, li, is_cpu: 1); |
888 | if (ret) |
889 | goto err; |
890 | } else { |
891 | /* |
892 | * dpcm { |
893 | * // Front-End |
894 | * ports@0 { |
895 | * ... |
896 | * }; |
897 | * // Back-End |
898 | * ports@0 { |
899 | * => lnk: port@0 { ep: { ... = rep; }; }; |
900 | * ... |
901 | * }; |
902 | * }; |
903 | * |
904 | * Codec { |
905 | * rports: ports { |
906 | * rport: port@0 { rep: { ... = ep; }; }; |
907 | * } |
908 | * } |
909 | */ |
910 | /* |
911 | * setup Codec here, CPU is already set as dummy. |
912 | * see |
913 | * simple_util_init_priv() |
914 | */ |
915 | |
916 | /* BE settings */ |
917 | dai_link->no_pcm = 1; |
918 | dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup; |
919 | |
920 | ret = graph_parse_node(priv, gtype: GRAPH_DPCM, port: rport, li, is_cpu: 0); |
921 | if (ret < 0) |
922 | goto err; |
923 | } |
924 | |
925 | graph_parse_convert(ep, props: dai_props); /* at node of <dpcm> */ |
926 | graph_parse_convert(ep: rep, props: dai_props); /* at node of <CPU/Codec> */ |
927 | |
928 | snd_soc_dai_link_set_capabilities(dai_link); |
929 | |
930 | graph_link_init(priv, port: rport, li, is_cpu_node: is_cpu); |
931 | err: |
932 | of_node_put(node: ep); |
933 | of_node_put(node: rep); |
934 | of_node_put(node: rport); |
935 | |
936 | return ret; |
937 | } |
938 | EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm); |
939 | |
940 | int audio_graph2_link_c2c(struct simple_util_priv *priv, |
941 | struct device_node *lnk, |
942 | struct link_info *li) |
943 | { |
944 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); |
945 | struct device_node *port0, *port1, *ports; |
946 | struct device_node *codec0_port, *codec1_port; |
947 | struct device_node *ep0, *ep1; |
948 | u32 val = 0; |
949 | int ret = -EINVAL; |
950 | |
951 | /* |
952 | * codec2codec { |
953 | * ports { |
954 | * rate = <48000>; |
955 | * => lnk: port@0 { c2c0_ep: { ... = codec0_ep; }; }; |
956 | * port@1 { c2c1_ep: { ... = codec1_ep; }; }; |
957 | * }; |
958 | * }; |
959 | * |
960 | * Codec { |
961 | * ports { |
962 | * port@0 { codec0_ep: ... }; }; |
963 | * port@1 { codec1_ep: ... }; }; |
964 | * }; |
965 | * }; |
966 | */ |
967 | of_node_get(node: lnk); |
968 | port0 = lnk; |
969 | ports = of_get_parent(node: port0); |
970 | port1 = of_get_next_child(node: ports, prev: lnk); |
971 | |
972 | /* |
973 | * Card2 can use original Codec2Codec settings if DT has. |
974 | * It will use default settings if no settings on DT. |
975 | * see |
976 | * simple_util_init_for_codec2codec() |
977 | * |
978 | * Add more settings here if needed |
979 | */ |
980 | of_property_read_u32(np: ports, propname: "rate" , out_value: &val); |
981 | if (val) { |
982 | struct device *dev = simple_priv_to_dev(priv); |
983 | struct snd_soc_pcm_stream *c2c_conf; |
984 | |
985 | c2c_conf = devm_kzalloc(dev, size: sizeof(*c2c_conf), GFP_KERNEL); |
986 | if (!c2c_conf) |
987 | goto err1; |
988 | |
989 | c2c_conf->formats = SNDRV_PCM_FMTBIT_S32_LE; /* update ME */ |
990 | c2c_conf->rates = SNDRV_PCM_RATE_8000_384000; |
991 | c2c_conf->rate_min = |
992 | c2c_conf->rate_max = val; |
993 | c2c_conf->channels_min = |
994 | c2c_conf->channels_max = 2; /* update ME */ |
995 | |
996 | dai_link->c2c_params = c2c_conf; |
997 | dai_link->num_c2c_params = 1; |
998 | } |
999 | |
1000 | ep0 = port_to_endpoint(port0); |
1001 | ep1 = port_to_endpoint(port1); |
1002 | |
1003 | codec0_port = of_graph_get_remote_port(node: ep0); |
1004 | codec1_port = of_graph_get_remote_port(node: ep1); |
1005 | |
1006 | /* |
1007 | * call Codec first. |
1008 | * see |
1009 | * __graph_parse_node() :: DAI Naming |
1010 | */ |
1011 | ret = graph_parse_node(priv, gtype: GRAPH_C2C, port: codec1_port, li, is_cpu: 0); |
1012 | if (ret < 0) |
1013 | goto err2; |
1014 | |
1015 | /* |
1016 | * call CPU, and set DAI Name |
1017 | */ |
1018 | ret = graph_parse_node(priv, gtype: GRAPH_C2C, port: codec0_port, li, is_cpu: 1); |
1019 | if (ret < 0) |
1020 | goto err2; |
1021 | |
1022 | graph_link_init(priv, port: codec0_port, li, is_cpu_node: 1); |
1023 | err2: |
1024 | of_node_put(node: ep0); |
1025 | of_node_put(node: ep1); |
1026 | of_node_put(node: codec0_port); |
1027 | of_node_put(node: codec1_port); |
1028 | err1: |
1029 | of_node_put(node: ports); |
1030 | of_node_put(node: port0); |
1031 | of_node_put(node: port1); |
1032 | |
1033 | return ret; |
1034 | } |
1035 | EXPORT_SYMBOL_GPL(audio_graph2_link_c2c); |
1036 | |
1037 | static int graph_link(struct simple_util_priv *priv, |
1038 | struct graph2_custom_hooks *hooks, |
1039 | enum graph_type gtype, |
1040 | struct device_node *lnk, |
1041 | struct link_info *li) |
1042 | { |
1043 | struct device *dev = simple_priv_to_dev(priv); |
1044 | GRAPH2_CUSTOM func = NULL; |
1045 | int ret = -EINVAL; |
1046 | |
1047 | switch (gtype) { |
1048 | case GRAPH_NORMAL: |
1049 | if (hooks && hooks->custom_normal) |
1050 | func = hooks->custom_normal; |
1051 | else |
1052 | func = audio_graph2_link_normal; |
1053 | break; |
1054 | case GRAPH_DPCM: |
1055 | if (hooks && hooks->custom_dpcm) |
1056 | func = hooks->custom_dpcm; |
1057 | else |
1058 | func = audio_graph2_link_dpcm; |
1059 | break; |
1060 | case GRAPH_C2C: |
1061 | if (hooks && hooks->custom_c2c) |
1062 | func = hooks->custom_c2c; |
1063 | else |
1064 | func = audio_graph2_link_c2c; |
1065 | break; |
1066 | default: |
1067 | break; |
1068 | } |
1069 | |
1070 | if (!func) { |
1071 | dev_err(dev, "non supported gtype (%d)\n" , gtype); |
1072 | goto err; |
1073 | } |
1074 | |
1075 | ret = func(priv, lnk, li); |
1076 | if (ret < 0) |
1077 | goto err; |
1078 | |
1079 | li->link++; |
1080 | err: |
1081 | return ret; |
1082 | } |
1083 | |
1084 | static int graph_counter(struct device_node *lnk) |
1085 | { |
1086 | /* |
1087 | * Multi CPU / Codec |
1088 | * |
1089 | * multi { |
1090 | * ports { |
1091 | * => lnk: port@0 { ... }; // to pair |
1092 | * port@1 { ... }; // Multi Element |
1093 | * port@2 { ... }; // Multi Element |
1094 | * ... |
1095 | * }; |
1096 | * }; |
1097 | * |
1098 | * ignore first lnk part |
1099 | */ |
1100 | if (graph_lnk_is_multi(lnk)) { |
1101 | struct device_node *ports = of_get_parent(node: lnk); |
1102 | struct device_node *port = NULL; |
1103 | int cnt = 0; |
1104 | |
1105 | /* |
1106 | * CPU/Codec = N:M case has many endpoints. |
1107 | * We can't use of_graph_get_endpoint_count() here |
1108 | */ |
1109 | while(1) { |
1110 | port = of_get_next_child(node: ports, prev: port); |
1111 | if (!port) |
1112 | break; |
1113 | cnt++; |
1114 | } |
1115 | |
1116 | return cnt - 1; |
1117 | } |
1118 | /* |
1119 | * Single CPU / Codec |
1120 | */ |
1121 | else |
1122 | return 1; |
1123 | } |
1124 | |
1125 | static int graph_count_normal(struct simple_util_priv *priv, |
1126 | struct device_node *lnk, |
1127 | struct link_info *li) |
1128 | { |
1129 | struct device_node *cpu_port = lnk; |
1130 | struct device_node *cpu_ep = port_to_endpoint(cpu_port); |
1131 | struct device_node *codec_port = of_graph_get_remote_port(node: cpu_ep); |
1132 | |
1133 | /* |
1134 | * CPU { |
1135 | * => lnk: port { endpoint { .. }; }; |
1136 | * }; |
1137 | */ |
1138 | /* |
1139 | * DON'T REMOVE platforms |
1140 | * see |
1141 | * simple-card.c :: simple_count_noml() |
1142 | */ |
1143 | li->num[li->link].cpus = |
1144 | li->num[li->link].platforms = graph_counter(lnk: cpu_port); |
1145 | |
1146 | li->num[li->link].codecs = graph_counter(lnk: codec_port); |
1147 | |
1148 | of_node_put(node: cpu_ep); |
1149 | of_node_put(node: codec_port); |
1150 | |
1151 | return 0; |
1152 | } |
1153 | |
1154 | static int graph_count_dpcm(struct simple_util_priv *priv, |
1155 | struct device_node *lnk, |
1156 | struct link_info *li) |
1157 | { |
1158 | struct device_node *ep = port_to_endpoint(lnk); |
1159 | struct device_node *rport = of_graph_get_remote_port(node: ep); |
1160 | |
1161 | /* |
1162 | * dpcm { |
1163 | * // Front-End |
1164 | * ports@0 { |
1165 | * => lnk: port@0 { endpoint { ... }; }; |
1166 | * ... |
1167 | * }; |
1168 | * // Back-End |
1169 | * ports@1 { |
1170 | * => lnk: port@0 { endpoint { ... }; }; |
1171 | * ... |
1172 | * }; |
1173 | * }; |
1174 | */ |
1175 | |
1176 | if (graph_util_is_ports0(port: lnk)) { |
1177 | /* |
1178 | * DON'T REMOVE platforms |
1179 | * see |
1180 | * simple-card.c :: simple_count_noml() |
1181 | */ |
1182 | li->num[li->link].cpus = graph_counter(lnk: rport); /* FE */ |
1183 | li->num[li->link].platforms = graph_counter(lnk: rport); |
1184 | } else { |
1185 | li->num[li->link].codecs = graph_counter(lnk: rport); /* BE */ |
1186 | } |
1187 | |
1188 | of_node_put(node: ep); |
1189 | of_node_put(node: rport); |
1190 | |
1191 | return 0; |
1192 | } |
1193 | |
1194 | static int graph_count_c2c(struct simple_util_priv *priv, |
1195 | struct device_node *lnk, |
1196 | struct link_info *li) |
1197 | { |
1198 | struct device_node *ports = of_get_parent(node: lnk); |
1199 | struct device_node *port0 = lnk; |
1200 | struct device_node *port1 = of_get_next_child(node: ports, prev: lnk); |
1201 | struct device_node *ep0 = port_to_endpoint(port0); |
1202 | struct device_node *ep1 = port_to_endpoint(port1); |
1203 | struct device_node *codec0 = of_graph_get_remote_port(node: ep0); |
1204 | struct device_node *codec1 = of_graph_get_remote_port(node: ep1); |
1205 | |
1206 | of_node_get(node: lnk); |
1207 | |
1208 | /* |
1209 | * codec2codec { |
1210 | * ports { |
1211 | * => lnk: port@0 { endpoint { ... }; }; |
1212 | * port@1 { endpoint { ... }; }; |
1213 | * }; |
1214 | * }; |
1215 | */ |
1216 | /* |
1217 | * DON'T REMOVE platforms |
1218 | * see |
1219 | * simple-card.c :: simple_count_noml() |
1220 | */ |
1221 | li->num[li->link].cpus = |
1222 | li->num[li->link].platforms = graph_counter(lnk: codec0); |
1223 | |
1224 | li->num[li->link].codecs = graph_counter(lnk: codec1); |
1225 | |
1226 | of_node_put(node: ports); |
1227 | of_node_put(node: port1); |
1228 | of_node_put(node: ep0); |
1229 | of_node_put(node: ep1); |
1230 | of_node_put(node: codec0); |
1231 | of_node_put(node: codec1); |
1232 | |
1233 | return 0; |
1234 | } |
1235 | |
1236 | static int graph_count(struct simple_util_priv *priv, |
1237 | struct graph2_custom_hooks *hooks, |
1238 | enum graph_type gtype, |
1239 | struct device_node *lnk, |
1240 | struct link_info *li) |
1241 | { |
1242 | struct device *dev = simple_priv_to_dev(priv); |
1243 | GRAPH2_CUSTOM func = NULL; |
1244 | int ret = -EINVAL; |
1245 | |
1246 | if (li->link >= SNDRV_MAX_LINKS) { |
1247 | dev_err(dev, "too many links\n" ); |
1248 | return ret; |
1249 | } |
1250 | |
1251 | switch (gtype) { |
1252 | case GRAPH_NORMAL: |
1253 | func = graph_count_normal; |
1254 | break; |
1255 | case GRAPH_DPCM: |
1256 | func = graph_count_dpcm; |
1257 | break; |
1258 | case GRAPH_C2C: |
1259 | func = graph_count_c2c; |
1260 | break; |
1261 | default: |
1262 | break; |
1263 | } |
1264 | |
1265 | if (!func) { |
1266 | dev_err(dev, "non supported gtype (%d)\n" , gtype); |
1267 | goto err; |
1268 | } |
1269 | |
1270 | ret = func(priv, lnk, li); |
1271 | if (ret < 0) |
1272 | goto err; |
1273 | |
1274 | li->link++; |
1275 | err: |
1276 | return ret; |
1277 | } |
1278 | |
1279 | static int graph_for_each_link(struct simple_util_priv *priv, |
1280 | struct graph2_custom_hooks *hooks, |
1281 | struct link_info *li, |
1282 | int (*func)(struct simple_util_priv *priv, |
1283 | struct graph2_custom_hooks *hooks, |
1284 | enum graph_type gtype, |
1285 | struct device_node *lnk, |
1286 | struct link_info *li)) |
1287 | { |
1288 | struct of_phandle_iterator it; |
1289 | struct device *dev = simple_priv_to_dev(priv); |
1290 | struct device_node *node = dev->of_node; |
1291 | struct device_node *lnk; |
1292 | enum graph_type gtype; |
1293 | int rc, ret; |
1294 | |
1295 | /* loop for all listed CPU port */ |
1296 | of_for_each_phandle(&it, rc, node, "links" , NULL, 0) { |
1297 | lnk = it.node; |
1298 | |
1299 | gtype = graph_get_type(priv, lnk); |
1300 | |
1301 | ret = func(priv, hooks, gtype, lnk, li); |
1302 | if (ret < 0) |
1303 | return ret; |
1304 | } |
1305 | |
1306 | return 0; |
1307 | } |
1308 | |
1309 | int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, |
1310 | struct graph2_custom_hooks *hooks) |
1311 | { |
1312 | struct snd_soc_card *card = simple_priv_to_card(priv); |
1313 | struct link_info *li; |
1314 | int ret; |
1315 | |
1316 | li = devm_kzalloc(dev, size: sizeof(*li), GFP_KERNEL); |
1317 | if (!li) |
1318 | return -ENOMEM; |
1319 | |
1320 | card->probe = graph_util_card_probe; |
1321 | card->owner = THIS_MODULE; |
1322 | card->dev = dev; |
1323 | |
1324 | if ((hooks) && (hooks)->hook_pre) { |
1325 | ret = (hooks)->hook_pre(priv); |
1326 | if (ret < 0) |
1327 | goto err; |
1328 | } |
1329 | |
1330 | ret = graph_for_each_link(priv, hooks, li, func: graph_count); |
1331 | if (!li->link) |
1332 | ret = -EINVAL; |
1333 | if (ret < 0) |
1334 | goto err; |
1335 | |
1336 | ret = simple_util_init_priv(priv, li); |
1337 | if (ret < 0) |
1338 | goto err; |
1339 | |
1340 | priv->pa_gpio = devm_gpiod_get_optional(dev, con_id: "pa" , flags: GPIOD_OUT_LOW); |
1341 | if (IS_ERR(ptr: priv->pa_gpio)) { |
1342 | ret = PTR_ERR(ptr: priv->pa_gpio); |
1343 | dev_err(dev, "failed to get amplifier gpio: %d\n" , ret); |
1344 | goto err; |
1345 | } |
1346 | |
1347 | ret = simple_util_parse_widgets(card, NULL); |
1348 | if (ret < 0) |
1349 | goto err; |
1350 | |
1351 | ret = simple_util_parse_routing(card, NULL); |
1352 | if (ret < 0) |
1353 | goto err; |
1354 | |
1355 | memset(li, 0, sizeof(*li)); |
1356 | ret = graph_for_each_link(priv, hooks, li, func: graph_link); |
1357 | if (ret < 0) |
1358 | goto err; |
1359 | |
1360 | ret = simple_util_parse_card_name(card, NULL); |
1361 | if (ret < 0) |
1362 | goto err; |
1363 | |
1364 | snd_soc_card_set_drvdata(card, data: priv); |
1365 | |
1366 | if ((hooks) && (hooks)->hook_post) { |
1367 | ret = (hooks)->hook_post(priv); |
1368 | if (ret < 0) |
1369 | goto err; |
1370 | } |
1371 | |
1372 | simple_util_debug_info(priv); |
1373 | |
1374 | ret = devm_snd_soc_register_card(dev, card); |
1375 | err: |
1376 | devm_kfree(dev, p: li); |
1377 | |
1378 | if (ret < 0) |
1379 | dev_err_probe(dev, err: ret, fmt: "parse error\n" ); |
1380 | |
1381 | return ret; |
1382 | } |
1383 | EXPORT_SYMBOL_GPL(audio_graph2_parse_of); |
1384 | |
1385 | static int graph_probe(struct platform_device *pdev) |
1386 | { |
1387 | struct simple_util_priv *priv; |
1388 | struct device *dev = &pdev->dev; |
1389 | |
1390 | /* Allocate the private data and the DAI link array */ |
1391 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
1392 | if (!priv) |
1393 | return -ENOMEM; |
1394 | |
1395 | return audio_graph2_parse_of(priv, dev, NULL); |
1396 | } |
1397 | |
1398 | static const struct of_device_id graph_of_match[] = { |
1399 | { .compatible = "audio-graph-card2" , }, |
1400 | {}, |
1401 | }; |
1402 | MODULE_DEVICE_TABLE(of, graph_of_match); |
1403 | |
1404 | static struct platform_driver graph_card = { |
1405 | .driver = { |
1406 | .name = "asoc-audio-graph-card2" , |
1407 | .pm = &snd_soc_pm_ops, |
1408 | .of_match_table = graph_of_match, |
1409 | }, |
1410 | .probe = graph_probe, |
1411 | .remove_new = simple_util_remove, |
1412 | }; |
1413 | module_platform_driver(graph_card); |
1414 | |
1415 | MODULE_ALIAS("platform:asoc-audio-graph-card2" ); |
1416 | MODULE_LICENSE("GPL v2" ); |
1417 | MODULE_DESCRIPTION("ASoC Audio Graph Card2" ); |
1418 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>" ); |
1419 | |