diff --git a/include/sound/graph_card.h b/include/sound/graph_card.h index ece78a84391c..4c8b94c77b8e 100644 --- a/include/sound/graph_card.h +++ b/include/sound/graph_card.h @@ -18,6 +18,7 @@ struct graph2_custom_hooks { int (*hook_post)(struct asoc_simple_priv *priv); GRAPH2_CUSTOM custom_normal; GRAPH2_CUSTOM custom_dpcm; + GRAPH2_CUSTOM custom_c2c; }; int audio_graph_parse_of(struct asoc_simple_priv *priv, struct device *dev); @@ -28,5 +29,7 @@ int audio_graph2_link_normal(struct asoc_simple_priv *priv, struct device_node *lnk, struct link_info *li); int audio_graph2_link_dpcm(struct asoc_simple_priv *priv, struct device_node *lnk, struct link_info *li); +int audio_graph2_link_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, struct link_info *li); #endif /* __GRAPH_CARD_H */ diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 56e9e6c3b86e..b6049bcfb771 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -176,17 +176,54 @@ links indicates connection part of CPU side (= A). }; }; + ************************************ + Codec to Codec + ************************************ + + +--+ + | |<-- Codec0 <- IN + | |--> Codec1 -> OUT + +--+ + + sound { + compatible = "audio-graph-card2"; + + routing = "OUT" ,"DAI1 Playback", + "DAI0 Capture", "IN"; + + links = <&c2c>; + + codec2codec { + ports { + rate = <48000>; + c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec0_ep>; }; }; + port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; + }; + }; + + Codec { + ports { + port@0 { + bitclock-master; + frame-master; + codec0_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; }; + port@1 { codec1_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; }; + }; + }; + */ enum graph_type { GRAPH_NORMAL, GRAPH_DPCM, + GRAPH_C2C, GRAPH_MULTI, /* don't use ! Use this only in __graph_get_type() */ }; #define GRAPH_NODENAME_MULTI "multi" #define GRAPH_NODENAME_DPCM "dpcm" +#define GRAPH_NODENAME_C2C "codec2codec" #define port_to_endpoint(port) of_get_child_by_name(port, "endpoint") @@ -212,6 +249,9 @@ static enum graph_type __graph_get_type(struct device_node *lnk) if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) return GRAPH_DPCM; + if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) + return GRAPH_C2C; + return GRAPH_NORMAL; } @@ -236,6 +276,9 @@ static enum graph_type graph_get_type(struct asoc_simple_priv *priv, else str = "DPCM Back-End"; break; + case GRAPH_C2C: + str = "Codec2Codec"; + break; default: break; } @@ -494,6 +537,13 @@ static int __graph_parse_node(struct asoc_simple_priv *priv, asoc_simple_set_dailink_name(dev, dai_link, "be.%pOFP.%s%s", codecs->of_node, codecs->dai_name, codec_multi); break; + case GRAPH_C2C: + /* run is_cpu only. see audio_graph2_link_c2c() */ + if (is_cpu) + asoc_simple_set_dailink_name(dev, dai_link, "c2c.%s%s-%s%s", + cpus->dai_name, cpu_multi, + codecs->dai_name, codec_multi); + break; default: break; } @@ -792,6 +842,91 @@ err: } EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm); +int audio_graph2_link_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); + struct snd_soc_pcm_stream *c2c_conf = dai_props->c2c_conf; + struct device_node *port0, *port1, *ports; + struct device_node *codec0_port, *codec1_port; + struct device_node *ep0, *ep1; + u32 val; + int ret = -EINVAL; + + /* + * codec2codec { + * ports { + * rate = <48000>; + * => lnk: port@0 { c2c0_ep: { ... = codec0_ep; }; }; + * port@1 { c2c1_ep: { ... = codec1_ep; }; }; + * }; + * }; + * + * Codec { + * ports { + * port@0 { codec0_ep: ... }; }; + * port@1 { codec1_ep: ... }; }; + * }; + * }; + */ + of_node_get(lnk); + port0 = lnk; + ports = of_get_parent(port0); + port1 = of_get_next_child(ports, lnk); + + if (!of_get_property(ports, "rate", &val)) { + struct device *dev = simple_priv_to_dev(priv); + + dev_err(dev, "Codec2Codec needs rate settings\n"); + goto err1; + } + + c2c_conf->formats = SNDRV_PCM_FMTBIT_S32_LE; /* update ME */ + c2c_conf->rate_min = + c2c_conf->rate_max = val; + c2c_conf->channels_min = + c2c_conf->channels_max = 2; /* update ME */ + dai_link->params = c2c_conf; + + ep0 = port_to_endpoint(port0); + ep1 = port_to_endpoint(port1); + + codec0_port = of_graph_get_remote_port(ep0); + codec1_port = of_graph_get_remote_port(ep1); + + /* + * call Codec first. + * see + * __graph_parse_node() :: DAI Naming + */ + ret = graph_parse_node(priv, GRAPH_C2C, codec1_port, li, 0); + if (ret < 0) + goto err2; + + /* + * call CPU, and set DAI Name + */ + ret = graph_parse_node(priv, GRAPH_C2C, codec0_port, li, 1); + if (ret < 0) + goto err2; + + graph_link_init(priv, codec0_port, li, 1); +err2: + of_node_put(ep0); + of_node_put(ep1); + of_node_put(codec0_port); + of_node_put(codec1_port); +err1: + of_node_put(ports); + of_node_put(port0); + of_node_put(port1); + + return ret; +} +EXPORT_SYMBOL_GPL(audio_graph2_link_c2c); + static int graph_link(struct asoc_simple_priv *priv, struct graph2_custom_hooks *hooks, enum graph_type gtype, @@ -815,6 +950,12 @@ static int graph_link(struct asoc_simple_priv *priv, else func = audio_graph2_link_dpcm; break; + case GRAPH_C2C: + if (hooks && hooks->custom_c2c) + func = hooks->custom_c2c; + else + func = audio_graph2_link_c2c; + break; default: break; } @@ -916,6 +1057,43 @@ static int graph_count_dpcm(struct asoc_simple_priv *priv, return 0; } +static int graph_count_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device_node *ports = of_get_parent(lnk); + struct device_node *port0 = lnk; + struct device_node *port1 = of_get_next_child(ports, lnk); + struct device_node *ep0 = port_to_endpoint(port0); + struct device_node *ep1 = port_to_endpoint(port1); + struct device_node *codec0 = of_graph_get_remote_port(ep0); + struct device_node *codec1 = of_graph_get_remote_port(ep1); + + of_node_get(lnk); + + /* + * codec2codec { + * ports { + * => lnk: port@0 { endpoint { ... }; }; + * port@1 { endpoint { ... }; }; + * }; + * }; + */ + li->num[li->link].cpus = + li->num[li->link].platforms = graph_counter(codec0); + li->num[li->link].codecs = graph_counter(codec1); + li->num[li->link].c2c = 1; + + of_node_put(ports); + of_node_put(port1); + of_node_put(ep0); + of_node_put(ep1); + of_node_put(codec0); + of_node_put(codec1); + + return 0; +} + static int graph_count(struct asoc_simple_priv *priv, struct graph2_custom_hooks *hooks, enum graph_type gtype, @@ -938,6 +1116,9 @@ static int graph_count(struct asoc_simple_priv *priv, case GRAPH_DPCM: func = graph_count_dpcm; break; + case GRAPH_C2C: + func = graph_count_c2c; + break; default: break; }