ASoC: audio-graph-card2: add CPU:Codec = N:M support
authorKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Mon, 13 Nov 2023 01:29:23 +0000 (01:29 +0000)
committerMark Brown <broonie@kernel.org>
Mon, 27 Nov 2023 13:44:01 +0000 (13:44 +0000)
Now ASoC is supporting CPU:Codec = N:M support.
This patch enables it on Audio Graph Card2.

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87r0ku4f0t.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/generic/audio-graph-card2.c

index c564f630abf62fd12b1cb882a54d5cdb0eca65bf..d9e10308a5081079b584916f362e37518bc1c022 100644 (file)
@@ -504,40 +504,203 @@ static int __graph_parse_node(struct simple_util_priv *priv,
        return 0;
 }
 
-static int graph_parse_node(struct simple_util_priv *priv,
-                           enum graph_type gtype,
-                           struct device_node *port,
-                           struct link_info *li, int is_cpu)
+static int graph_parse_node_multi_nm(struct snd_soc_dai_link *dai_link,
+                                    int *nm_idx, int cpu_idx,
+                                    struct device_node *mcpu_port)
 {
-       struct device_node *ep;
-       int ret = 0;
+       /*
+        *              +---+           +---+
+        *              |  X|<-@------->|x  |
+        *              |   |           |   |
+        *      cpu0 <--|A 1|<--------->|4 a|-> codec0
+        *      cpu1 <--|B 2|<-----+--->|5 b|-> codec1
+        *      cpu2 <--|C 3|<----/     +---+
+        *              +---+
+        *
+        * multi {
+        *      ports {
+        *              port@0 { mcpu_top_ep    {...  = mcodec_ep;      }; };   // (X) to pair
+        * <mcpu_port>  port@1 { mcpu0_ep       { ... = cpu0_ep;        };      // (A) Multi Element
+        *                       mcpu0_ep_0     { ... = mcodec0_ep_0;   }; };   // (1) connected Codec
+        *              port@2 { mcpu1_ep       { ... = cpu1_ep;        };      // (B) Multi Element
+        *                       mcpu1_ep_0     { ... = mcodec1_ep_0;   }; };   // (2) connected Codec
+        *              port@3 { mcpu2_ep       { ... = cpu2_ep;        };      // (C) Multi Element
+        *                       mcpu2_ep_0     { ... = mcodec1_ep_1;   }; };   // (3) connected Codec
+        *      };
+        *
+        *      ports {
+        *              port@0 { mcodec_top_ep  {...  = mcpu_ep;        }; };   // (x) to pair
+        * <mcodec_port>port@1 { mcodec0_ep     { ... = codec0_ep;      };      // (a) Multi Element
+        *                       mcodec0_ep_0   { ... = mcpu0_ep_0;     }; };   // (4) connected CPU
+        *              port@2 { mcodec1_ep     { ... = codec1_ep;      };      // (b) Multi Element
+        *                       mcodec1_ep_0   { ... = mcpu1_ep_0;     };      // (5) connected CPU
+        *                       mcodec1_ep_1   { ... = mcpu2_ep_0;     }; };   // (5) connected CPU
+        *      };
+        * };
+        */
+       struct device_node *mcpu_ep             = port_to_endpoint(mcpu_port);
+       struct device_node *mcpu_ep_n           = mcpu_ep;
+       struct device_node *mcpu_port_top       = of_get_next_child(of_get_parent(mcpu_port), NULL);
+       struct device_node *mcpu_ep_top         = port_to_endpoint(mcpu_port_top);
+       struct device_node *mcodec_ep_top       = of_graph_get_remote_endpoint(mcpu_ep_top);
+       struct device_node *mcodec_port_top     = of_get_parent(mcodec_ep_top);
+       struct device_node *mcodec_ports        = of_get_parent(mcodec_port_top);
+       int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);
+       int ret = -EINVAL;
 
-       if (graph_lnk_is_multi(port)) {
-               int idx;
+       if (cpu_idx > dai_link->num_cpus)
+               goto mcpu_err;
 
-               of_node_get(port);
+       while (1) {
+               struct device_node *mcodec_ep_n;
+               struct device_node *mcodec_port_i;
+               struct device_node *mcodec_port;
+               int codec_idx;
 
-               for (idx = 0;; idx++) {
-                       ep = graph_get_next_multi_ep(&port);
-                       if (!ep)
-                               break;
+               if (*nm_idx > nm_max)
+                       break;
 
-                       ret = __graph_parse_node(priv, gtype, ep,
-                                                li, is_cpu, idx);
-                       of_node_put(ep);
-                       if (ret < 0)
+               mcpu_ep_n = of_get_next_child(mcpu_port, mcpu_ep_n);
+               if (!mcpu_ep_n) {
+                       ret = 0;
+                       break;
+               }
+
+               mcodec_ep_n     = of_graph_get_remote_endpoint(mcpu_ep_n);
+               mcodec_port     = of_get_parent(mcodec_ep_n);
+
+               if (mcodec_ports != of_get_parent(mcodec_port))
+                       goto mcpu_err;
+
+               codec_idx = 0;
+               mcodec_port_i = of_get_next_child(mcodec_ports, NULL);
+               while (1) {
+                       if (codec_idx > dai_link->num_codecs)
+                               goto mcodec_err;
+
+                       mcodec_port_i = of_get_next_child(mcodec_ports, mcodec_port_i);
+
+                       if (!mcodec_port_i)
+                               goto mcodec_err;
+
+                       if (mcodec_port_i == mcodec_port)
                                break;
+
+                       codec_idx++;
                }
-       } else {
-               /* Single CPU / Codec */
-               ep = port_to_endpoint(port);
-               ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, 0);
+
+               dai_link->ch_maps[*nm_idx].cpu          = cpu_idx;
+               dai_link->ch_maps[*nm_idx].codec        = codec_idx;
+
+               (*nm_idx)++;
+
+               of_node_put(mcodec_port_i);
+mcodec_err:
+               of_node_put(mcodec_port);
+               of_node_put(mcpu_ep_n);
+               of_node_put(mcodec_ep_n);
+       }
+mcpu_err:
+       of_node_put(mcpu_ep);
+       of_node_put(mcpu_port_top);
+       of_node_put(mcpu_ep_top);
+       of_node_put(mcodec_ep_top);
+       of_node_put(mcodec_port_top);
+       of_node_put(mcodec_ports);
+
+       return ret;
+}
+
+static int graph_parse_node_multi(struct simple_util_priv *priv,
+                                 enum graph_type gtype,
+                                 struct device_node *port,
+                                 struct link_info *li, int is_cpu)
+{
+       struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
+       struct device *dev = simple_priv_to_dev(priv);
+       struct device_node *ep;
+       int ret = -ENOMEM;
+       int nm_idx = 0;
+       int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);
+
+       /*
+        * create ch_maps if CPU:Codec = N:M
+        * DPCM is out of scope
+        */
+       if (gtype != GRAPH_DPCM && !dai_link->ch_maps &&
+           dai_link->num_cpus > 1 && dai_link->num_codecs > 1 &&
+           dai_link->num_cpus != dai_link->num_codecs) {
+
+               dai_link->ch_maps = devm_kcalloc(dev, nm_max,
+                                       sizeof(struct snd_soc_dai_link_ch_map), GFP_KERNEL);
+               if (!dai_link->ch_maps)
+                       goto multi_err;
+       }
+
+       for (int idx = 0;; idx++) {
+               /*
+                * multi {
+                *      ports {
+                * <port>       port@0 { ...                        }; // to pair
+                *              port@1 { mcpu1_ep { ... = cpu1_ep };}; // Multi Element
+                *              port@2 { mcpu2_ep { ... = cpu2_ep };}; // Multi Element
+                *      };
+                * };
+                *
+                * cpu {
+                *      ports {
+                * <ep>         port@0 { cpu1_ep   { ... = mcpu1_ep };};
+                *      };
+                * };
+                */
+               ep = graph_get_next_multi_ep(&port);
+               if (!ep)
+                       break;
+
+               ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, idx);
                of_node_put(ep);
+               if (ret < 0)
+                       goto multi_err;
+
+               /* CPU:Codec = N:M */
+               if (is_cpu && dai_link->ch_maps) {
+                       ret = graph_parse_node_multi_nm(dai_link, &nm_idx, idx, port);
+                       if (ret < 0)
+                               goto multi_err;
+               }
        }
 
+       if (is_cpu && dai_link->ch_maps && (nm_idx != nm_max))
+               ret = -EINVAL;
+
+multi_err:
        return ret;
 }
 
+static int graph_parse_node_single(struct simple_util_priv *priv,
+                                  enum graph_type gtype,
+                                  struct device_node *port,
+                                  struct link_info *li, int is_cpu)
+{
+       struct device_node *ep = port_to_endpoint(port);
+       int ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, 0);
+
+       of_node_put(ep);
+
+       return ret;
+}
+
+static int graph_parse_node(struct simple_util_priv *priv,
+                           enum graph_type gtype,
+                           struct device_node *port,
+                           struct link_info *li, int is_cpu)
+{
+       if (graph_lnk_is_multi(port))
+               return graph_parse_node_multi(priv, gtype, port, li, is_cpu);
+       else
+               return graph_parse_node_single(priv, gtype, port, li, is_cpu);
+}
+
 static void graph_parse_daifmt(struct device_node *node,
                               unsigned int *daifmt, unsigned int *bit_frame)
 {
@@ -929,8 +1092,24 @@ static int graph_counter(struct device_node *lnk)
         *
         * ignore first lnk part
         */
-       if (graph_lnk_is_multi(lnk))
-               return of_graph_get_endpoint_count(of_get_parent(lnk)) - 1;
+       if (graph_lnk_is_multi(lnk)) {
+               struct device_node *ports = of_get_parent(lnk);
+               struct device_node *port = NULL;
+               int cnt = 0;
+
+               /*
+                * CPU/Codec = N:M case has many endpoints.
+                * We can't use of_graph_get_endpoint_count() here
+                */
+               while(1) {
+                       port = of_get_next_child(ports, port);
+                       if (!port)
+                               break;
+                       cnt++;
+               }
+
+               return cnt - 1;
+       }
        /*
         * Single CPU / Codec
         */