Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Generic drivers"
config SND_SIMPLE_CARD_UTILS
tristate
config SND_SIMPLE_CARD
tristate "ASoC Simple sound card support"
select SND_SIMPLE_CARD_UTILS
help
This option enables generic simple sound card support
It also support DPCM of multi CPU single Codec ststem.
config SND_AUDIO_GRAPH_CARD
tristate "ASoC Audio Graph sound card support"
depends on OF
select SND_SIMPLE_CARD_UTILS
help
This option enables generic simple sound card support
with OF-graph DT bindings.
It also support DPCM of multi CPU single Codec ststem.
config SND_AUDIO_GRAPH_CARD2
tristate "ASoC Audio Graph sound card2 support"
depends on OF
select SND_SIMPLE_CARD_UTILS
help
This option enables generic simple sound card2 support
with OF-graph DT bindings.
config SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE
tristate "ASoC Audio Graph Card2 base custom sample support"
depends on SND_AUDIO_GRAPH_CARD2
help
This option enables Audio Graph Card2 base custom sample
config SND_TEST_COMPONENT
tristate "ASoC Test component sound support"
depends on OF
help
This option enables test component sound driver support.
endmenu
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
snd-soc-simple-card-utils-y := simple-card-utils.o
snd-soc-simple-card-y := simple-card.o
snd-soc-audio-graph-card-y := audio-graph-card.o
snd-soc-audio-graph-card2-y := audio-graph-card2.o
snd-soc-audio-graph-card2-custom-sample-y := audio-graph-card2-custom-sample.o
snd-soc-test-component-y := test-component.o
obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o
obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o
obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o
obj-$(CONFIG_SND_AUDIO_GRAPH_CARD2) += snd-soc-audio-graph-card2.o
obj-$(CONFIG_SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE) += snd-soc-audio-graph-card2-custom-sample.o
obj-$(CONFIG_SND_TEST_COMPONENT) += snd-soc-test-component.o
@@ -0,0 +1,661 @@
// SPDX-License-Identifier: GPL-2.0
//
// ASoC audio graph sound card support
//
// Copyright (C) 2016 Renesas Solutions Corp.
// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
//
// based on ${LINUX}/sound/soc/generic/simple-card.c
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <sound/graph_card.h>
#define DPCM_SELECTABLE 1
#define graph_ret(priv, ret) _graph_ret(priv, __func__, ret)
static inline int _graph_ret(struct simple_util_priv *priv,
const char *func, int ret)
{
return snd_soc_ret(simple_priv_to_dev(priv), ret, "at %s()\n", func);
}
#define ep_to_port(ep) of_get_parent(ep)
static struct device_node *port_to_ports(struct device_node *port)
{
struct device_node *ports = of_get_parent(port);
if (!of_node_name_eq(ports, "ports")) {
of_node_put(ports);
return NULL;
}
return ports;
}
static int graph_outdrv_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int event)
{
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
struct simple_util_priv *priv = snd_soc_card_get_drvdata(card);
switch (event) {
case SND_SOC_DAPM_POST_PMU:
gpiod_set_value_cansleep(priv->pa_gpio, 1);
break;
case SND_SOC_DAPM_PRE_PMD:
gpiod_set_value_cansleep(priv->pa_gpio, 0);
break;
default:
return -EINVAL;
}
return 0;
}
static const struct snd_soc_dapm_widget graph_dapm_widgets[] = {
SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM,
0, 0, NULL, 0, graph_outdrv_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
};
static const struct snd_soc_ops graph_ops = {
.startup = simple_util_startup,
.shutdown = simple_util_shutdown,
.hw_params = simple_util_hw_params,
};
static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc)
{
struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc);
if (dai && (dai->component->driver->pcm_new ||
(dai->driver->ops && dai->driver->ops->pcm_new)))
return true;
return false;
}
static void graph_parse_convert(struct device *dev,
struct device_node *ep,
struct simple_util_data *adata)
{
struct device_node *top = dev->of_node;
struct device_node *port __free(device_node) = ep_to_port(ep);
struct device_node *ports __free(device_node) = port_to_ports(port);
struct device_node *node __free(device_node) = of_graph_get_port_parent(ep);
simple_util_parse_convert(top, NULL, adata);
simple_util_parse_convert(ports, NULL, adata);
simple_util_parse_convert(port, NULL, adata);
simple_util_parse_convert(ep, NULL, adata);
}
static int graph_parse_node(struct simple_util_priv *priv,
struct device_node *ep,
struct link_info *li,
int *cpu)
{
struct device *dev = simple_priv_to_dev(priv);
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_dai_link_component *dlc;
struct simple_util_dai *dai;
int ret;
if (cpu) {
dlc = snd_soc_link_to_cpu(dai_link, 0);
dai = simple_props_to_dai_cpu(dai_props, 0);
} else {
dlc = snd_soc_link_to_codec(dai_link, 0);
dai = simple_props_to_dai_codec(dai_props, 0);
}
ret = graph_util_parse_dai(priv, ep, dlc, cpu);
if (ret < 0)
goto end;
ret = simple_util_parse_tdm(ep, dai);
if (ret < 0)
goto end;
ret = simple_util_parse_clk(dev, ep, dai, dlc);
end:
return graph_ret(priv, ret);
}
static int graph_link_init(struct simple_util_priv *priv,
struct device_node *ep_cpu,
struct device_node *ep_codec,
struct link_info *li,
char *name)
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *top = dev->of_node;
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 device_node *port_cpu __free(device_node) = ep_to_port(ep_cpu);
struct device_node *port_codec __free(device_node) = ep_to_port(ep_codec);
struct device_node *ports_cpu __free(device_node) = port_to_ports(port_cpu);
struct device_node *ports_codec __free(device_node) = port_to_ports(port_codec);
enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;
enum snd_soc_trigger_order trigger_stop = SND_SOC_TRIGGER_ORDER_DEFAULT;
bool playback_only = 0, capture_only = 0;
int ret;
ret = simple_util_parse_daifmt(dev, ep_cpu, ep_codec,
NULL, &dai_link->dai_fmt);
if (ret < 0)
goto end;
graph_util_parse_link_direction(top, &playback_only, &capture_only);
graph_util_parse_link_direction(port_cpu, &playback_only, &capture_only);
graph_util_parse_link_direction(port_codec, &playback_only, &capture_only);
graph_util_parse_link_direction(ep_cpu, &playback_only, &capture_only);
graph_util_parse_link_direction(ep_codec, &playback_only, &capture_only);
of_property_read_u32(top, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(ports_cpu, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(ports_codec, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(port_cpu, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(port_codec, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(ep_cpu, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(ep_codec, "mclk-fs", &dai_props->mclk_fs);
graph_util_parse_trigger_order(priv, top, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, ports_cpu, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, ports_codec, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, port_cpu, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, port_cpu, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, ep_cpu, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, ep_codec, &trigger_start, &trigger_stop);
dai_link->playback_only = playback_only;
dai_link->capture_only = capture_only;
dai_link->trigger_start = trigger_start;
dai_link->trigger_stop = trigger_stop;
dai_link->init = simple_util_dai_init;
dai_link->ops = &graph_ops;
if (priv->ops)
dai_link->ops = priv->ops;
ret = simple_util_set_dailink_name(priv, dai_link, name);
end:
return graph_ret(priv, ret);
}
static int graph_dai_link_of_dpcm(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
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 device_node *top = dev->of_node;
struct device_node *ep = li->cpu ? cpu_ep : codec_ep;
char dai_name[64];
int ret;
dev_dbg(dev, "link_of DPCM (%pOF)\n", ep);
if (li->cpu) {
struct snd_soc_card *card = simple_priv_to_card(priv);
struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(dai_link, 0);
struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, 0);
int is_single_links = 0;
/* Codec is dummy */
/* FE settings */
dai_link->dynamic = 1;
dai_link->dpcm_merged_format = 1;
ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
if (ret)
goto end;
snprintf(dai_name, sizeof(dai_name),
"fe.%pOFP.%s", cpus->of_node, cpus->dai_name);
/*
* In BE<->BE connections it is not required to create
* PCM devices at CPU end of the dai link and thus 'no_pcm'
* flag needs to be set. It is useful when there are many
* BE components and some of these have to be connected to
* form a valid audio path.
*
* For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where
* there are 'n' BE components in the path.
*/
if (card->component_chaining && !soc_component_is_pcm(cpus)) {
dai_link->no_pcm = 1;
dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;
}
simple_util_canonicalize_cpu(cpus, is_single_links);
simple_util_canonicalize_platform(platforms, cpus);
} else {
struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, 0);
struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, 0);
/* CPU is dummy */
/* BE settings */
dai_link->no_pcm = 1;
dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;
ret = graph_parse_node(priv, codec_ep, li, NULL);
if (ret < 0)
goto end;
snprintf(dai_name, sizeof(dai_name),
"be.%pOFP.%s", codecs->of_node, codecs->dai_name);
/* check "prefix" from top node */
struct device_node *port __free(device_node) = ep_to_port(ep);
struct device_node *ports __free(device_node) = port_to_ports(port);
snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node, "prefix");
snd_soc_of_parse_node_prefix(ports, cconf, codecs->of_node, "prefix");
snd_soc_of_parse_node_prefix(port, cconf, codecs->of_node, "prefix");
}
graph_parse_convert(dev, ep, &dai_props->adata);
ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
li->link++;
end:
return graph_ret(priv, ret);
}
static int graph_dai_link_of(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(dai_link, 0);
struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, 0);
struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, 0);
char dai_name[64];
int ret, is_single_links = 0;
dev_dbg(dev, "link_of (%pOF)\n", cpu_ep);
ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
if (ret < 0)
goto end;
ret = graph_parse_node(priv, codec_ep, li, NULL);
if (ret < 0)
goto end;
snprintf(dai_name, sizeof(dai_name),
"%s-%s", cpus->dai_name, codecs->dai_name);
simple_util_canonicalize_cpu(cpus, is_single_links);
simple_util_canonicalize_platform(platforms, cpus);
ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
if (ret < 0)
goto end;
li->link++;
end:
return graph_ret(priv, ret);
}
static inline bool parse_as_dpcm_link(struct simple_util_priv *priv,
struct device_node *codec_port,
struct simple_util_data *adata)
{
if (priv->force_dpcm)
return true;
if (!priv->dpcm_selectable)
return false;
/*
* It is DPCM
* if Codec port has many endpoints,
* or has convert-xxx property
*/
if ((of_get_child_count(codec_port) > 1) ||
simple_util_is_convert_required(adata))
return true;
return false;
}
static int __graph_for_each_link(struct simple_util_priv *priv,
struct link_info *li,
int (*func_noml)(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li),
int (*func_dpcm)(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li))
{
struct of_phandle_iterator it;
struct device *dev = simple_priv_to_dev(priv);
struct device_node *node = dev->of_node;
struct device_node *cpu_port;
struct device_node *codec_port_old = NULL;
struct simple_util_data adata;
int rc, ret = 0;
/* loop for all listed CPU port */
of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
cpu_port = it.node;
/* loop for all CPU endpoint */
for_each_of_graph_port_endpoint(cpu_port, cpu_ep) {
/* get codec */
struct device_node *codec_ep __free(device_node) = of_graph_get_remote_endpoint(cpu_ep);
struct device_node *codec_port __free(device_node) = ep_to_port(codec_ep);
/* get convert-xxx property */
memset(&adata, 0, sizeof(adata));
graph_parse_convert(dev, codec_ep, &adata);
graph_parse_convert(dev, cpu_ep, &adata);
/* check if link requires DPCM parsing */
if (parse_as_dpcm_link(priv, codec_port, &adata)) {
/*
* Codec endpoint can be NULL for pluggable audio HW.
* Platform DT can populate the Codec endpoint depending on the
* plugged HW.
*/
/* Do it all CPU endpoint, and 1st Codec endpoint */
if (li->cpu ||
((codec_port_old != codec_port) && codec_ep))
ret = func_dpcm(priv, cpu_ep, codec_ep, li);
/* else normal sound */
} else {
if (li->cpu)
ret = func_noml(priv, cpu_ep, codec_ep, li);
}
if (ret < 0)
goto end;
codec_port_old = codec_port;
}
}
end:
return graph_ret(priv, ret);
}
static int graph_for_each_link(struct simple_util_priv *priv,
struct link_info *li,
int (*func_noml)(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li),
int (*func_dpcm)(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li))
{
int ret;
/*
* Detect all CPU first, and Detect all Codec 2nd.
*
* In Normal sound case, all DAIs are detected
* as "CPU-Codec".
*
* In DPCM sound case,
* all CPUs are detected as "CPU-dummy", and
* all Codecs are detected as "dummy-Codec".
* To avoid random sub-device numbering,
* detect "dummy-Codec" in last;
*/
for (li->cpu = 1; li->cpu >= 0; li->cpu--) {
ret = __graph_for_each_link(priv, li, func_noml, func_dpcm);
if (ret < 0)
break;
}
return graph_ret(priv, ret);
}
static int graph_count_noml(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
int ret = -EINVAL;
if (li->link >= SNDRV_MAX_LINKS)
goto end;
/*
* DON'T REMOVE platforms
* see
* simple-card.c :: simple_count_noml()
*/
li->num[li->link].cpus = 1;
li->num[li->link].platforms = 1;
li->num[li->link].codecs = 1;
li->link += 1; /* 1xCPU-Codec */
dev_dbg(dev, "Count As Normal\n");
ret = 0;
end:
return graph_ret(priv, ret);
}
static int graph_count_dpcm(struct simple_util_priv *priv,
struct device_node *cpu_ep,
struct device_node *codec_ep,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
int ret = -EINVAL;
if (li->link >= SNDRV_MAX_LINKS)
goto end;
if (li->cpu) {
/*
* DON'T REMOVE platforms
* see
* simple-card.c :: simple_count_noml()
*/
li->num[li->link].cpus = 1;
li->num[li->link].platforms = 1;
li->link++; /* 1xCPU-dummy */
} else {
li->num[li->link].codecs = 1;
li->link++; /* 1xdummy-Codec */
}
dev_dbg(dev, "Count As DPCM\n");
ret = 0;
end:
return graph_ret(priv, ret);
}
static int graph_get_dais_count(struct simple_util_priv *priv,
struct link_info *li)
{
/*
* link_num : number of links.
* CPU-Codec / CPU-dummy / dummy-Codec
* dais_num : number of DAIs
* ccnf_num : number of codec_conf
* same number for "dummy-Codec"
*
* ex1)
* CPU0 --- Codec0 link : 5
* CPU1 --- Codec1 dais : 7
* CPU2 -/ ccnf : 1
* CPU3 --- Codec2
*
* => 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
* => 7 DAIs = 4xCPU + 3xCodec
* => 1 ccnf = 1xdummy-Codec
*
* ex2)
* CPU0 --- Codec0 link : 5
* CPU1 --- Codec1 dais : 6
* CPU2 -/ ccnf : 1
* CPU3 -/
*
* => 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
* => 6 DAIs = 4xCPU + 2xCodec
* => 1 ccnf = 1xdummy-Codec
*
* ex3)
* CPU0 --- Codec0 link : 6
* CPU1 -/ dais : 6
* CPU2 --- Codec1 ccnf : 2
* CPU3 -/
*
* => 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
* => 6 DAIs = 4xCPU + 2xCodec
* => 2 ccnf = 2xdummy-Codec
*
* ex4)
* CPU0 --- Codec0 (convert-rate) link : 3
* CPU1 --- Codec1 dais : 4
* ccnf : 1
*
* => 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec
* => 4 DAIs = 2xCPU + 2xCodec
* => 1 ccnf = 1xdummy-Codec
*/
return graph_for_each_link(priv, li,
graph_count_noml,
graph_count_dpcm);
}
int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev)
{
struct snd_soc_card *card = simple_priv_to_card(priv);
int ret = -ENOMEM;
struct link_info *li __free(kfree) = kzalloc_obj(*li);
if (!li)
goto end;
card->owner = THIS_MODULE;
card->dev = dev;
ret = graph_get_dais_count(priv, li);
if (ret < 0)
goto end;
ret = -EINVAL;
if (!li->link)
goto end;
ret = simple_util_init_priv(priv, li);
if (ret < 0)
goto end;
priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);
if (IS_ERR(priv->pa_gpio)) {
ret = PTR_ERR(priv->pa_gpio);
dev_err(dev, "failed to get amplifier gpio: %d\n", ret);
goto end;
}
ret = simple_util_parse_widgets(card, NULL);
if (ret < 0)
goto end;
ret = simple_util_parse_routing(card, NULL);
if (ret < 0)
goto end;
memset(li, 0, sizeof(*li));
ret = graph_for_each_link(priv, li,
graph_dai_link_of,
graph_dai_link_of_dpcm);
if (ret < 0)
goto err;
ret = simple_util_parse_card_name(priv, NULL);
if (ret < 0)
goto err;
snd_soc_card_set_drvdata(card, priv);
simple_util_debug_info(priv);
ret = devm_snd_soc_register_card(dev, card);
if (ret < 0)
goto err;
return 0;
err:
simple_util_clean_reference(card);
end:
return dev_err_probe(dev, ret, "parse error\n");
}
EXPORT_SYMBOL_GPL(audio_graph_parse_of);
static int graph_probe(struct platform_device *pdev)
{
struct simple_util_priv *priv;
struct device *dev = &pdev->dev;
struct snd_soc_card *card;
/* Allocate the private data and the DAI link array */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card = simple_priv_to_card(priv);
card->dapm_widgets = graph_dapm_widgets;
card->num_dapm_widgets = ARRAY_SIZE(graph_dapm_widgets);
card->probe = graph_util_card_probe;
if (of_device_get_match_data(dev))
priv->dpcm_selectable = 1;
return audio_graph_parse_of(priv, dev);
}
static const struct of_device_id graph_of_match[] = {
{ .compatible = "audio-graph-card", },
{ .compatible = "audio-graph-scu-card",
.data = (void *)DPCM_SELECTABLE },
{},
};
MODULE_DEVICE_TABLE(of, graph_of_match);
static struct platform_driver graph_card = {
.driver = {
.name = "asoc-audio-graph-card",
.pm = &snd_soc_pm_ops,
.of_match_table = graph_of_match,
},
.probe = graph_probe,
.remove = simple_util_remove,
};
module_platform_driver(graph_card);
MODULE_ALIAS("platform:asoc-audio-graph-card");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ASoC Audio Graph Sound Card");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-2.0
//
// audio-graph-card2-custom-sample.c
//
// Copyright (C) 2020 Renesas Electronics Corp.
// Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
//
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/graph_card.h>
/*
* Custom driver can have own priv
* which includes simple_util_priv.
*/
struct custom_priv {
struct simple_util_priv simple_priv;
/* custom driver's own params */
int custom_params;
};
/* You can get custom_priv from simple_priv */
#define simple_to_custom(simple) container_of((simple), struct custom_priv, simple_priv)
static int custom_card_probe(struct snd_soc_card *card)
{
struct simple_util_priv *simple_priv = snd_soc_card_get_drvdata(card);
struct custom_priv *custom_priv = simple_to_custom(simple_priv);
struct device *dev = simple_priv_to_dev(simple_priv);
dev_info(dev, "custom probe\n");
custom_priv->custom_params = 1;
/* you can use generic probe function */
return graph_util_card_probe(card);
}
static int custom_hook_pre(struct simple_util_priv *priv)
{
struct device *dev = simple_priv_to_dev(priv);
/* You can custom before parsing */
dev_info(dev, "hook : %s\n", __func__);
return 0;
}
static int custom_hook_post(struct simple_util_priv *priv)
{
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_card *card;
/* You can custom after parsing */
dev_info(dev, "hook : %s\n", __func__);
/* overwrite .probe sample */
card = simple_priv_to_card(priv);
card->probe = custom_card_probe;
return 0;
}
static int custom_normal(struct simple_util_priv *priv,
struct device_node *lnk,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
/*
* You can custom Normal parsing
* before/affter audio_graph2_link_normal()
*/
dev_info(dev, "hook : %s\n", __func__);
return audio_graph2_link_normal(priv, lnk, li);
}
static int custom_dpcm(struct simple_util_priv *priv,
struct device_node *lnk,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
/*
* You can custom DPCM parsing
* before/affter audio_graph2_link_dpcm()
*/
dev_info(dev, "hook : %s\n", __func__);
return audio_graph2_link_dpcm(priv, lnk, li);
}
static int custom_c2c(struct simple_util_priv *priv,
struct device_node *lnk,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
/*
* You can custom Codec2Codec parsing
* before/affter audio_graph2_link_c2c()
*/
dev_info(dev, "hook : %s\n", __func__);
return audio_graph2_link_c2c(priv, lnk, li);
}
/*
* audio-graph-card2 has many hooks for your customizing.
*/
static struct graph2_custom_hooks custom_hooks = {
.hook_pre = custom_hook_pre,
.hook_post = custom_hook_post,
.custom_normal = custom_normal,
.custom_dpcm = custom_dpcm,
.custom_c2c = custom_c2c,
};
static int custom_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
struct device *dev = simple_priv_to_dev(priv);
dev_info(dev, "custom startup\n");
return simple_util_startup(substream);
}
/* You can use custom ops */
static const struct snd_soc_ops custom_ops = {
.startup = custom_startup,
.shutdown = simple_util_shutdown,
.hw_params = simple_util_hw_params,
};
static int custom_probe(struct platform_device *pdev)
{
struct custom_priv *custom_priv;
struct simple_util_priv *simple_priv;
struct device *dev = &pdev->dev;
int ret;
custom_priv = devm_kzalloc(dev, sizeof(*custom_priv), GFP_KERNEL);
if (!custom_priv)
return -ENOMEM;
simple_priv = &custom_priv->simple_priv;
simple_priv->ops = &custom_ops; /* customize dai_link ops */
/* "audio-graph-card2-custom-sample" is too long */
simple_priv->snd_card.name = "card2-custom";
/* use audio-graph-card2 parsing with own custom hooks */
ret = audio_graph2_parse_of(simple_priv, dev, &custom_hooks);
if (ret < 0)
return ret;
/* customize more if needed */
return 0;
}
static const struct of_device_id custom_of_match[] = {
{ .compatible = "audio-graph-card2-custom-sample", },
{},
};
MODULE_DEVICE_TABLE(of, custom_of_match);
static struct platform_driver custom_card = {
.driver = {
.name = "audio-graph-card2-custom-sample",
.of_match_table = custom_of_match,
},
.probe = custom_probe,
.remove = simple_util_remove,
};
module_platform_driver(custom_card);
MODULE_ALIAS("platform:asoc-audio-graph-card2-custom-sample");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ASoC Audio Graph Card2 Custom Sample");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0
/*
* audio-graph-card2-custom-sample1.dtsi
*
* Copyright (C) 2020 Renesas Electronics Corp.
* Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This sample indicates how to use audio-graph-card2 and its
* custom driver. "audio-graph-card2-custom-sample" is the custome driver
* which is using audio-graph-card2.
*
* You can easily use this sample by adding below line on your DT file,
* and add new CONFIG to your .config.
*
* #include "../../../../../sound/soc/generic/audio-graph-card2-custom-sample1.dtsi"
*
* CONFIG_SND_AUDIO_GRAPH_CARD2
* CONFIG_SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE
* CONFIG_SND_TEST_COMPONENT
*
*
* You can indicate more detail each device behavior as debug if you modify
* "compatible" on each test-component. see below
*
* test_cpu {
* - compatible = "test-cpu";
* + compatible = "test-cpu-verbose";
* ...
* };
*
* test_codec {
* - compatible = "test-codec";
* + compatible = "test-codec-verbose";
* ...
* };
*
*
* Below sample doesn't use "format" property,
* because test-component driver (test-cpu/test-codec) is supporting
* snd_soc_dai_ops :: .auto_selectable_formats.
* see
* snd_soc_runtime_get_dai_fmt()
* linux/sound/soc/generic/test-component.c :: test_dai_formats
*/
/ {
audio-graph-card2-custom-sample-1 {
/*
* You can use audio-graph-card2 directly by using
*
* compatible = "audio-graph-card2";
*/
compatible = "audio-graph-card2-custom-sample";
label = "card2-custom-sample-1";
/*
* @ : used at links
*/
links = <
/*
*
* [Normal]
*
* <cpu1_0>
* cpu1_0 <-@-----> codec1_0
*/
&cpu1_0 /* CPU side only */
/*
* [Semi-Multi]
*
* CPU:Codec = 1:N
*
* <sm> +-+
* cpu1_1 <--@---->| |-> codec1_1
* | |-> codec1_2
* +-+
*/
&sm /* CPU side only */
/*
* [Multi-CPU/Codec-A]
*
* +-+ <mcpuA> +-+
* cpu1_2 <-| |<---@------>| |-> codec1_3
* cpu1_3 <-| | | |-> codec1_4
* +-+ +-+
*/
&mcpuA /* CPU side only */
/*
* [Multi-CPU/Codec-B]
*
* +-+ <mcpuB> +-+
* | |<---@------>| |
* | | | |
* cpu1_4 <-| |<---------->| |-> codec1_5
* cpu1_5 <-| |<---+------>| |-> codec1_6
* +-+ \----->| |-> codec1_7
* +-+
*/
&mcpuB /* CPU side only */
/*
* [Multi-CPU/Codec-C]
*
* +-+ <mcpuC> +-+
* | |<---@------>| |
* | | | |
* cpu1_6 <-| |<---------->| |-> codec1_8
* cpu1_7 <-| |<-----+---->| |-> codec1_9
* cpu1_8 <-| |<----/ +-+
* +-+
*/
&mcpuC /* CPU side only */
>;
multi {
#address-cells = <1>;
#size-cells = <0>;
/*
* [Semi-Multi]
*
* <sm> +---+
* cpu1_1 <---@--->|X A|-> codec1_1
* | B|-> codec1_2
* +---+
*/
ports@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
port@0 { reg = <0>; smcodec_ep: endpoint { remote-endpoint = <&cpu1_1_ep>; };};/* (X) to pair */
port@1 { reg = <1>; smcodec_A_ep: endpoint { remote-endpoint = <&codec1_1_ep>; };};/* (A) Multi Element */
port@2 { reg = <2>; smcodec_B_ep: endpoint { remote-endpoint = <&codec1_2_ep>; };};/* (B) Multi Element */
};
/*
* [Multi-CPU-A]
*
* +---+ <mcpuA> +---+
* cpu1_2 <-|A X|<---@---->|x a|-> codec1_3
* cpu1_3 <-|B | | b|-> codec1_4
* +---+ +---+
*/
ports@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
mcpuA: port@0 { reg = <0>; mcpu_A_ep: endpoint { remote-endpoint = <&mcodec_A_ep>; };}; /* (X) to pair */
port@1 { reg = <1>; mcpu_AA_ep: endpoint { remote-endpoint = <&cpu1_2_ep>; };}; /* (A) Multi Element */
port@2 { reg = <2>; mcpu_AB_ep: endpoint { remote-endpoint = <&cpu1_3_ep>; };}; /* (B) Multi Element */
};
/*
* [Multi-Codec-A]
*
* +---+ <mcpuA> +---+
* cpu1_2 <-|A X|<-@------>|x a|-> codec1_3
* cpu1_3 <-|B | | b|-> codec1_4
* +---+ +---+
*/
ports@2 {
reg = <2>;
#address-cells = <1>;
#size-cells = <0>;
port@0 { reg = <0>; mcodec_A_ep: endpoint { remote-endpoint = <&mcpu_A_ep>; };}; /* (x) to pair */
port@1 { reg = <1>; mcodec_Aa_ep: endpoint { remote-endpoint = <&codec1_3_ep>; };}; /* (a) Multi Element */
port@2 { reg = <2>; mcodec_Ab_ep: endpoint { remote-endpoint = <&codec1_4_ep>; };}; /* (b) Multi Element */
};
/*
* [Multi-CPU-B]
*
* +---+ <mcpuB> +---+
* | X|<---@---->|x |
* | | | |
* cpu1_4 <-|A 1|<-------->|3 a|-> codec1_5
* cpu1_5 <-|B 2|<---+---->|4 b|-> codec1_6
* +---+ \--->|5 c|-> codec1_7
* +---+
*/
ports@3 {
reg = <3>;
#address-cells = <1>;
#size-cells = <0>;
mcpuB: port@0 {
reg = <0>;
mcpu_BX_ep: endpoint { remote-endpoint = <&mcodec_Bx_ep>; }; /* (X) to pair */
};
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
mcpu_BA_ep: endpoint@0 { reg = <0>; remote-endpoint = <&cpu1_4_ep>; }; /* (A) Multi Element */
mcpu_B1_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcodec_B3_ep>; }; /* (1) connected Codec */
};
port@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
mcpu_BB_ep: endpoint@0 { reg = <0>; remote-endpoint = <&cpu1_5_ep>; }; /* (B) Multi Element */
mcpu_B2_0_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcodec_B4_ep>; }; /* (2) connected Codec */
mcpu_B2_1_ep: endpoint@2 { reg = <2>; remote-endpoint = <&mcodec_B5_ep>; }; /* (2) connected Codec */
};
};
/*
* [Multi-Codec-B]
*
* +---+ <mcpuB> +---+
* | X|<-@------>|x |
* | | | |
* cpu1_4 <-|A 1|<-------->|3 a|-> codec1_5
* cpu1_5 <-|B 2|<---+---->|4 b|-> codec1_6
* +---+ \--->|5 c|-> codec1_7
* +---+
*/
ports@4 {
reg = <4>;
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
mcodec_Bx_ep: endpoint { remote-endpoint = <&mcpu_BX_ep>; }; /* (x) to pair */
};
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
mcodec_Ba_ep: endpoint@0 { reg = <0>; remote-endpoint = <&codec1_5_ep>;}; /* (a) Multi Element */
mcodec_B3_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcpu_B1_ep>; }; /* (3) connected CPU */
};
port@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
mcodec_Bb_ep: endpoint@0 { reg = <0>; remote-endpoint = <&codec1_6_ep>; }; /* (b) Multi Element */
mcodec_B4_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcpu_B2_0_ep>;}; /* (4) connected CPU */
};
port@3 {
#address-cells = <1>;
#size-cells = <0>;
reg = <3>;
mcodec_Bc_ep: endpoint@0 { reg = <0>; remote-endpoint = <&codec1_7_ep>; }; /* (c) Multi Element */
mcodec_B5_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcpu_B2_1_ep>;}; /* (5) connected CPU */
};
};
/*
* [Multi-CPU-C]
*
* +---+ <mcpuC> +---+
* | X|<-@------>|x |
* | | | |
* cpu1_6 <-|A 1|<-------->|4 a|-> codec1_8
* cpu1_7 <-|B 2|<-----+-->|5 b|-> codec1_9
* cpu1_8 <-|C 3|<----/ +---+
* +---+
*/
ports@5 {
reg = <5>;
#address-cells = <1>;
#size-cells = <0>;
mcpuC: port@0 {
reg = <0>;
mcpu_CX_ep: endpoint { remote-endpoint = <&mcodec_Cx_ep>; }; /* (X) to pair */
};
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
mcpu_CA_ep: endpoint@0 { reg = <0>; remote-endpoint = <&cpu1_6_ep>; }; /* (A) Multi Element */
mcpu_C1_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcodec_C4_ep>; }; /* (1) connected Codec */
};
port@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
mcpu_CB_ep: endpoint@0 { reg = <0>; remote-endpoint = <&cpu1_7_ep>; }; /* (B) Multi Element */
mcpu_C2_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcodec_C5_0_ep>; }; /* (2) connected Codec */
};
port@3 {
#address-cells = <1>;
#size-cells = <0>;
reg = <3>;
mcpu_CC_ep: endpoint@0 { reg = <0>; remote-endpoint = <&cpu1_8_ep>; }; /* (C) Multi Element */
mcpu_C3_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcodec_C5_1_ep>; }; /* (3) connected Codec */
};
};
/*
* [Multi-Codec-C]
*
* +---+ <mcpuC> +---+
* | X|<-@------>|x |
* | | | |
* cpu1_6 <-|A 1|<-------->|4 a|-> codec1_8
* cpu1_7 <-|B 2|<-----+-->|5 b|-> codec1_9
* cpu1_8 <-|C 3|<----/ +---+
* +---+
*/
ports@6 {
reg = <6>;
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
mcodec_Cx_ep: endpoint { remote-endpoint = <&mcpu_CX_ep>; }; /* (x) to pair */
};
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
mcodec_Ca_ep: endpoint@0 { reg = <0>; remote-endpoint = <&codec1_8_ep>;}; /* (a) Multi Element */
mcodec_C4_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcpu_C1_ep>; }; /* (4) connected CPU */
};
port@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
mcodec_Cb_ep: endpoint@0 { reg = <0>; remote-endpoint = <&codec1_9_ep>;}; /* (b) Multi Element */
mcodec_C5_0_ep: endpoint@1 { reg = <1>; remote-endpoint = <&mcpu_C2_ep>; }; /* (5) connected CPU */
mcodec_C5_1_ep: endpoint@2 { reg = <2>; remote-endpoint = <&mcpu_C3_ep>; }; /* (5) connected CPU */
};
};
};
};
test_cpu_1 {
/*
* update compatible to indicate more detail behaviour
* if you want. see test-compatible for more detail.
*
* ex)
* - compatible = "test-cpu";
* + compatible = "test-cpu-verbose";
*/
compatible = "test-cpu";
ports {
#address-cells = <1>;
#size-cells = <0>;
bitclock-master;
frame-master;
/* [Normal] */
cpu1_0: port@0 { reg = <0>; cpu1_0_ep: endpoint { remote-endpoint = <&codec1_0_ep>;}; };
/* [Semi-Multi] */
sm: port@1 { reg = <1>; cpu1_1_ep: endpoint { remote-endpoint = <&smcodec_ep>; }; };
/* [Multi-CPU-A] */
port@2 { reg = <2>; cpu1_2_ep: endpoint { remote-endpoint = <&mcpu_AA_ep>; }; };
port@3 { reg = <3>; cpu1_3_ep: endpoint { remote-endpoint = <&mcpu_AB_ep>; }; };
/* [Multi-CPU-B] */
port@4 { reg = <4>; cpu1_4_ep: endpoint { remote-endpoint = <&mcpu_BA_ep>; }; };
port@5 { reg = <5>; cpu1_5_ep: endpoint { remote-endpoint = <&mcpu_BB_ep>; }; };
/* [Multi-CPU-C] */
port@6 { reg = <6>; cpu1_6_ep: endpoint { remote-endpoint = <&mcpu_CA_ep>; }; };
port@7 { reg = <7>; cpu1_7_ep: endpoint { remote-endpoint = <&mcpu_CB_ep>; }; };
port@8 { reg = <8>; cpu1_8_ep: endpoint { remote-endpoint = <&mcpu_CC_ep>; }; };
};
};
test_codec_1 {
/*
* update compatible to indicate more detail behaviour
* if you want. see test-compatible for more detail.
*
* ex)
* - compatible = "test-codec";
* + compatible = "test-codec-verbose";
*/
compatible = "test-codec";
ports {
#address-cells = <1>;
#size-cells = <0>;
/* [Normal] */
port@0 { reg = <0>; codec1_0_ep: endpoint { remote-endpoint = <&cpu1_0_ep>; }; };
/* [Semi-Multi] */
port@1 { reg = <1>; codec1_1_ep: endpoint { remote-endpoint = <&smcodec_A_ep>; }; };
port@2 { reg = <2>; codec1_2_ep: endpoint { remote-endpoint = <&smcodec_B_ep>; }; };
/* [Multi-Codec-0] */
port@3 { reg = <3>; codec1_3_ep: endpoint { remote-endpoint = <&mcodec_Aa_ep>; }; };
port@4 { reg = <4>; codec1_4_ep: endpoint { remote-endpoint = <&mcodec_Ab_ep>; }; };
/* [Multi-Codec-1] */
port@5 { reg = <5>; codec1_5_ep: endpoint { remote-endpoint = <&mcodec_Ba_ep>; }; };
port@6 { reg = <6>; codec1_6_ep: endpoint { remote-endpoint = <&mcodec_Bb_ep>; }; };
port@7 { reg = <7>; codec1_7_ep: endpoint { remote-endpoint = <&mcodec_Bc_ep>; }; };
/* [Multi-Codec-2] */
port@8 { reg = <8>; codec1_8_ep: endpoint { remote-endpoint = <&mcodec_Ca_ep>; }; };
port@9 { reg = <9>; codec1_9_ep: endpoint { remote-endpoint = <&mcodec_Cb_ep>; }; };
};
};
};
@@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0
/*
* audio-graph-card2-custom-sample2.dtsi
*
* Copyright (C) 2020 Renesas Electronics Corp.
* Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This sample indicates how to use audio-graph-card2 and its
* custom driver. "audio-graph-card2-custom-sample" is the custome driver
* which is using audio-graph-card2.
*
* You can easily use this sample by adding below line on your DT file,
* and add new CONFIG to your .config.
*
* #include "../../../../../sound/soc/generic/audio-graph-card2-custom-sample2.dtsi"
*
* CONFIG_SND_AUDIO_GRAPH_CARD2
* CONFIG_SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE
* CONFIG_SND_TEST_COMPONENT
*
*
* You can indicate more detail each device behavior as debug if you modify
* "compatible" on each test-component. see below
*
* test_cpu {
* - compatible = "test-cpu";
* + compatible = "test-cpu-verbose";
* ...
* };
*
* test_codec {
* - compatible = "test-codec";
* + compatible = "test-codec-verbose";
* ...
* };
*
*
* Below sample doesn't use "format" property,
* because test-component driver (test-cpu/test-codec) is supporting
* snd_soc_dai_ops :: .auto_selectable_formats.
* see
* snd_soc_runtime_get_dai_fmt()
* linux/sound/soc/generic/test-component.c :: test_dai_formats
*/
/ {
audio-graph-card2-custom-sample-2 {
/*
* You can use audio-graph-card2 directly by using
*
* compatible = "audio-graph-card2";
*/
compatible = "audio-graph-card2-custom-sample";
label = "card2-custom-sample-2";
/* for [DPCM] */
/* BE FE */
routing = "TC DAI0 Playback", "DAI0 Playback",
"TC DAI0 Playback", "DAI1 Playback",
"DAI0 Capture", "TC DAI0 Capture",
"DAI1 Capture", "TC DAI0 Capture",
/* for [DPCM-Multi] */
/* BE FE */
"TC DAI1 Playback", "DAI2 Playback",
"TC DAI2 Playback", "DAI2 Playback",
"TC DAI1 Playback", "DAI3 Playback",
"TC DAI2 Playback", "DAI3 Playback",
"DAI2 Capture", "TC DAI1 Capture",
"DAI2 Capture", "TC DAI2 Capture",
"DAI3 Capture", "TC DAI1 Capture",
"DAI3 Capture", "TC DAI2 Capture",
/* for [Codec2Codec] */
"TC OUT", "TC DAI4 Playback",
"TC DAI3 Capture", "TC IN",
/* for [Codec2Codec-Multi] */
"TC OUT", "TC DAI7 Playback",
"TC DAI5 Capture", "TC IN",
"TC OUT", "TC DAI8 Playback",
"TC DAI6 Capture", "TC IN";
/*
* @ : used at links
*/
links = <
/*
* [DPCM]
*
* cpu20/cpu21 are converting rate to 44.1kHz
*
* FE BE
* <feA> **** <beA>
* cpu2_0 <----@---* *------@---> codec2_0 (44.1kHz)
* cpu2_1 <----@---* *
* <feB> ****
*/
&feA &feB &beA /* both FE / BE */
/*
* [DPCM-Multi]
*
* FE BE
* <feC> **** <beB> +-+
* cpu2_2 <----@---* *------@---> | | -> codec2_1
* cpu2_3 <----@---* * | | -> codec2_2
* <feD> **** +-+
*/
&feC &feD &beB /* both FE / BE*/
/*
* [Codec2Codec]
*
* <c2c>
* +-@-> codec2_3
* |
* +---> codec2_4
*/
&c2c /* CPU side only */
/*
* [Codec2Codec-Multi]
*
* --NOTE--
* Multi connect N:M is not supported by ASoC.
*
* <c2c_m> +-+
* +---@-->| |-> codec2_5
* | | |-> codec2_6
* | +-+
* | +-+
* +------>| |-> codec2_7
* | |-> codec2_8
* +-+
*/
&c2c_m /* CPU side only */
>;
multi {
#address-cells = <1>;
#size-cells = <0>;
/*
* [DPCM-Multi]::BE
*
* FE BE
* <feC> **** <beB> +---+
* cpu2_2 <----@---* *------@---> |x a| -> codec2_1
* cpu2_3 <----@---* * | b| -> codec2_2
* <feD> **** +---+
*/
ports@2 {
reg = <2>;
#address-cells = <1>;
#size-cells = <0>;
port@0 { reg = <0>; mbe_x_ep: endpoint { remote-endpoint = <&beB_ep>; };};/* (x) to pair */
port@1 { reg = <1>; mbe_a_ep: endpoint { remote-endpoint = <&codec2_1_ep>; };};/* (a) Multi Element */
port@2 { reg = <2>; mbe_b_ep: endpoint { remote-endpoint = <&codec2_2_ep>; };};/* (b) Multi Element */
};
/*
* [Codec2Codec-Multi]::CPU
*
* <c2c_m> c2cmf +---+
* +---@---------->|X A|-> codec2_5
* | | B|-> codec2_6
* | +---+
* | c2cmb +---+
* +-------------->|x a|-> codec2_7
* | b|-> codec2_8
* +---+
*/
ports@3 {
reg = <3>;
#address-cells = <1>;
#size-cells = <0>;
port@0 { reg = <0>; mc2c0X_ep: endpoint { remote-endpoint = <&c2cmf_ep>; };};/* (X) to pair */
port@1 { reg = <1>; mc2c0A_ep: endpoint { remote-endpoint = <&codec2_5_ep>; };};/* (A) Multi Element */
port@2 { reg = <2>; mc2c0B_ep: endpoint { remote-endpoint = <&codec2_6_ep>; };};/* (B) Multi Element */
};
/*
* [Codec2Codec-Multi]::Codec
*
* <c2c_m> c2cmf +---+
* +---@---------->|X A|-> codec2_5
* | | B|-> codec2_6
* | +---+
* | c2cmb +---+
* +-------------->|x a|-> codec2_7
* | b|-> codec2_8
* +---+
*/
ports@4 {
reg = <4>;
#address-cells = <1>;
#size-cells = <0>;
port@0 { reg = <0>; mc2c1x_ep: endpoint { remote-endpoint = <&c2cmb_ep>; };};/* (x) to pair */
port@1 { reg = <1>; mc2c1a_ep: endpoint { remote-endpoint = <&codec2_7_ep>; };};/* (a) Multi Element */
port@2 { reg = <2>; mc2c1b_ep: endpoint { remote-endpoint = <&codec2_8_ep>; };};/* (b) Multi Element */
};
};
dpcm {
#address-cells = <1>;
#size-cells = <0>;
/* FE part */
ports@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
/*
* [DPCM]::FE
*
* FE BE
* <feA> **** <beA>
* cpu2_0 <----@---* *------@---> codec2_0 (44.1kHz)
* cpu2_1 <----@---* *
* <feB> ****
*/
feA: port@0 { reg = <0>; feA_ep: endpoint { remote-endpoint = <&cpu2_0_ep>; }; };
feB: port@1 { reg = <1>; feB_ep: endpoint { remote-endpoint = <&cpu2_1_ep>; }; };
/*
* [DPCM-Multi]::FE
*
* FE BE
* <feC> **** <beB> +-+
* cpu2_2 <----@---* *------@---> | | -> codec2_1
* cpu2_3 <----@---* * | | -> codec2_2
* <feD> **** +-+
*/
feC: port@2 { reg = <2>; feC_ep: endpoint { remote-endpoint = <&cpu2_2_ep>; }; };
feD: port@3 { reg = <3>; feD_ep: endpoint { remote-endpoint = <&cpu2_3_ep>; }; };
};
/* BE part */
ports@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
/*
* [DPCM]::BE
*
* FE BE
* <feA> **** <beA>
* cpu2_0 <----@---* *------@---> codec2_0 (44.1kHz)
* cpu2_1 <----@---* *
* <feB> ****
*/
beA: port@0 { reg = <0>; beA_ep: endpoint { remote-endpoint = <&codec2_0_ep>; }; };
/*
* [DPCM-Multi]::BE
*
* FE BE
* <feC> **** <beB> +-------+
* cpu2_2 <----@---* *------@---> |mbe_x | -> codec2_1
* cpu2_3 <----@---* * | | -> codec2_2
* <feD> **** +-------+
*/
beB: port@1 { reg = <1>; beB_ep: endpoint { remote-endpoint = <&mbe_x_ep>; }; };
};
};
codec2codec {
#address-cells = <1>;
#size-cells = <0>;
/*
* [Codec2Codec]
*
* <c2c>
* +-@--> codec2_3
* |
* +----> codec2_4
*/
ports@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
/* use default settings */
c2c: port@0 { reg = <0>; c2cf_ep: endpoint { remote-endpoint = <&codec2_3_ep>; }; };
port@1 { reg = <1>; c2cb_ep: endpoint { remote-endpoint = <&codec2_4_ep>; }; };
};
/*
* [Codec2Codec-Multi]
*
* <c2c_m> c2cmf +--------+
* +---@---------->|mc2c0X |-> codec2_5
* | | |-> codec2_6
* | +--------+
* | c2cmb +--------+
* +-------------->|mc2c1x |-> codec2_7
* | |-> codec2_8
* +--------+
*/
ports@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
/* use original settings */
rate = <48000>;
c2c_m: port@0 { reg = <0>; c2cmf_ep: endpoint { remote-endpoint = <&mc2c0X_ep>; }; };
port@1 { reg = <1>; c2cmb_ep: endpoint { remote-endpoint = <&mc2c1x_ep>; }; };
};
};
};
test_cpu_2 {
/*
* update compatible to indicate more detail behaviour
* if you want. see test-compatible for more detail.
*
* ex)
* - compatible = "test-cpu";
* + compatible = "test-cpu-verbose";
*/
compatible = "test-cpu";
ports {
#address-cells = <1>;
#size-cells = <0>;
bitclock-master;
frame-master;
/* [DPCM]::FE */
port@0 { reg = <0>; cpu2_0_ep: endpoint { remote-endpoint = <&feA_ep>; };};
port@1 { reg = <1>; cpu2_1_ep: endpoint { remote-endpoint = <&feB_ep>; };};
/* [DPCM-Multi]::FE */
port@2 { reg = <2>; cpu2_2_ep: endpoint { remote-endpoint = <&feC_ep>; };};
port@3 { reg = <3>; cpu2_3_ep: endpoint { remote-endpoint = <&feD_ep>; };};
};
};
test_codec_2 {
/*
* update compatible to indicate more detail behaviour
* if you want. see test-compatible for more detail.
*
* ex)
* - compatible = "test-codec";
* + compatible = "test-codec-verbose";
*/
compatible = "test-codec";
ports {
#address-cells = <1>;
#size-cells = <0>;
/*
* prefix can be added to *component*,
* see audio-graph-card2::routing
*/
prefix = "TC";
/* [DPCM]::BE */
port@0 {
convert-rate = <44100>;
reg = <0>; codec2_0_ep: endpoint { remote-endpoint = <&beA_ep>; };
};
/* [DPCM-Multi]::BE */
port@1 { reg = <1>; codec2_1_ep: endpoint { remote-endpoint = <&mbe_a_ep>; };};
port@2 { reg = <2>; codec2_2_ep: endpoint { remote-endpoint = <&mbe_b_ep>; };};
/* [Codec2Codec] */
port@3 { bitclock-master;
frame-master;
reg = <3>; codec2_3_ep: endpoint { remote-endpoint = <&c2cf_ep>; };};
port@4 { reg = <4>; codec2_4_ep: endpoint { remote-endpoint = <&c2cb_ep>; };};
/* [Codec2Codec-Multi] */
port@5 { bitclock-master;
frame-master;
reg = <5>; codec2_5_ep: endpoint { remote-endpoint = <&mc2c0A_ep>; };};
port@6 { reg = <6>; codec2_6_ep: endpoint { remote-endpoint = <&mc2c0B_ep>; };};
port@7 { reg = <7>; codec2_7_ep: endpoint { remote-endpoint = <&mc2c1a_ep>; };};
port@8 { reg = <8>; codec2_8_ep: endpoint { remote-endpoint = <&mc2c1b_ep>; };};
};
};
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+786
View File
@@ -0,0 +1,786 @@
// SPDX-License-Identifier: GPL-2.0
//
// ASoC simple sound card support
//
// Copyright (C) 2012 Renesas Solutions Corp.
// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <sound/simple_card_utils.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#define DPCM_SELECTABLE 1
#define DAI "sound-dai"
#define CELL "#sound-dai-cells"
#define PREFIX "simple-audio-card,"
static const struct snd_soc_ops simple_ops = {
.startup = simple_util_startup,
.shutdown = simple_util_shutdown,
.hw_params = simple_util_hw_params,
};
#define simple_ret(priv, ret) _simple_ret(priv, __func__, ret)
static inline int _simple_ret(struct simple_util_priv *priv,
const char *func, int ret)
{
return snd_soc_ret(simple_priv_to_dev(priv), ret, "at %s()\n", func);
}
static int simple_parse_platform(struct simple_util_priv *priv,
struct device_node *node,
struct snd_soc_dai_link_component *dlc)
{
struct of_phandle_args args;
int ret;
if (!node)
return 0;
/*
* Get node via "sound-dai = <&phandle port>"
* It will be used as the of_node for component matching during
* snd_soc_add_pcm_runtime().
*/
ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args);
if (ret)
return simple_ret(priv, ret);
/* dai_name is not required and may not exist for plat component */
dlc->of_node = args.np;
return 0;
}
static int simple_parse_dai(struct simple_util_priv *priv,
struct device_node *node,
struct snd_soc_dai_link_component *dlc,
int *is_single_link)
{
struct device *dev = simple_priv_to_dev(priv);
struct of_phandle_args args;
struct snd_soc_dai_link_component resolved_dlc = {};
struct snd_soc_dai *dai;
const char *fallback_dai_name;
int ret;
if (!node)
return 0;
/*
* Get node via "sound-dai = <&phandle port>"
* It will be used as the of_node for component matching during
* snd_soc_add_pcm_runtime().
*/
ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args);
if (ret)
goto end;
/*
* Try to find from DAI args
*/
dai = snd_soc_get_dai_via_args(&args);
if (dai) {
ret = -ENOMEM;
dlc->dai_name = snd_soc_dai_name_get(dai);
dlc->dai_args = snd_soc_copy_dai_args(dev, &args);
if (!dlc->dai_args)
goto end;
} else {
ret = snd_soc_get_dlc(&args, &resolved_dlc);
if (ret < 0)
goto end;
/* Keep fallback dai_name valid across component rebind */
fallback_dai_name = resolved_dlc.dai_name;
if (fallback_dai_name) {
fallback_dai_name = devm_kstrdup_const(dev, fallback_dai_name,
GFP_KERNEL);
ret = -ENOMEM;
if (!fallback_dai_name) {
of_node_put(resolved_dlc.of_node);
goto end;
}
}
dlc->of_node = resolved_dlc.of_node;
dlc->dai_name = fallback_dai_name;
dlc->dai_args = resolved_dlc.dai_args;
}
if (is_single_link)
*is_single_link = !args.args_count;
ret = 0;
end:
return simple_ret(priv, ret);
}
static void simple_parse_convert(struct device *dev,
struct device_node *np,
struct simple_util_data *adata)
{
struct device_node *top = dev->of_node;
struct device_node *node __free(device_node) = of_get_parent(np);
simple_util_parse_convert(top, PREFIX, adata);
simple_util_parse_convert(node, PREFIX, adata);
simple_util_parse_convert(node, NULL, adata);
simple_util_parse_convert(np, NULL, adata);
}
static int simple_parse_node(struct simple_util_priv *priv,
struct device_node *np,
struct link_info *li,
char *prefix,
int *cpu)
{
struct device *dev = simple_priv_to_dev(priv);
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_dai_link_component *dlc;
struct simple_util_dai *dai;
int ret;
if (cpu) {
dlc = snd_soc_link_to_cpu(dai_link, 0);
dai = simple_props_to_dai_cpu(dai_props, 0);
} else {
dlc = snd_soc_link_to_codec(dai_link, 0);
dai = simple_props_to_dai_codec(dai_props, 0);
}
ret = simple_parse_dai(priv, np, dlc, cpu);
if (ret)
goto end;
ret = simple_util_parse_clk(dev, np, dai, dlc);
if (ret)
goto end;
ret = simple_util_parse_tdm(np, dai);
end:
return simple_ret(priv, ret);
}
static int simple_link_init(struct simple_util_priv *priv,
struct device_node *cpu,
struct device_node *codec,
struct link_info *li,
char *prefix, char *name)
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *top = dev->of_node;
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 device_node *node __free(device_node) = of_get_parent(cpu);
enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;
enum snd_soc_trigger_order trigger_stop = SND_SOC_TRIGGER_ORDER_DEFAULT;
bool playback_only = 0, capture_only = 0;
int ret;
ret = simple_util_parse_daifmt(dev, node, codec,
prefix, &dai_link->dai_fmt);
if (ret < 0)
goto end;
graph_util_parse_link_direction(top, &playback_only, &capture_only);
graph_util_parse_link_direction(node, &playback_only, &capture_only);
graph_util_parse_link_direction(cpu, &playback_only, &capture_only);
graph_util_parse_link_direction(codec, &playback_only, &capture_only);
of_property_read_u32(top, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(top, PREFIX "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(node, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(node, PREFIX "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(cpu, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(cpu, PREFIX "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(codec, "mclk-fs", &dai_props->mclk_fs);
of_property_read_u32(codec, PREFIX "mclk-fs", &dai_props->mclk_fs);
graph_util_parse_trigger_order(priv, top, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, node, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, cpu, &trigger_start, &trigger_stop);
graph_util_parse_trigger_order(priv, codec, &trigger_start, &trigger_stop);
dai_link->playback_only = playback_only;
dai_link->capture_only = capture_only;
dai_link->trigger_start = trigger_start;
dai_link->trigger_stop = trigger_stop;
dai_link->init = simple_util_dai_init;
dai_link->ops = &simple_ops;
ret = simple_util_set_dailink_name(priv, dai_link, name);
end:
return simple_ret(priv, ret);
}
static int simple_dai_link_of_dpcm(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li,
bool is_top)
{
struct device *dev = simple_priv_to_dev(priv);
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 device_node *top = dev->of_node;
struct device_node *node __free(device_node) = of_get_parent(np);
char *prefix = "";
char dai_name[64];
int ret;
dev_dbg(dev, "link_of DPCM (%pOF)\n", np);
/* For single DAI link & old style of DT node */
if (is_top)
prefix = PREFIX;
if (li->cpu) {
struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(dai_link, 0);
struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, 0);
int is_single_links = 0;
/* Codec is dummy */
/* FE settings */
dai_link->dynamic = 1;
dai_link->dpcm_merged_format = 1;
ret = simple_parse_node(priv, np, li, prefix, &is_single_links);
if (ret < 0)
goto out_put_node;
snprintf(dai_name, sizeof(dai_name), "fe.%s", cpus->dai_name);
simple_util_canonicalize_cpu(cpus, is_single_links);
simple_util_canonicalize_platform(platforms, cpus);
} else {
struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, 0);
struct snd_soc_codec_conf *cconf;
/* CPU is dummy */
/* BE settings */
dai_link->no_pcm = 1;
dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;
cconf = simple_props_to_codec_conf(dai_props, 0);
ret = simple_parse_node(priv, np, li, prefix, NULL);
if (ret < 0)
goto out_put_node;
snprintf(dai_name, sizeof(dai_name), "be.%s", codecs->dai_name);
/* check "prefix" from top node */
snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node,
PREFIX "prefix");
snd_soc_of_parse_node_prefix(node, cconf, codecs->of_node,
"prefix");
snd_soc_of_parse_node_prefix(np, cconf, codecs->of_node,
"prefix");
}
simple_parse_convert(dev, np, &dai_props->adata);
ret = simple_link_init(priv, np, codec, li, prefix, dai_name);
out_put_node:
li->link++;
return simple_ret(priv, ret);
}
static int simple_dai_link_of(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li,
bool is_top)
{
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(dai_link, 0);
struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, 0);
struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, 0);
struct device_node *cpu = NULL;
char dai_name[64];
char prop[128];
char *prefix = "";
int ret, single_cpu = 0;
cpu = np;
struct device_node *node __free(device_node) = of_get_parent(np);
dev_dbg(dev, "link_of (%pOF)\n", node);
/* For single DAI link & old style of DT node */
if (is_top)
prefix = PREFIX;
snprintf(prop, sizeof(prop), "%splat", prefix);
struct device_node *plat __free(device_node) = of_get_child_by_name(node, prop);
ret = simple_parse_node(priv, cpu, li, prefix, &single_cpu);
if (ret < 0)
goto dai_link_of_err;
ret = simple_parse_node(priv, codec, li, prefix, NULL);
if (ret < 0)
goto dai_link_of_err;
ret = simple_parse_platform(priv, plat, platforms);
if (ret < 0)
goto dai_link_of_err;
snprintf(dai_name, sizeof(dai_name),
"%s-%s", cpus->dai_name, codecs->dai_name);
simple_util_canonicalize_cpu(cpus, single_cpu);
simple_util_canonicalize_platform(platforms, cpus);
ret = simple_link_init(priv, cpu, codec, li, prefix, dai_name);
dai_link_of_err:
li->link++;
return simple_ret(priv, ret);
}
static int __simple_for_each_link(struct simple_util_priv *priv,
struct link_info *li,
int (*func_noml)(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li, bool is_top),
int (*func_dpcm)(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li, bool is_top))
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *top = dev->of_node;
struct device_node *node;
uintptr_t dpcm_selectable = (uintptr_t)of_device_get_match_data(dev);
bool is_top = 0;
int ret = 0;
/* Check if it has dai-link */
node = of_get_child_by_name(top, PREFIX "dai-link");
if (!node) {
node = of_node_get(top);
is_top = 1;
}
struct device_node *add_devs __free(device_node) = of_get_child_by_name(top, PREFIX "additional-devs");
/* loop for all dai-link */
do {
struct simple_util_data adata;
int num = of_get_child_count(node);
/* Skip additional-devs node */
if (node == add_devs) {
node = of_get_next_child(top, node);
continue;
}
/* get codec */
struct device_node *codec __free(device_node) =
of_get_child_by_name(node, is_top ? PREFIX "codec" : "codec");
if (!codec) {
ret = -ENODEV;
goto error;
}
/* get platform */
struct device_node *plat __free(device_node) =
of_get_child_by_name(node, is_top ? PREFIX "plat" : "plat");
/* get convert-xxx property */
memset(&adata, 0, sizeof(adata));
for_each_child_of_node_scoped(node, np) {
if (np == add_devs)
continue;
simple_parse_convert(dev, np, &adata);
}
/* loop for all CPU/Codec node */
for_each_child_of_node_scoped(node, np) {
if (plat == np || add_devs == np)
continue;
/*
* It is DPCM
* if it has many CPUs,
* or has convert-xxx property
*/
if (dpcm_selectable &&
(num > 2 || simple_util_is_convert_required(&adata))) {
/*
* np
* |1(CPU)|0(Codec) li->cpu
* CPU |Pass |return
* Codec |return|Pass
*/
if (li->cpu != (np == codec))
ret = func_dpcm(priv, np, codec, li, is_top);
/* else normal sound */
} else {
/*
* np
* |1(CPU)|0(Codec) li->cpu
* CPU |Pass |return
* Codec |return|return
*/
if (li->cpu && (np != codec))
ret = func_noml(priv, np, codec, li, is_top);
}
if (ret < 0)
goto error;
}
node = of_get_next_child(top, node);
} while (!is_top && node);
error:
of_node_put(node);
return simple_ret(priv, ret);
}
static int simple_for_each_link(struct simple_util_priv *priv,
struct link_info *li,
int (*func_noml)(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li, bool is_top),
int (*func_dpcm)(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li, bool is_top))
{
int ret;
/*
* Detect all CPU first, and Detect all Codec 2nd.
*
* In Normal sound case, all DAIs are detected
* as "CPU-Codec".
*
* In DPCM sound case,
* all CPUs are detected as "CPU-dummy", and
* all Codecs are detected as "dummy-Codec".
* To avoid random sub-device numbering,
* detect "dummy-Codec" in last;
*/
for (li->cpu = 1; li->cpu >= 0; li->cpu--) {
ret = __simple_for_each_link(priv, li, func_noml, func_dpcm);
if (ret < 0)
break;
}
return simple_ret(priv, ret);
}
static void simple_depopulate_aux(void *data)
{
struct simple_util_priv *priv = data;
of_platform_depopulate(simple_priv_to_dev(priv));
}
static int simple_populate_aux(struct simple_util_priv *priv)
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *node __free(device_node) = of_get_child_by_name(dev->of_node, PREFIX "additional-devs");
int ret;
if (!node)
return 0;
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret)
goto end;
ret = devm_add_action_or_reset(dev, simple_depopulate_aux, priv);
end:
return simple_ret(priv, ret);
}
static int simple_parse_of(struct simple_util_priv *priv, struct link_info *li)
{
struct snd_soc_card *card = simple_priv_to_card(priv);
int ret;
ret = simple_util_parse_widgets(card, PREFIX);
if (ret < 0)
goto end;
ret = simple_util_parse_routing(card, PREFIX);
if (ret < 0)
goto end;
ret = simple_util_parse_pin_switches(card, PREFIX);
if (ret < 0)
goto end;
/* Single/Muti DAI link(s) & New style of DT node */
memset(li, 0, sizeof(*li));
ret = simple_for_each_link(priv, li,
simple_dai_link_of,
simple_dai_link_of_dpcm);
if (ret < 0)
goto end;
ret = simple_util_parse_card_name(priv, PREFIX);
if (ret < 0)
goto end;
ret = simple_populate_aux(priv);
if (ret < 0)
goto end;
ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs");
end:
return simple_ret(priv, ret);
}
static int simple_count_noml(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li, bool is_top)
{
int ret = -EINVAL;
if (li->link >= SNDRV_MAX_LINKS)
goto end;
/*
* DON'T REMOVE platforms
*
* Some CPU might be using soc-generic-dmaengine-pcm. This means CPU and Platform
* are different Component, but are sharing same component->dev.
* Simple Card had been supported it without special Platform selection.
* We need platforms here.
*
* In case of no Platform, it will be Platform == CPU, but Platform will be
* ignored by snd_soc_rtd_add_component().
*
* see
* simple-card-utils.c :: simple_util_canonicalize_platform()
*/
li->num[li->link].cpus = 1;
li->num[li->link].platforms = 1;
li->num[li->link].codecs = 1;
li->link += 1;
ret = 0;
end:
return simple_ret(priv, ret);
}
static int simple_count_dpcm(struct simple_util_priv *priv,
struct device_node *np,
struct device_node *codec,
struct link_info *li, bool is_top)
{
int ret = -EINVAL;
if (li->link >= SNDRV_MAX_LINKS)
goto end;
if (li->cpu) {
/*
* DON'T REMOVE platforms
* see
* simple_count_noml()
*/
li->num[li->link].cpus = 1;
li->num[li->link].platforms = 1;
li->link++; /* CPU-dummy */
} else {
li->num[li->link].codecs = 1;
li->link++; /* dummy-Codec */
}
ret = 0;
end:
return simple_ret(priv, ret);
}
static int simple_get_dais_count(struct simple_util_priv *priv,
struct link_info *li)
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *top = dev->of_node;
/*
* link_num : number of links.
* CPU-Codec / CPU-dummy / dummy-Codec
* dais_num : number of DAIs
* ccnf_num : number of codec_conf
* same number for "dummy-Codec"
*
* ex1)
* CPU0 --- Codec0 link : 5
* CPU1 --- Codec1 dais : 7
* CPU2 -/ ccnf : 1
* CPU3 --- Codec2
*
* => 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
* => 7 DAIs = 4xCPU + 3xCodec
* => 1 ccnf = 1xdummy-Codec
*
* ex2)
* CPU0 --- Codec0 link : 5
* CPU1 --- Codec1 dais : 6
* CPU2 -/ ccnf : 1
* CPU3 -/
*
* => 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
* => 6 DAIs = 4xCPU + 2xCodec
* => 1 ccnf = 1xdummy-Codec
*
* ex3)
* CPU0 --- Codec0 link : 6
* CPU1 -/ dais : 6
* CPU2 --- Codec1 ccnf : 2
* CPU3 -/
*
* => 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
* => 6 DAIs = 4xCPU + 2xCodec
* => 2 ccnf = 2xdummy-Codec
*
* ex4)
* CPU0 --- Codec0 (convert-rate) link : 3
* CPU1 --- Codec1 dais : 4
* ccnf : 1
*
* => 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec
* => 4 DAIs = 2xCPU + 2xCodec
* => 1 ccnf = 1xdummy-Codec
*/
if (!top) {
li->num[0].cpus = 1;
li->num[0].codecs = 1;
li->num[0].platforms = 1;
li->link = 1;
return 0;
}
return simple_for_each_link(priv, li,
simple_count_noml,
simple_count_dpcm);
}
static int simple_soc_probe(struct snd_soc_card *card)
{
struct simple_util_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
ret = simple_util_init_hp(card, &priv->hp_jack, PREFIX);
if (ret < 0)
goto end;
ret = simple_util_init_mic(card, &priv->mic_jack, PREFIX);
if (ret < 0)
goto end;
ret = simple_util_init_aux_jacks(priv, PREFIX);
end:
return simple_ret(priv, ret);
}
static int simple_probe(struct platform_device *pdev)
{
struct simple_util_priv *priv;
struct device *dev = &pdev->dev;
struct snd_soc_card *card;
int ret;
/* Allocate the private data and the DAI link array */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card = simple_priv_to_card(priv);
card->owner = THIS_MODULE;
card->dev = dev;
card->probe = simple_soc_probe;
card->driver_name = "simple-card";
ret = -ENOMEM;
struct link_info *li __free(kfree) = kzalloc_obj(*li);
if (!li)
goto end;
ret = simple_get_dais_count(priv, li);
if (ret < 0)
goto end;
ret = -EINVAL;
if (!li->link)
goto end;
ret = simple_util_init_priv(priv, li);
if (ret < 0)
goto end;
ret = simple_parse_of(priv, li);
if (ret < 0) {
dev_err_probe(dev, ret, "parse error\n");
goto err;
}
snd_soc_card_set_drvdata(card, priv);
simple_util_debug_info(priv);
ret = devm_snd_soc_register_card(dev, card);
if (ret < 0)
goto err;
return 0;
err:
simple_util_clean_reference(card);
end:
return dev_err_probe(dev, ret, "parse error\n");
}
static const struct of_device_id simple_of_match[] = {
{ .compatible = "simple-audio-card", },
{ .compatible = "simple-scu-audio-card",
.data = (void *)DPCM_SELECTABLE },
{},
};
MODULE_DEVICE_TABLE(of, simple_of_match);
static struct platform_driver simple_card = {
.driver = {
.name = "asoc-simple-card",
.pm = &snd_soc_pm_ops,
.of_match_table = simple_of_match,
},
.probe = simple_probe,
.remove = simple_util_remove,
};
module_platform_driver(simple_card);
MODULE_ALIAS("platform:asoc-simple-card");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ASoC Simple Sound Card");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
@@ -0,0 +1,650 @@
// SPDX-License-Identifier: GPL-2.0
//
// test-component.c -- Test Audio Component driver
//
// Copyright (C) 2020 Renesas Electronics Corporation
// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#define TEST_NAME_LEN 32
struct test_dai_name {
char name[TEST_NAME_LEN];
char name_playback[TEST_NAME_LEN];
char name_capture[TEST_NAME_LEN];
};
struct test_priv {
struct device *dev;
struct snd_pcm_substream *substream;
struct delayed_work dwork;
struct snd_soc_component_driver *component_driver;
struct snd_soc_dai_driver *dai_driver;
struct test_dai_name *name;
};
struct test_adata {
u32 is_cpu:1;
u32 cmp_v:1;
u32 dai_v:1;
};
#define mile_stone(d) dev_info((d)->dev, "%s() : %s", __func__, (d)->driver->name)
#define mile_stone_x(dev) dev_info(dev, "%s()", __func__)
static int test_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
mile_stone(dai);
return 0;
}
static int test_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out)
{
mile_stone(dai);
return 0;
}
static int test_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
{
mile_stone(dai);
return 0;
}
static int test_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
unsigned int format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
unsigned int clock = fmt & SND_SOC_DAIFMT_CLOCK_MASK;
unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
unsigned int master = fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
char *str;
dev_info(dai->dev, "name : %s", dai->name);
str = "unknown";
switch (format) {
case SND_SOC_DAIFMT_I2S:
str = "i2s";
break;
case SND_SOC_DAIFMT_RIGHT_J:
str = "right_j";
break;
case SND_SOC_DAIFMT_LEFT_J:
str = "left_j";
break;
case SND_SOC_DAIFMT_DSP_A:
str = "dsp_a";
break;
case SND_SOC_DAIFMT_DSP_B:
str = "dsp_b";
break;
case SND_SOC_DAIFMT_AC97:
str = "ac97";
break;
case SND_SOC_DAIFMT_PDM:
str = "pdm";
break;
}
dev_info(dai->dev, "format : %s", str);
if (clock == SND_SOC_DAIFMT_CONT)
str = "continuous";
else
str = "gated";
dev_info(dai->dev, "clock : %s", str);
str = "unknown";
switch (master) {
case SND_SOC_DAIFMT_BP_FP:
str = "clk provider, frame provider";
break;
case SND_SOC_DAIFMT_BC_FP:
str = "clk consumer, frame provider";
break;
case SND_SOC_DAIFMT_BP_FC:
str = "clk provider, frame consumer";
break;
case SND_SOC_DAIFMT_BC_FC:
str = "clk consumer, frame consumer";
break;
}
dev_info(dai->dev, "clock : codec is %s", str);
str = "unknown";
switch (inv) {
case SND_SOC_DAIFMT_NB_NF:
str = "normal bit, normal frame";
break;
case SND_SOC_DAIFMT_NB_IF:
str = "normal bit, invert frame";
break;
case SND_SOC_DAIFMT_IB_NF:
str = "invert bit, normal frame";
break;
case SND_SOC_DAIFMT_IB_IF:
str = "invert bit, invert frame";
break;
}
dev_info(dai->dev, "signal : %s", str);
return 0;
}
static int test_dai_set_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
dev_info(dai->dev, "set tdm slot: tx_mask=0x%08X, rx_mask=0x%08X, slots=%d, slot_width=%d\n",
tx_mask, rx_mask, slots, slot_width);
return 0;
}
static int test_dai_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
{
mile_stone(dai);
return 0;
}
static int test_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
mile_stone(dai);
return 0;
}
static void test_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
mile_stone(dai);
}
static int test_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
mile_stone(dai);
return 0;
}
static int test_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
mile_stone(dai);
return 0;
}
static int test_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
mile_stone(dai);
return 0;
}
static const u64 test_dai_formats =
SND_SOC_POSSIBLE_DAIFMT_I2S |
SND_SOC_POSSIBLE_DAIFMT_RIGHT_J |
SND_SOC_POSSIBLE_DAIFMT_LEFT_J |
SND_SOC_POSSIBLE_DAIFMT_DSP_A |
SND_SOC_POSSIBLE_DAIFMT_DSP_B |
SND_SOC_POSSIBLE_DAIFMT_AC97 |
SND_SOC_POSSIBLE_DAIFMT_PDM |
SND_SOC_POSSIBLE_DAIFMT_NB_NF |
SND_SOC_POSSIBLE_DAIFMT_NB_IF |
SND_SOC_POSSIBLE_DAIFMT_IB_NF |
SND_SOC_POSSIBLE_DAIFMT_IB_IF;
static const struct snd_soc_dai_ops test_ops = {
.set_fmt = test_dai_set_fmt,
.set_tdm_slot = test_dai_set_tdm_slot,
.startup = test_dai_startup,
.shutdown = test_dai_shutdown,
.auto_selectable_formats = &test_dai_formats,
.num_auto_selectable_formats = 1,
};
static const struct snd_soc_dai_ops test_verbose_ops = {
.set_sysclk = test_dai_set_sysclk,
.set_pll = test_dai_set_pll,
.set_clkdiv = test_dai_set_clkdiv,
.set_fmt = test_dai_set_fmt,
.set_tdm_slot = test_dai_set_tdm_slot,
.mute_stream = test_dai_mute_stream,
.startup = test_dai_startup,
.shutdown = test_dai_shutdown,
.hw_params = test_dai_hw_params,
.hw_free = test_dai_hw_free,
.trigger = test_dai_trigger,
.auto_selectable_formats = &test_dai_formats,
.num_auto_selectable_formats = 1,
};
#define STUB_RATES SNDRV_PCM_RATE_CONTINUOUS
#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_U8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_U16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S24_3LE | \
SNDRV_PCM_FMTBIT_U24_LE | \
SNDRV_PCM_FMTBIT_S32_LE | \
SNDRV_PCM_FMTBIT_U32_LE)
static int test_component_probe(struct snd_soc_component *component)
{
mile_stone(component);
return 0;
}
static void test_component_remove(struct snd_soc_component *component)
{
mile_stone(component);
}
static int test_component_suspend(struct snd_soc_component *component)
{
mile_stone(component);
return 0;
}
static int test_component_resume(struct snd_soc_component *component)
{
mile_stone(component);
return 0;
}
#define PREALLOC_BUFFER (32 * 1024)
static int test_component_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
mile_stone(component);
snd_pcm_set_managed_buffer_all(
rtd->pcm,
SNDRV_DMA_TYPE_DEV,
rtd->card->snd_card->dev,
PREALLOC_BUFFER, PREALLOC_BUFFER);
return 0;
}
static void test_component_pcm_free(struct snd_soc_component *component,
struct snd_pcm *pcm)
{
mile_stone(component);
}
static int test_component_set_sysclk(struct snd_soc_component *component,
int clk_id, int source, unsigned int freq, int dir)
{
mile_stone(component);
return 0;
}
static int test_component_set_pll(struct snd_soc_component *component, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out)
{
mile_stone(component);
return 0;
}
static int test_component_set_jack(struct snd_soc_component *component,
struct snd_soc_jack *jack, void *data)
{
mile_stone(component);
return 0;
}
static void test_component_seq_notifier(struct snd_soc_component *component,
enum snd_soc_dapm_type type, int subseq)
{
mile_stone(component);
}
static int test_component_stream_event(struct snd_soc_component *component, int event)
{
mile_stone(component);
return 0;
}
static int test_component_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
mile_stone(component);
return 0;
}
static const struct snd_pcm_hardware test_component_hardware = {
/* Random values to keep userspace happy when checking constraints */
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.buffer_bytes_max = 32 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8192,
.periods_min = 1,
.periods_max = 128,
.fifo_size = 256,
};
static int test_component_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
mile_stone(component);
/* BE's dont need dummy params */
if (!rtd->dai_link->no_pcm)
snd_soc_set_runtime_hwparams(substream, &test_component_hardware);
return 0;
}
static int test_component_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
mile_stone(component);
return 0;
}
static int test_component_ioctl(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
unsigned int cmd, void *arg)
{
mile_stone(component);
return 0;
}
static int test_component_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
mile_stone(component);
return 0;
}
static int test_component_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
mile_stone(component);
return 0;
}
static int test_component_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
mile_stone(component);
return 0;
}
static void test_component_timer_stop(struct test_priv *priv)
{
cancel_delayed_work(&priv->dwork);
}
static void test_component_timer_start(struct test_priv *priv)
{
schedule_delayed_work(&priv->dwork, msecs_to_jiffies(10));
}
static void test_component_dwork(struct work_struct *work)
{
struct test_priv *priv = container_of(work, struct test_priv, dwork.work);
if (priv->substream)
snd_pcm_period_elapsed(priv->substream);
test_component_timer_start(priv);
}
static int test_component_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
struct test_priv *priv = dev_get_drvdata(component->dev);
mile_stone(component);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
test_component_timer_start(priv);
priv->substream = substream; /* set substream later */
break;
case SNDRV_PCM_TRIGGER_STOP:
priv->substream = NULL;
test_component_timer_stop(priv);
}
return 0;
}
static int test_component_sync_stop(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
mile_stone(component);
return 0;
}
static snd_pcm_uframes_t test_component_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
static int pointer;
if (!runtime)
return 0;
pointer += 10;
if (pointer > PREALLOC_BUFFER)
pointer = 0;
/* mile_stone(component); */
return bytes_to_frames(runtime, pointer);
}
static int test_component_get_time_info(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct timespec64 *system_ts,
struct timespec64 *audio_ts,
struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
{
mile_stone(component);
return 0;
}
static int test_component_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
mile_stone_x(rtd->dev);
return 0;
}
/* CPU */
static const struct test_adata test_cpu = { .is_cpu = 1, .cmp_v = 0, .dai_v = 0, };
static const struct test_adata test_cpu_vv = { .is_cpu = 1, .cmp_v = 1, .dai_v = 1, };
static const struct test_adata test_cpu_nv = { .is_cpu = 1, .cmp_v = 0, .dai_v = 1, };
static const struct test_adata test_cpu_vn = { .is_cpu = 1, .cmp_v = 1, .dai_v = 0, };
/* Codec */
static const struct test_adata test_codec = { .is_cpu = 0, .cmp_v = 0, .dai_v = 0, };
static const struct test_adata test_codec_vv = { .is_cpu = 0, .cmp_v = 1, .dai_v = 1, };
static const struct test_adata test_codec_nv = { .is_cpu = 0, .cmp_v = 0, .dai_v = 1, };
static const struct test_adata test_codec_vn = { .is_cpu = 0, .cmp_v = 1, .dai_v = 0, };
static const struct of_device_id test_of_match[] = {
{ .compatible = "test-cpu", .data = (void *)&test_cpu, },
{ .compatible = "test-cpu-verbose", .data = (void *)&test_cpu_vv, },
{ .compatible = "test-cpu-verbose-dai", .data = (void *)&test_cpu_nv, },
{ .compatible = "test-cpu-verbose-component", .data = (void *)&test_cpu_vn, },
{ .compatible = "test-codec", .data = (void *)&test_codec, },
{ .compatible = "test-codec-verbose", .data = (void *)&test_codec_vv, },
{ .compatible = "test-codec-verbose-dai", .data = (void *)&test_codec_nv, },
{ .compatible = "test-codec-verbose-component", .data = (void *)&test_codec_vn, },
{},
};
MODULE_DEVICE_TABLE(of, test_of_match);
static const struct snd_soc_dapm_widget widgets[] = {
/*
* FIXME
*
* Just IN/OUT is OK for now,
* but need to be updated ?
*/
SND_SOC_DAPM_INPUT("IN"),
SND_SOC_DAPM_OUTPUT("OUT"),
};
static int test_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
const struct test_adata *adata = of_device_get_match_data(&pdev->dev);
struct snd_soc_component_driver *cdriv;
struct snd_soc_dai_driver *ddriv;
struct test_dai_name *dname;
struct test_priv *priv;
int num, ret, i;
num = of_graph_get_endpoint_count(node);
if (!num) {
dev_err(dev, "no port exits\n");
return -EINVAL;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
cdriv = devm_kzalloc(dev, sizeof(*cdriv), GFP_KERNEL);
ddriv = devm_kcalloc(dev, num, sizeof(*ddriv), GFP_KERNEL);
dname = devm_kcalloc(dev, num, sizeof(*dname), GFP_KERNEL);
if (!priv || !cdriv || !ddriv || !dname || !adata)
return -EINVAL;
priv->dev = dev;
priv->component_driver = cdriv;
priv->dai_driver = ddriv;
priv->name = dname;
INIT_DELAYED_WORK(&priv->dwork, test_component_dwork);
dev_set_drvdata(dev, priv);
if (adata->is_cpu) {
cdriv->name = "test_cpu";
cdriv->pcm_new = test_component_pcm_new;
cdriv->pointer = test_component_pointer;
cdriv->trigger = test_component_trigger;
cdriv->legacy_dai_naming = 1;
} else {
cdriv->name = "test_codec";
cdriv->idle_bias_on = 1;
cdriv->endianness = 1;
}
cdriv->open = test_component_open;
cdriv->dapm_widgets = widgets;
cdriv->num_dapm_widgets = ARRAY_SIZE(widgets);
if (adata->cmp_v) {
cdriv->probe = test_component_probe;
cdriv->remove = test_component_remove;
cdriv->suspend = test_component_suspend;
cdriv->resume = test_component_resume;
cdriv->set_sysclk = test_component_set_sysclk;
cdriv->set_pll = test_component_set_pll;
cdriv->set_jack = test_component_set_jack;
cdriv->seq_notifier = test_component_seq_notifier;
cdriv->stream_event = test_component_stream_event;
cdriv->set_bias_level = test_component_set_bias_level;
cdriv->close = test_component_close;
cdriv->ioctl = test_component_ioctl;
cdriv->hw_params = test_component_hw_params;
cdriv->hw_free = test_component_hw_free;
cdriv->prepare = test_component_prepare;
cdriv->sync_stop = test_component_sync_stop;
cdriv->get_time_info = test_component_get_time_info;
cdriv->be_hw_params_fixup = test_component_be_hw_params_fixup;
if (adata->is_cpu)
cdriv->pcm_free = test_component_pcm_free;
}
i = 0;
for_each_of_graph_port(node, port) {
snprintf(dname[i].name, TEST_NAME_LEN, "%s.%d", node->name, i);
ddriv[i].name = dname[i].name;
snprintf(dname[i].name_playback, TEST_NAME_LEN, "DAI%d Playback", i);
ddriv[i].playback.stream_name = dname[i].name_playback;
ddriv[i].playback.channels_min = 1;
ddriv[i].playback.channels_max = 384;
ddriv[i].playback.rates = STUB_RATES;
ddriv[i].playback.formats = STUB_FORMATS;
snprintf(dname[i].name_capture, TEST_NAME_LEN, "DAI%d Capture", i);
ddriv[i].capture.stream_name = dname[i].name_capture;
ddriv[i].capture.channels_min = 1;
ddriv[i].capture.channels_max = 384;
ddriv[i].capture.rates = STUB_RATES;
ddriv[i].capture.formats = STUB_FORMATS;
if (adata->dai_v)
ddriv[i].ops = &test_verbose_ops;
else
ddriv[i].ops = &test_ops;
i++;
}
ret = devm_snd_soc_register_component(dev, cdriv, ddriv, num);
if (ret < 0)
return ret;
mile_stone_x(dev);
return 0;
}
static void test_driver_remove(struct platform_device *pdev)
{
mile_stone_x(&pdev->dev);
}
static struct platform_driver test_driver = {
.driver = {
.name = "test-component",
.of_match_table = test_of_match,
},
.probe = test_driver_probe,
.remove = test_driver_remove,
};
module_platform_driver(test_driver);
MODULE_ALIAS("platform:asoc-test-component");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
MODULE_DESCRIPTION("ASoC Test Component");
MODULE_LICENSE("GPL v2");