]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ASoC: ti: davinci-mcasp: Add audio-graph-card2 and DPCM support
authorSen Wang <sen@ti.com>
Wed, 3 Jun 2026 21:18:30 +0000 (16:18 -0500)
committerMark Brown <broonie@kernel.org>
Thu, 11 Jun 2026 19:48:11 +0000 (20:48 +0100)
Extend the McASP driver to support audio-graph-card2 of-graph topology,
while maintaining backwards compatibility for existing simple-audio-card
phandles and machine drivers, which now uses the default MCASP_GRAPH_NONE
code path.

Signed-off-by: Sen Wang <sen@ti.com>
Link: https://patch.msgid.link/20260603211835.635184-1-sen@ti.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/ti/davinci-mcasp.c

index f229f847eaf4ec50ecb62773356c2c5ac93c45b5..79239131b6598e3808b3bb3bfd5a78c0bf34e758 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
+#include <linux/of_graph.h>
 #include <linux/platform_data/davinci_asp.h>
 #include <linux/math64.h>
 #include <linux/bitmap.h>
@@ -74,6 +75,14 @@ struct davinci_mcasp_ruledata {
        int stream;
 };
 
+enum mcasp_graph_mode {
+       MCASP_GRAPH_NONE,       /* 1:1, simple-audio-card, no of-graph endpoints */
+       MCASP_GRAPH_PORT,       /* 1:1, audio-graph-card: port { endpoint } */
+       MCASP_GRAPH_PORTS,      /* 1:N, audio-graph-card2 non-DPCM: ports { port@0; ... } */
+       MCASP_GRAPH_DPCM,       /* N:M, audio-graph-card2 DPCM: N FE DAIs, detected via */
+                               /* remote "dpcm" node in the sound card DT */
+};
+
 struct davinci_mcasp {
        struct snd_dmaengine_dai_dma_data dma_data[2];
        struct davinci_mcasp_pdata *pdata;
@@ -124,6 +133,10 @@ struct davinci_mcasp {
        int     max_format_width;
        u8      active_serializers[2];
 
+       /* Audio graph support */
+       enum mcasp_graph_mode graph_mode;
+       int     num_dais;
+
 #ifdef CONFIG_GPIOLIB
        struct gpio_chip gpio_chip;
 #endif
@@ -1486,7 +1499,18 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream,
                return -EINVAL;
        }
 
-       ret = davinci_mcasp_set_dai_fmt(cpu_dai, mcasp->dai_fmt);
+       struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+       unsigned int cpu_fmt;
+
+       if (mcasp->graph_mode != MCASP_GRAPH_NONE && rtd->dai_link->dai_fmt)
+               /* clock provider bits stored separately in ext_fmt */
+               cpu_fmt = snd_soc_daifmt_clock_provider_flipped(
+                               rtd->dai_link->dai_fmt) |
+                         rtd->dai_link->cpus[0].ext_fmt;
+       else
+               cpu_fmt = mcasp->dai_fmt;
+
+       ret = davinci_mcasp_set_dai_fmt(cpu_dai, cpu_fmt);
        if (ret)
                return ret;
 
@@ -2029,8 +2053,31 @@ static struct snd_soc_dai_driver davinci_mcasp_dai[] = {
 
 };
 
+/* Translate of-graph endpoint to DAI ID (DPCM: port reg; else 0). */
+static int davinci_mcasp_of_xlate_dai_id(struct snd_soc_component *component,
+                                        struct device_node *endpoint)
+{
+       struct davinci_mcasp *mcasp = snd_soc_component_get_drvdata(component);
+       struct device_node *port;
+       u32 port_reg = 0;
+
+       if (mcasp->graph_mode != MCASP_GRAPH_DPCM)
+               return 0;
+
+       /* endpoint is inside mcasp/ports/port@N — read port's reg */
+       port = of_get_parent(endpoint);
+       if (!port)
+               return -EINVAL;
+
+       of_property_read_u32(port, "reg", &port_reg);
+       of_node_put(port);
+
+       return port_reg;
+}
+
 static const struct snd_soc_component_driver davinci_mcasp_component = {
        .name                   = "davinci-mcasp",
+       .of_xlate_dai_id        = davinci_mcasp_of_xlate_dai_id,
        .legacy_dai_naming      = 1,
 };
 
@@ -2138,6 +2185,88 @@ static bool davinci_mcasp_have_gpiochip(struct davinci_mcasp *mcasp)
        return device_property_present(mcasp->dev, "gpio-controller");
 }
 
+/* Return true if the remote sound card uses a "dpcm" container. */
+static bool mcasp_detect_dpcm(struct device_node *np)
+{
+       struct device_node *ep, *remote_ep;
+       struct device_node *port, *container, *parent;
+       bool is_dpcm = false;
+
+       /* Grab the first endpoint under this McASP node */
+       ep = of_graph_get_next_endpoint(np, NULL);
+       if (!ep)
+               return false;
+
+       /* Follow remote-endpoint phandle into the card node */
+       remote_ep = of_graph_get_remote_endpoint(ep);
+       of_node_put(ep);
+       if (!remote_ep)
+               return false;
+
+       /* Traverse the remote: remote_ep -> port -> ports@N -> dpcm */
+       port = of_get_parent(remote_ep);
+       of_node_put(remote_ep);
+       if (!port)
+               return false;
+
+       container = of_get_parent(port);
+       of_node_put(port);
+       if (!container)
+               return false;
+
+       if (of_node_name_eq(container, "ports")) {
+               parent = of_get_parent(container);
+               of_node_put(container);
+               if (parent) {
+                       is_dpcm = of_node_name_eq(parent, "dpcm");
+                       of_node_put(parent);
+               }
+       } else {
+               of_node_put(container);
+       }
+
+       return is_dpcm;
+}
+
+/* Detect audio-graph topology and return the number of DAIs to register. */
+static int davinci_mcasp_parse_of_graph(struct davinci_mcasp *mcasp,
+                                       struct device_node *np)
+{
+       struct device_node *port, *ports;
+       int num_dais = 0;
+
+       mcasp->graph_mode = MCASP_GRAPH_NONE;
+
+       /* audio-graph-card2: ports { port@0 ... }; DPCM -> N DAIs, else 1 */
+       ports = of_get_child_by_name(np, "ports");
+       if (ports) {
+               int port_count = of_get_child_count(ports);
+
+               of_node_put(ports);
+
+               if (mcasp_detect_dpcm(np)) {
+                       num_dais = port_count;
+                       mcasp->graph_mode = MCASP_GRAPH_DPCM;
+               } else {
+                       num_dais = 1;
+                       mcasp->graph_mode = MCASP_GRAPH_PORTS;
+               }
+
+               return num_dais;
+       }
+
+       /* audio-graph-card: single port { endpoint } */
+       port = of_get_child_by_name(np, "port");
+       if (port) {
+               num_dais = of_graph_get_endpoint_count(port);
+               if (num_dais > 0)
+                       mcasp->graph_mode = MCASP_GRAPH_PORT;
+               of_node_put(port);
+       }
+
+       return num_dais ? num_dais : 1;
+}
+
 static int davinci_mcasp_get_config(struct davinci_mcasp *mcasp,
                                    struct platform_device *pdev)
 {
@@ -2555,6 +2684,53 @@ static inline int davinci_mcasp_init_gpiochip(struct davinci_mcasp *mcasp)
 }
 #endif /* CONFIG_GPIOLIB */
 
+static int davinci_mcasp_register_component(struct davinci_mcasp *mcasp,
+                                           struct platform_device *pdev)
+{
+       struct snd_soc_dai_driver *dais;
+       int i;
+
+       if (mcasp->graph_mode == MCASP_GRAPH_NONE || mcasp->num_dais <= 1)
+               return devm_snd_soc_register_component(&pdev->dev,
+                                                      &davinci_mcasp_component,
+                                                      &davinci_mcasp_dai[mcasp->op_mode], 1);
+
+       dais = devm_kcalloc(&pdev->dev, mcasp->num_dais, sizeof(*dais),
+                           GFP_KERNEL);
+       if (!dais)
+               return -ENOMEM;
+
+       for (i = 0; i < mcasp->num_dais; i++) {
+               memcpy(&dais[i], &davinci_mcasp_dai[mcasp->op_mode],
+                      sizeof(*dais));
+               dais[i].id = i;
+               dais[i].name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+                                             "davinci-mcasp.%d", i);
+               if (!dais[i].name)
+                       return -ENOMEM;
+
+               /* Unique stream names per DAI */
+               if (dais[i].playback.channels_min) {
+                       dais[i].playback.stream_name =
+                               devm_kasprintf(&pdev->dev, GFP_KERNEL,
+                                              "Playback Port %d", i);
+                       if (!dais[i].playback.stream_name)
+                               return -ENOMEM;
+               }
+               if (dais[i].capture.channels_min) {
+                       dais[i].capture.stream_name =
+                               devm_kasprintf(&pdev->dev, GFP_KERNEL,
+                                              "Capture Port %d", i);
+                       if (!dais[i].capture.stream_name)
+                               return -ENOMEM;
+               }
+       }
+
+       return devm_snd_soc_register_component(&pdev->dev,
+                                              &davinci_mcasp_component,
+                                              dais, mcasp->num_dais);
+}
+
 static int davinci_mcasp_probe(struct platform_device *pdev)
 {
        struct snd_dmaengine_dai_dma_data *dma_data;
@@ -2760,9 +2936,10 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
                goto err;
        }
 
-       ret = devm_snd_soc_register_component(&pdev->dev, &davinci_mcasp_component,
-                                             &davinci_mcasp_dai[mcasp->op_mode], 1);
+       /* Parse audio-graph structure and register DAIs */
+       mcasp->num_dais = davinci_mcasp_parse_of_graph(mcasp, pdev->dev.of_node);
 
+       ret = davinci_mcasp_register_component(mcasp, pdev);
        if (ret != 0)
                goto err;