ASoC: ti: davinci-mcasp: Add audio-graph-card2 and DPCM support

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>
This commit is contained in:
Sen Wang 2026-06-03 16:18:30 -05:00 committed by Mark Brown
parent b7e44d1986
commit 216f7452a6
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0

View 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;