summaryrefslogtreecommitdiffstats
path: root/sound/soc/generic
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /sound/soc/generic
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'sound/soc/generic')
-rw-r--r--sound/soc/generic/Kconfig43
-rw-r--r--sound/soc/generic/Makefile14
-rw-r--r--sound/soc/generic/audio-graph-card.c661
-rw-r--r--sound/soc/generic/audio-graph-card2-custom-sample.c186
-rw-r--r--sound/soc/generic/audio-graph-card2-custom-sample1.dtsi396
-rw-r--r--sound/soc/generic/audio-graph-card2-custom-sample2.dtsi382
-rw-r--r--sound/soc/generic/audio-graph-card2.c1415
-rw-r--r--sound/soc/generic/simple-card-utils.c1251
-rw-r--r--sound/soc/generic/simple-card.c785
-rw-r--r--sound/soc/generic/test-component.c650
10 files changed, 5783 insertions, 0 deletions
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig
new file mode 100644
index 000000000..64b0817e2
--- /dev/null
+++ b/sound/soc/generic/Kconfig
@@ -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
diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile
new file mode 100644
index 000000000..d5abb3eed
--- /dev/null
+++ b/sound/soc/generic/Makefile
@@ -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
diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c
new file mode 100644
index 000000000..0a8a6d891
--- /dev/null
+++ b/sound/soc/generic/audio-graph-card.c
@@ -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(priv, NULL);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_routing(priv, 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;
+
+ /* Card name should be set after graph_for_each_link() */
+ 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);
+err:
+ if (ret < 0) {
+ simple_util_clean_reference(priv);
+ return dev_err_probe(dev, ret, "parse error\n");
+ }
+end:
+ return graph_ret(priv, ret);
+}
+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>");
diff --git a/sound/soc/generic/audio-graph-card2-custom-sample.c b/sound/soc/generic/audio-graph-card2-custom-sample.c
new file mode 100644
index 000000000..14b212675
--- /dev/null
+++ b/sound/soc/generic/audio-graph-card2-custom-sample.c
@@ -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>");
diff --git a/sound/soc/generic/audio-graph-card2-custom-sample1.dtsi b/sound/soc/generic/audio-graph-card2-custom-sample1.dtsi
new file mode 100644
index 000000000..12d40e05d
--- /dev/null
+++ b/sound/soc/generic/audio-graph-card2-custom-sample1.dtsi
@@ -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>; }; };
+ };
+ };
+};
diff --git a/sound/soc/generic/audio-graph-card2-custom-sample2.dtsi b/sound/soc/generic/audio-graph-card2-custom-sample2.dtsi
new file mode 100644
index 000000000..1fb061a10
--- /dev/null
+++ b/sound/soc/generic/audio-graph-card2-custom-sample2.dtsi
@@ -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>; };};
+ };
+ };
+};
diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c
new file mode 100644
index 000000000..9fb3d3df5
--- /dev/null
+++ b/sound/soc/generic/audio-graph-card2.c
@@ -0,0 +1,1415 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ASoC Audio Graph Card2 support
+//
+// Copyright (C) 2020 Renesas Electronics Corp.
+// Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+//
+// based on ${LINUX}/sound/soc/generic/audio-graph-card.c
+#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>
+
+/************************************
+ daifmt
+ ************************************
+ ports {
+ format = "left_j";
+ port@0 {
+ bitclock-master;
+ sample0: endpoint@0 {
+ frame-master;
+ };
+ sample1: endpoint@1 {
+ format = "i2s";
+ };
+ };
+ ...
+ };
+
+ You can set daifmt at ports/port/endpoint.
+ It uses *latest* format, and *share* master settings.
+ In above case,
+ sample0: left_j, bitclock-master, frame-master
+ sample1: i2s, bitclock-master
+
+ If there was no settings, *Codec* will be
+ bitclock/frame provider as default.
+ see
+ graph_parse_daifmt().
+
+ "format" property is no longer needed on DT if both CPU/Codec drivers are
+ supporting snd_soc_dai_ops :: .auto_selectable_formats.
+ see
+ snd_soc_runtime_get_dai_fmt()
+
+ sample driver
+ linux/sound/soc/renesas/rcar/core.c
+ linux/sound/soc/codecs/ak4613.c
+ linux/sound/soc/codecs/pcm3168a.c
+ linux/sound/soc/soc-utils.c
+ linux/sound/soc/generic/test-component.c
+
+ ************************************
+ Normal Audio-Graph
+ ************************************
+
+ CPU <---> Codec
+
+ sound {
+ compatible = "audio-graph-card2";
+ links = <&cpu>;
+ };
+
+ CPU {
+ cpu: port {
+ bitclock-master;
+ frame-master;
+ cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; };
+ };
+
+ Codec {
+ port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; };
+ };
+
+ ************************************
+ Multi-CPU/Codec
+ ************************************
+
+It has link connection part (= X,x) and list part (= A,B,a,b).
+"links" is connection part of CPU side (= @).
+
+ +----+ +---+
+ CPU1 --|A X| <-@----> |x a|-- Codec1
+ CPU2 --|B | | b|-- Codec2
+ +----+ +---+
+
+ sound {
+ compatible = "audio-graph-card2";
+
+(@) links = <&mcpu>;
+
+ multi {
+ ports@0 {
+(@) mcpu: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; // (X) to pair
+ port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; // (A) Multi Element
+ port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; // (B) Multi Element
+ };
+ ports@1 {
+ port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; // (x) to pair
+ port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; // (a) Multi Element
+ port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; // (b) Multi Element
+ };
+ };
+ };
+
+ CPU {
+ ports {
+ bitclock-master;
+ frame-master;
+ port@0 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; };
+ port@1 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; };
+ };
+ };
+
+ Codec {
+ ports {
+ port@0 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; };
+ port@1 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; };
+ };
+ };
+
+ ************************************
+ DPCM
+ ************************************
+
+ DSP
+ ************
+ PCM0 <--> * fe0 be0 * <--> DAI0: Codec Headset
+ PCM1 <--> * fe1 be1 * <--> DAI1: Codec Speakers
+ PCM2 <--> * fe2 be2 * <--> DAI2: MODEM
+ PCM3 <--> * fe3 be3 * <--> DAI3: BT
+ * be4 * <--> DAI4: DMIC
+ * be5 * <--> DAI5: FM
+ ************
+
+ sound {
+ compatible = "audio-graph-card2";
+
+ // indicate routing
+ routing = "xxx Playback", "xxx Playback",
+ "xxx Playback", "xxx Playback",
+ "xxx Playback", "xxx Playback";
+
+ // indicate all Front-End, Back-End
+ links = <&fe0, &fe1, ...,
+ &be0, &be1, ...>;
+
+ dpcm {
+ // Front-End
+ ports@0 {
+ fe0: port@0 { fe0_ep: endpoint { remote-endpoint = <&pcm0_ep>; }; };
+ fe1: port@1 { fe1_ep: endpoint { remote-endpoint = <&pcm1_ep>; }; };
+ ...
+ };
+ // Back-End
+ ports@1 {
+ be0: port@0 { be0_ep: endpoint { remote-endpoint = <&dai0_ep>; }; };
+ be1: port@1 { be1_ep: endpoint { remote-endpoint = <&dai1_ep>; }; };
+ ...
+ };
+ };
+ };
+
+ CPU {
+ ports {
+ bitclock-master;
+ frame-master;
+ port@0 { pcm0_ep: endpoint { remote-endpoint = <&fe0_ep>; }; };
+ port@1 { pcm1_ep: endpoint { remote-endpoint = <&fe1_ep>; }; };
+ ...
+ };
+ };
+
+ Codec {
+ ports {
+ port@0 { dai0_ep: endpoint { remote-endpoint = <&be0_ep>; }; };
+ port@1 { dai1_ep: endpoint { remote-endpoint = <&be1_ep>; }; };
+ ...
+ };
+ };
+
+ ************************************
+ Codec to Codec
+ ************************************
+
+ +--+
+ | |<-- Codec0 <- IN
+ | |--> Codec1 -> OUT
+ +--+
+
+ sound {
+ compatible = "audio-graph-card2";
+
+ routing = "OUT" ,"DAI1 Playback",
+ "DAI0 Capture", "IN";
+
+ links = <&c2c>;
+
+ codec2codec {
+ ports {
+ rate = <48000>;
+ c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec0_ep>; }; };
+ port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec1_ep>; }; };
+ };
+ };
+
+ Codec {
+ ports {
+ port@0 {
+ bitclock-master;
+ frame-master;
+ codec0_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; };
+ port@1 { codec1_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; };
+ };
+ };
+
+*/
+
+enum graph_type {
+ GRAPH_NORMAL,
+ GRAPH_DPCM,
+ GRAPH_C2C,
+
+ GRAPH_MULTI, /* don't use ! Use this only in __graph_get_type() */
+};
+
+#define GRAPH_NODENAME_MULTI "multi"
+#define GRAPH_NODENAME_DPCM "dpcm"
+#define GRAPH_NODENAME_C2C "codec2codec"
+
+#define 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 enum graph_type __graph_get_type(struct device_node *lnk)
+{
+ struct device_node *np, *parent_np;
+ enum graph_type ret;
+
+ /*
+ * target {
+ * ports {
+ * => lnk: port@0 { ... };
+ * port@1 { ... };
+ * };
+ * };
+ */
+ np = of_get_parent(lnk);
+ if (of_node_name_eq(np, "ports")) {
+ parent_np = of_get_parent(np);
+ of_node_put(np);
+ np = parent_np;
+ }
+
+ if (of_node_name_eq(np, GRAPH_NODENAME_MULTI)) {
+ ret = GRAPH_MULTI;
+ fw_devlink_purge_absent_suppliers(&np->fwnode);
+ goto out_put;
+ }
+
+ if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) {
+ ret = GRAPH_DPCM;
+ fw_devlink_purge_absent_suppliers(&np->fwnode);
+ goto out_put;
+ }
+
+ if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) {
+ ret = GRAPH_C2C;
+ fw_devlink_purge_absent_suppliers(&np->fwnode);
+ goto out_put;
+ }
+
+ ret = GRAPH_NORMAL;
+
+out_put:
+ of_node_put(np);
+ return ret;
+
+}
+
+static enum graph_type graph_get_type(struct simple_util_priv *priv,
+ struct device_node *lnk)
+{
+ enum graph_type type = __graph_get_type(lnk);
+
+ /* GRAPH_MULTI here means GRAPH_NORMAL */
+ if (type == GRAPH_MULTI)
+ type = GRAPH_NORMAL;
+
+#ifdef DEBUG
+ {
+ struct device *dev = simple_priv_to_dev(priv);
+ const char *str = "Normal";
+
+ switch (type) {
+ case GRAPH_DPCM:
+ if (graph_util_is_ports0(lnk))
+ str = "DPCM Front-End";
+ else
+ str = "DPCM Back-End";
+ break;
+ case GRAPH_C2C:
+ str = "Codec2Codec";
+ break;
+ default:
+ break;
+ }
+
+ dev_dbg(dev, "%pOF (%s)", lnk, str);
+ }
+#endif
+ return type;
+}
+
+static int graph_lnk_is_multi(struct device_node *lnk)
+{
+ return __graph_get_type(lnk) == GRAPH_MULTI;
+}
+
+static struct device_node *graph_get_next_multi_ep(struct device_node **port, int idx)
+{
+ struct device_node *ports __free(device_node) = port_to_ports(*port);
+ struct device_node *rep = NULL;
+
+ /*
+ * multi {
+ * ports {
+ * => lnk: port@0 { ... }; // to pair
+ * port@1 { ep { ... = rep0 } }; // Multi Element
+ * port@2 { ep { ... = rep1 } }; // Multi Element
+ * ...
+ * };
+ * };
+ *
+ * xxx {
+ * port@0 { rep0 };
+ * port@1 { rep1 };
+ * };
+ */
+
+ /*
+ * Don't use of_graph_get_next_port() here
+ *
+ * In overlay case, "port" are not necessarily in order. So we need to use
+ * of_graph_get_port_by_id() instead
+ */
+ of_node_put(*port);
+
+ *port = of_graph_get_port_by_id(ports, idx);
+ if (*port) {
+ struct device_node *ep __free(device_node) = of_graph_get_next_port_endpoint(*port, NULL);
+
+ rep = of_graph_get_remote_endpoint(ep);
+ }
+
+ return rep;
+}
+
+static const struct snd_soc_ops graph_ops = {
+ .startup = simple_util_startup,
+ .shutdown = simple_util_shutdown,
+ .hw_params = simple_util_hw_params,
+};
+
+static void graph_parse_convert(struct device_node *ep,
+ struct simple_dai_props *props)
+{
+ struct device_node *port __free(device_node) = ep_to_port(ep);
+ struct device_node *ports __free(device_node) = port_to_ports(port);
+ struct simple_util_data *adata = &props->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,
+ enum graph_type gtype,
+ struct device_node *ep,
+ struct link_info *li,
+ int is_cpu, int idx)
+{
+ 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, is_single_links = 0;
+
+ if (is_cpu) {
+ dlc = snd_soc_link_to_cpu(dai_link, idx);
+ dai = simple_props_to_dai_cpu(dai_props, idx);
+ } else {
+ dlc = snd_soc_link_to_codec(dai_link, idx);
+ dai = simple_props_to_dai_codec(dai_props, idx);
+ }
+
+ ret = graph_util_parse_dai(priv, ep, dlc, &is_single_links);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_tdm(ep, dai);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_tdm_width_map(priv, ep, dai);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_clk(dev, ep, dai, dlc);
+ if (ret < 0)
+ goto end;
+
+ /*
+ * set DAI Name
+ */
+ if (!dai_link->name) {
+ struct snd_soc_dai_link_component *cpus = dlc;
+ struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, idx);
+ char *cpu_multi = "";
+ char *codec_multi = "";
+
+ if (dai_link->num_cpus > 1)
+ cpu_multi = "_multi";
+ if (dai_link->num_codecs > 1)
+ codec_multi = "_multi";
+
+ switch (gtype) {
+ case GRAPH_NORMAL:
+ /* run is_cpu only. see audio_graph2_link_normal() */
+ if (is_cpu)
+ simple_util_set_dailink_name(priv, dai_link, "%s%s-%s%s",
+ cpus->dai_name, cpu_multi,
+ codecs->dai_name, codec_multi);
+ break;
+ case GRAPH_DPCM:
+ if (is_cpu)
+ simple_util_set_dailink_name(priv, dai_link, "fe.%pOFP.%s%s",
+ cpus->of_node, cpus->dai_name, cpu_multi);
+ else
+ simple_util_set_dailink_name(priv, dai_link, "be.%pOFP.%s%s",
+ codecs->of_node, codecs->dai_name, codec_multi);
+ break;
+ case GRAPH_C2C:
+ /* run is_cpu only. see audio_graph2_link_c2c() */
+ if (is_cpu)
+ simple_util_set_dailink_name(priv, dai_link, "c2c.%s%s-%s%s",
+ cpus->dai_name, cpu_multi,
+ codecs->dai_name, codec_multi);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Check "prefix" from top node
+ * if DPCM-BE case
+ */
+ if (!is_cpu && gtype == GRAPH_DPCM) {
+ struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, idx);
+ struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, idx);
+ struct device_node *rport __free(device_node) = ep_to_port(ep);
+ struct device_node *rports __free(device_node) = port_to_ports(rport);
+
+ snd_soc_of_parse_node_prefix(rports, cconf, codecs->of_node, "prefix");
+ snd_soc_of_parse_node_prefix(rport, cconf, codecs->of_node, "prefix");
+ }
+
+ if (is_cpu) {
+ struct snd_soc_dai_link_component *cpus = dlc;
+ struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, idx);
+
+ simple_util_canonicalize_cpu(cpus, is_single_links);
+ simple_util_canonicalize_platform(platforms, cpus);
+ }
+end:
+ return graph_ret(priv, ret);
+}
+
+static int graph_parse_node_multi_nm(struct simple_util_priv *priv,
+ struct snd_soc_dai_link *dai_link,
+ int *nm_idx, int cpu_idx,
+ struct device_node *mcpu_port)
+{
+ /*
+ * +---+ +---+
+ * | X|<-@------->|x |
+ * | | | |
+ * cpu0 <--|A 1|<--------->|4 a|-> codec0
+ * cpu1 <--|B 2|<-----+--->|5 b|-> codec1
+ * cpu2 <--|C 3|<----/ +---+
+ * +---+
+ *
+ * multi {
+ * ports {
+ * port@0 { mcpu_top_ep {... = mcodec_ep; }; }; // (X) to pair
+ * <mcpu_port> port@1 { mcpu0_ep { ... = cpu0_ep; }; // (A) Multi Element
+ * mcpu0_ep_0 { ... = mcodec0_ep_0; }; }; // (1) connected Codec
+ * port@2 { mcpu1_ep { ... = cpu1_ep; }; // (B) Multi Element
+ * mcpu1_ep_0 { ... = mcodec1_ep_0; }; }; // (2) connected Codec
+ * port@3 { mcpu2_ep { ... = cpu2_ep; }; // (C) Multi Element
+ * mcpu2_ep_0 { ... = mcodec1_ep_1; }; }; // (3) connected Codec
+ * };
+ *
+ * ports {
+ * port@0 { mcodec_top_ep {... = mcpu_ep; }; }; // (x) to pair
+ * <mcodec_port>port@1 { mcodec0_ep { ... = codec0_ep; }; // (a) Multi Element
+ * mcodec0_ep_0 { ... = mcpu0_ep_0; }; }; // (4) connected CPU
+ * port@2 { mcodec1_ep { ... = codec1_ep; }; // (b) Multi Element
+ * mcodec1_ep_0 { ... = mcpu1_ep_0; }; // (5) connected CPU
+ * mcodec1_ep_1 { ... = mcpu2_ep_0; }; }; // (5) connected CPU
+ * };
+ * };
+ */
+ struct device_node *mcpu_ep __free(device_node) = of_graph_get_next_port_endpoint(mcpu_port, NULL);
+ struct device_node *mcpu_ports __free(device_node) = port_to_ports(mcpu_port);
+ struct device_node *mcpu_port_top __free(device_node) = of_graph_get_next_port(mcpu_ports, NULL);
+ struct device_node *mcpu_ep_top __free(device_node) = of_graph_get_next_port_endpoint(mcpu_port_top, NULL);
+ struct device_node *mcodec_ep_top __free(device_node) = of_graph_get_remote_endpoint(mcpu_ep_top);
+ struct device_node *mcodec_port_top __free(device_node) = ep_to_port(mcodec_ep_top);
+ struct device_node *mcodec_ports __free(device_node) = port_to_ports(mcodec_port_top);
+ int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);
+ int ret = -EINVAL;
+
+ if (cpu_idx > dai_link->num_cpus)
+ goto end;
+
+ for_each_of_graph_port_endpoint(mcpu_port, mcpu_ep_n) {
+ int codec_idx = 0;
+
+ /* ignore 1st ep which is for element */
+ if (mcpu_ep_n == mcpu_ep)
+ continue;
+
+ if (*nm_idx > nm_max)
+ break;
+
+ struct device_node *mcodec_ep_n __free(device_node) = of_graph_get_remote_endpoint(mcpu_ep_n);
+ struct device_node *mcodec_port __free(device_node) = ep_to_port(mcodec_ep_n);
+
+ ret = -EINVAL;
+ if (mcodec_ports != port_to_ports(mcodec_port))
+ break;
+
+ for_each_of_graph_port(mcodec_ports, mcodec_port_i) {
+
+ /* ignore 1st port which is for pair connection */
+ if (mcodec_port_top == mcodec_port_i)
+ continue;
+
+ if (codec_idx > dai_link->num_codecs)
+ break;
+
+ if (mcodec_port_i == mcodec_port) {
+ dai_link->ch_maps[*nm_idx].cpu = cpu_idx;
+ dai_link->ch_maps[*nm_idx].codec = codec_idx;
+
+ (*nm_idx)++;
+ ret = 0;
+ break;
+ }
+ codec_idx++;
+ }
+ if (ret < 0)
+ break;
+ }
+end:
+ return graph_ret(priv, ret);
+}
+
+static int graph_parse_node_multi(struct simple_util_priv *priv,
+ enum graph_type gtype,
+ struct device_node *port,
+ struct link_info *li, int is_cpu)
+{
+ struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
+ struct device *dev = simple_priv_to_dev(priv);
+ int ret = -ENOMEM;
+ int nm_idx = 0;
+ int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);
+
+ /*
+ * create ch_maps if CPU:Codec = N:M
+ * DPCM is out of scope
+ */
+ if (gtype != GRAPH_DPCM && !dai_link->ch_maps &&
+ dai_link->num_cpus > 1 && dai_link->num_codecs > 1 &&
+ dai_link->num_cpus != dai_link->num_codecs) {
+
+ dai_link->ch_maps = devm_kcalloc(dev, nm_max,
+ sizeof(struct snd_soc_dai_link_ch_map), GFP_KERNEL);
+ if (!dai_link->ch_maps)
+ goto multi_err;
+ }
+
+ for (int idx = 0;; idx++) {
+ /*
+ * multi {
+ * ports {
+ * <port> port@0 { ... }; // to pair
+ * port@1 { mcpu1_ep { ... = cpu1_ep };}; // Multi Element
+ * port@2 { mcpu2_ep { ... = cpu2_ep };}; // Multi Element
+ * };
+ * };
+ *
+ * cpu {
+ * ports {
+ * <ep> port@0 { cpu1_ep { ... = mcpu1_ep };};
+ * };
+ * };
+ */
+ struct device_node *ep __free(device_node) = graph_get_next_multi_ep(&port, idx + 1);
+ if (!ep)
+ break;
+
+ ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, idx);
+ if (ret < 0)
+ goto multi_err;
+
+ /* CPU:Codec = N:M */
+ if (is_cpu && dai_link->ch_maps) {
+ ret = graph_parse_node_multi_nm(priv, dai_link, &nm_idx, idx, port);
+ if (ret < 0)
+ goto multi_err;
+ }
+ }
+
+ if (is_cpu && dai_link->ch_maps && (nm_idx != nm_max))
+ ret = -EINVAL;
+
+multi_err:
+ return graph_ret(priv, ret);
+}
+
+static int graph_parse_node_single(struct simple_util_priv *priv,
+ enum graph_type gtype,
+ struct device_node *ep,
+ struct link_info *li, int is_cpu)
+{
+ return graph_ret(priv, __graph_parse_node(priv, gtype, ep, li, is_cpu, 0));
+}
+
+static int graph_parse_node(struct simple_util_priv *priv,
+ enum graph_type gtype,
+ struct device_node *ep,
+ struct link_info *li, int is_cpu)
+{
+ struct device_node *port __free(device_node) = ep_to_port(ep);
+ int ret;
+
+ if (graph_lnk_is_multi(port))
+ ret = graph_parse_node_multi(priv, gtype, port, li, is_cpu);
+ else
+ ret = graph_parse_node_single(priv, gtype, ep, li, is_cpu);
+
+ return graph_ret(priv, ret);
+}
+
+static void graph_parse_daifmt(struct device_node *node, unsigned int *daifmt)
+{
+ unsigned int fmt;
+
+ if (!node)
+ return;
+
+ /*
+ * see also above "daifmt" explanation
+ * and samples.
+ */
+
+ /*
+ * ports {
+ * (A)
+ * port {
+ * (B)
+ * endpoint {
+ * (C)
+ * };
+ * };
+ * };
+ * };
+ */
+
+#define update_daifmt(name) \
+ if (!(*daifmt & SND_SOC_DAIFMT_##name##_MASK) && \
+ (fmt & SND_SOC_DAIFMT_##name##_MASK)) \
+ *daifmt |= fmt & SND_SOC_DAIFMT_##name##_MASK
+
+ /*
+ * format
+ *
+ * This function is called by (C) -> (B) -> (A) order.
+ * Set if applicable part was not yet set.
+ */
+ fmt = snd_soc_daifmt_parse_format(node, NULL);
+ update_daifmt(FORMAT);
+ update_daifmt(CLOCK);
+ update_daifmt(INV);
+}
+
+static unsigned int graph_parse_bitframe(struct device_node *ep)
+{
+ struct device_node *port __free(device_node) = ep_to_port(ep);
+ struct device_node *ports __free(device_node) = port_to_ports(port);
+
+ return snd_soc_daifmt_clock_provider_from_bitmap(
+ snd_soc_daifmt_parse_clock_provider_as_bitmap(ep, NULL) |
+ snd_soc_daifmt_parse_clock_provider_as_bitmap(port, NULL) |
+ snd_soc_daifmt_parse_clock_provider_as_bitmap(ports, NULL));
+}
+
+static void graph_link_init(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct device_node *ep_cpu,
+ struct device_node *ep_codec,
+ struct link_info *li,
+ int is_cpu_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 = ep_to_port(ep_cpu);
+ struct device_node *port_codec = ep_to_port(ep_codec);
+ struct device_node *multi_cpu_port = NULL, *multi_codec_port = NULL;
+ struct snd_soc_dai_link_component *dlc;
+ unsigned int daifmt = 0;
+ bool playback_only = 0, capture_only = 0;
+ enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;
+ enum snd_soc_trigger_order trigger_stop = SND_SOC_TRIGGER_ORDER_DEFAULT;
+ int multi_cpu_port_idx = 1, multi_codec_port_idx = 1;
+ int i;
+
+ if (graph_lnk_is_multi(port_cpu)) {
+ multi_cpu_port = port_cpu;
+ ep_cpu = graph_get_next_multi_ep(&multi_cpu_port, multi_cpu_port_idx++);
+ of_node_put(port_cpu);
+ port_cpu = ep_to_port(ep_cpu);
+ } else {
+ of_node_get(ep_cpu);
+ }
+ struct device_node *ports_cpu __free(device_node) = port_to_ports(port_cpu);
+
+ if (graph_lnk_is_multi(port_codec)) {
+ multi_codec_port = port_codec;
+ ep_codec = graph_get_next_multi_ep(&multi_codec_port, multi_codec_port_idx++);
+ of_node_put(port_codec);
+ port_codec = ep_to_port(ep_codec);
+ } else {
+ of_node_get(ep_codec);
+ }
+ struct device_node *ports_codec __free(device_node) = port_to_ports(port_codec);
+
+ graph_parse_daifmt(ep_cpu, &daifmt);
+ graph_parse_daifmt(ep_codec, &daifmt);
+ graph_parse_daifmt(port_cpu, &daifmt);
+ graph_parse_daifmt(port_codec, &daifmt);
+ graph_parse_daifmt(ports_cpu, &daifmt);
+ graph_parse_daifmt(ports_codec, &daifmt);
+ graph_parse_daifmt(lnk, &daifmt);
+
+ graph_util_parse_link_direction(lnk, &playback_only, &capture_only);
+ graph_util_parse_link_direction(ports_cpu, &playback_only, &capture_only);
+ graph_util_parse_link_direction(ports_codec, &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(lnk, "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, lnk, &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);
+
+ for_each_link_cpus(dai_link, i, dlc) {
+ dlc->ext_fmt = graph_parse_bitframe(ep_cpu);
+
+ if (multi_cpu_port)
+ ep_cpu = graph_get_next_multi_ep(&multi_cpu_port, multi_cpu_port_idx++);
+ }
+
+ for_each_link_codecs(dai_link, i, dlc) {
+ dlc->ext_fmt = graph_parse_bitframe(ep_codec);
+
+ if (multi_codec_port)
+ ep_codec = graph_get_next_multi_ep(&multi_codec_port, multi_codec_port_idx++);
+ }
+
+ /*** Don't use port_cpu / port_codec after here ***/
+
+ 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->dai_fmt = daifmt;
+ dai_link->init = simple_util_dai_init;
+ dai_link->ops = &graph_ops;
+ if (priv->ops)
+ dai_link->ops = priv->ops;
+
+ of_node_put(port_cpu);
+ of_node_put(port_codec);
+ of_node_put(ep_cpu);
+ of_node_put(ep_codec);
+}
+
+int audio_graph2_link_normal(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device_node *cpu_port = lnk;
+ struct device_node *cpu_ep __free(device_node) = of_graph_get_next_port_endpoint(cpu_port, NULL);
+ struct device_node *codec_ep __free(device_node) = of_graph_get_remote_endpoint(cpu_ep);
+ int ret;
+
+ /*
+ * call Codec first.
+ * see
+ * __graph_parse_node() :: DAI Naming
+ */
+ ret = graph_parse_node(priv, GRAPH_NORMAL, codec_ep, li, 0);
+ if (ret < 0)
+ goto end;
+
+ /*
+ * call CPU, and set DAI Name
+ */
+ ret = graph_parse_node(priv, GRAPH_NORMAL, cpu_ep, li, 1);
+ if (ret < 0)
+ goto end;
+
+ graph_link_init(priv, lnk, cpu_ep, codec_ep, li, 1);
+
+end:
+ return graph_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(audio_graph2_link_normal);
+
+int audio_graph2_link_dpcm(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device_node *ep __free(device_node) = of_graph_get_next_port_endpoint(lnk, NULL);
+ struct device_node *rep __free(device_node) = of_graph_get_remote_endpoint(ep);
+ struct device_node *cpu_ep = NULL;
+ struct device_node *codec_ep = NULL;
+ 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);
+ int is_cpu = graph_util_is_ports0(lnk);
+ int ret;
+
+ if (is_cpu) {
+ cpu_ep = rep;
+
+ /*
+ * dpcm {
+ * // Front-End
+ * ports@0 {
+ * => lnk: port@0 { ep: { ... = rep }; };
+ * ...
+ * };
+ * // Back-End
+ * ports@0 {
+ * ...
+ * };
+ * };
+ *
+ * CPU {
+ * rports: ports {
+ * rport: port@0 { rep: { ... = ep } };
+ * }
+ * }
+ */
+ /*
+ * setup CPU here, Codec is already set as dummy.
+ * see
+ * simple_util_init_priv()
+ */
+ dai_link->dynamic = 1;
+ dai_link->dpcm_merged_format = 1;
+
+ ret = graph_parse_node(priv, GRAPH_DPCM, cpu_ep, li, 1);
+ if (ret)
+ return ret;
+
+ } else {
+ codec_ep = rep;
+
+ /*
+ * dpcm {
+ * // Front-End
+ * ports@0 {
+ * ...
+ * };
+ * // Back-End
+ * ports@0 {
+ * => lnk: port@0 { ep: { ... = rep; }; };
+ * ...
+ * };
+ * };
+ *
+ * Codec {
+ * rports: ports {
+ * rport: port@0 { rep: { ... = ep; }; };
+ * }
+ * }
+ */
+ /*
+ * setup Codec here, CPU is already set as dummy.
+ * see
+ * simple_util_init_priv()
+ */
+
+ /* BE settings */
+ dai_link->no_pcm = 1;
+ dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;
+
+ ret = graph_parse_node(priv, GRAPH_DPCM, codec_ep, li, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ graph_parse_convert(ep, dai_props); /* at node of <dpcm> */
+ graph_parse_convert(rep, dai_props); /* at node of <CPU/Codec> */
+
+ graph_link_init(priv, lnk, cpu_ep, codec_ep, li, is_cpu);
+
+ return graph_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm);
+
+int audio_graph2_link_c2c(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
+ struct device_node *port0 = lnk;
+ struct device_node *ports __free(device_node) = port_to_ports(port0);
+ struct device_node *port1 __free(device_node) = of_graph_get_next_port(ports, port0);
+ u32 val = 0;
+ int ret = -EINVAL;
+
+ /*
+ * codec2codec {
+ * ports {
+ * rate = <48000>;
+ * => lnk: port@0 { c2c0_ep: { ... = codec0_ep; }; };
+ * port@1 { c2c1_ep: { ... = codec1_ep; }; };
+ * };
+ * };
+ *
+ * Codec {
+ * ports {
+ * port@0 { codec0_ep: ... }; };
+ * port@1 { codec1_ep: ... }; };
+ * };
+ * };
+ */
+
+ /*
+ * Card2 can use original Codec2Codec settings if DT has.
+ * It will use default settings if no settings on DT.
+ * see
+ * simple_util_init_for_codec2codec()
+ *
+ * Add more settings here if needed
+ */
+ of_property_read_u32(ports, "rate", &val);
+ if (val) {
+ struct device *dev = simple_priv_to_dev(priv);
+ struct snd_soc_pcm_stream *c2c_conf;
+
+ c2c_conf = devm_kzalloc(dev, sizeof(*c2c_conf), GFP_KERNEL);
+ if (!c2c_conf) {
+ /*
+ * Clang doesn't allow to use "goto end" before calling __free(),
+ * because it bypasses the initialization. Use graph_ret() directly.
+ */
+ return graph_ret(priv, -ENOMEM);
+ }
+
+ c2c_conf->formats = SNDRV_PCM_FMTBIT_S32_LE; /* update ME */
+ c2c_conf->rates = SNDRV_PCM_RATE_8000_384000;
+ c2c_conf->rate_min =
+ c2c_conf->rate_max = val;
+ c2c_conf->channels_min =
+ c2c_conf->channels_max = 2; /* update ME */
+
+ dai_link->c2c_params = c2c_conf;
+ dai_link->num_c2c_params = 1;
+ }
+
+ struct device_node *ep0 __free(device_node) = of_graph_get_next_port_endpoint(port0, NULL);
+ struct device_node *ep1 __free(device_node) = of_graph_get_next_port_endpoint(port1, NULL);
+
+ struct device_node *codec0_ep __free(device_node) = of_graph_get_remote_endpoint(ep0);
+ struct device_node *codec1_ep __free(device_node) = of_graph_get_remote_endpoint(ep1);
+
+ /*
+ * call Codec first.
+ * see
+ * __graph_parse_node() :: DAI Naming
+ */
+ ret = graph_parse_node(priv, GRAPH_C2C, codec1_ep, li, 0);
+ if (ret < 0)
+ goto end;
+
+ /*
+ * call CPU, and set DAI Name
+ */
+ ret = graph_parse_node(priv, GRAPH_C2C, codec0_ep, li, 1);
+ if (ret < 0)
+ goto end;
+
+ graph_link_init(priv, lnk, codec0_ep, codec1_ep, li, 1);
+end:
+ return graph_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(audio_graph2_link_c2c);
+
+static int graph_link(struct simple_util_priv *priv,
+ struct graph2_custom_hooks *hooks,
+ enum graph_type gtype,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device *dev = simple_priv_to_dev(priv);
+ GRAPH2_CUSTOM func = NULL;
+ int ret = -EINVAL;
+
+ switch (gtype) {
+ case GRAPH_NORMAL:
+ if (hooks && hooks->custom_normal)
+ func = hooks->custom_normal;
+ else
+ func = audio_graph2_link_normal;
+ break;
+ case GRAPH_DPCM:
+ if (hooks && hooks->custom_dpcm)
+ func = hooks->custom_dpcm;
+ else
+ func = audio_graph2_link_dpcm;
+ break;
+ case GRAPH_C2C:
+ if (hooks && hooks->custom_c2c)
+ func = hooks->custom_c2c;
+ else
+ func = audio_graph2_link_c2c;
+ break;
+ default:
+ break;
+ }
+
+ if (!func) {
+ dev_err(dev, "non supported gtype (%d)\n", gtype);
+ goto err;
+ }
+
+ ret = func(priv, lnk, li);
+ if (ret < 0)
+ goto err;
+
+ li->link++;
+err:
+ return graph_ret(priv, ret);
+}
+
+static int graph_counter(struct device_node *lnk)
+{
+ /*
+ * Multi CPU / Codec
+ *
+ * multi {
+ * ports {
+ * => lnk: port@0 { ... }; // to pair
+ * port@1 { ... }; // Multi Element
+ * port@2 { ... }; // Multi Element
+ * ...
+ * };
+ * };
+ *
+ * ignore first lnk part
+ */
+ if (graph_lnk_is_multi(lnk)) {
+ struct device_node *ports = port_to_ports(lnk);
+
+ /*
+ * CPU/Codec = N:M case has many endpoints.
+ * We can't use of_graph_get_endpoint_count() here
+ */
+ return of_graph_get_port_count(ports) - 1;
+ }
+ /*
+ * Single CPU / Codec
+ */
+ else
+ return 1;
+}
+
+static int graph_count_normal(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device_node *cpu_port = lnk;
+ struct device_node *cpu_ep __free(device_node) = of_graph_get_next_port_endpoint(cpu_port, NULL);
+ struct device_node *codec_port __free(device_node) = of_graph_get_remote_port(cpu_ep);
+
+ /*
+ * CPU {
+ * => lnk: port { endpoint { .. }; };
+ * };
+ */
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
+ li->num[li->link].cpus =
+ li->num[li->link].platforms = graph_counter(cpu_port);
+
+ li->num[li->link].codecs = graph_counter(codec_port);
+
+ return 0;
+}
+
+static int graph_count_dpcm(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device_node *ep __free(device_node) = of_graph_get_next_port_endpoint(lnk, NULL);
+ struct device_node *rport __free(device_node) = of_graph_get_remote_port(ep);
+
+ /*
+ * dpcm {
+ * // Front-End
+ * ports@0 {
+ * => lnk: port@0 { endpoint { ... }; };
+ * ...
+ * };
+ * // Back-End
+ * ports@1 {
+ * => lnk: port@0 { endpoint { ... }; };
+ * ...
+ * };
+ * };
+ */
+
+ if (graph_util_is_ports0(lnk)) {
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
+ li->num[li->link].cpus = graph_counter(rport); /* FE */
+ li->num[li->link].platforms = graph_counter(rport);
+ } else {
+ li->num[li->link].codecs = graph_counter(rport); /* BE */
+ }
+
+ return 0;
+}
+
+static int graph_count_c2c(struct simple_util_priv *priv,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device_node *ports __free(device_node) = port_to_ports(lnk);
+ struct device_node *port0 = of_node_get(lnk);
+ struct device_node *port1 = of_node_get(of_graph_get_next_port(ports, of_node_get(port0)));
+ struct device_node *ep0 __free(device_node) = of_graph_get_next_port_endpoint(port0, NULL);
+ struct device_node *ep1 __free(device_node) = of_graph_get_next_port_endpoint(port1, NULL);
+ struct device_node *codec0 __free(device_node) = of_graph_get_remote_port(ep0);
+ struct device_node *codec1 __free(device_node) = of_graph_get_remote_port(ep1);
+
+ /*
+ * codec2codec {
+ * ports {
+ * => lnk: port@0 { endpoint { ... }; };
+ * port@1 { endpoint { ... }; };
+ * };
+ * };
+ */
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
+ li->num[li->link].cpus =
+ li->num[li->link].platforms = graph_counter(codec0);
+
+ li->num[li->link].codecs = graph_counter(codec1);
+
+ return 0;
+}
+
+static int graph_count(struct simple_util_priv *priv,
+ struct graph2_custom_hooks *hooks,
+ enum graph_type gtype,
+ struct device_node *lnk,
+ struct link_info *li)
+{
+ struct device *dev = simple_priv_to_dev(priv);
+ GRAPH2_CUSTOM func = NULL;
+ int ret = -EINVAL;
+
+ if (li->link >= SNDRV_MAX_LINKS) {
+ dev_err(dev, "too many links\n");
+ return ret;
+ }
+
+ switch (gtype) {
+ case GRAPH_NORMAL:
+ func = graph_count_normal;
+ break;
+ case GRAPH_DPCM:
+ func = graph_count_dpcm;
+ break;
+ case GRAPH_C2C:
+ func = graph_count_c2c;
+ break;
+ default:
+ break;
+ }
+
+ if (!func) {
+ dev_err(dev, "non supported gtype (%d)\n", gtype);
+ goto err;
+ }
+
+ ret = func(priv, lnk, li);
+ if (ret < 0)
+ goto err;
+
+ li->link++;
+err:
+ return graph_ret(priv, ret);
+}
+
+static int graph_for_each_link(struct simple_util_priv *priv,
+ struct graph2_custom_hooks *hooks,
+ struct link_info *li,
+ int (*func)(struct simple_util_priv *priv,
+ struct graph2_custom_hooks *hooks,
+ enum graph_type gtype,
+ struct device_node *lnk,
+ 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 *lnk;
+ enum graph_type gtype;
+ int rc, ret = 0;
+
+ /* loop for all listed CPU port */
+ of_for_each_phandle(&it, rc, node, "links", NULL, 0) {
+ lnk = it.node;
+
+ gtype = graph_get_type(priv, lnk);
+
+ ret = func(priv, hooks, gtype, lnk, li);
+ if (ret < 0)
+ break;
+ }
+
+ return graph_ret(priv, ret);
+}
+
+int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev,
+ struct graph2_custom_hooks *hooks)
+{
+ 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->probe = graph_util_card_probe;
+ card->owner = THIS_MODULE;
+ card->dev = dev;
+
+ if ((hooks) && (hooks)->hook_pre) {
+ ret = (hooks)->hook_pre(priv);
+ if (ret < 0)
+ goto end;
+ }
+
+ ret = graph_for_each_link(priv, hooks, li, graph_count);
+ if (!li->link)
+ ret = -EINVAL;
+ if (ret < 0)
+ 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(priv, NULL);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_routing(priv, NULL);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_aux_devs(priv, NULL);
+ if (ret < 0)
+ goto err;
+
+ memset(li, 0, sizeof(*li));
+ ret = graph_for_each_link(priv, hooks, li, graph_link);
+ if (ret < 0)
+ goto err;
+
+ /* Card name should be set after graph_for_each_link() */
+ ret = simple_util_parse_card_name(priv, NULL);
+ if (ret < 0)
+ goto err;
+
+ snd_soc_card_set_drvdata(card, priv);
+
+ if ((hooks) && (hooks)->hook_post) {
+ ret = (hooks)->hook_post(priv);
+ if (ret < 0)
+ goto err;
+ }
+
+ simple_util_debug_info(priv);
+
+ ret = devm_snd_soc_register_card(dev, card);
+err:
+ if (ret < 0) {
+ simple_util_clean_reference(priv);
+ return dev_err_probe(dev, ret, "parse error\n");
+ }
+end:
+ return graph_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(audio_graph2_parse_of);
+
+static int graph_probe(struct platform_device *pdev)
+{
+ struct simple_util_priv *priv;
+ struct device *dev = &pdev->dev;
+
+ /* Allocate the private data and the DAI link array */
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ return audio_graph2_parse_of(priv, dev, NULL);
+}
+
+static const struct of_device_id graph_of_match[] = {
+ { .compatible = "audio-graph-card2", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, graph_of_match);
+
+static struct platform_driver graph_card = {
+ .driver = {
+ .name = "asoc-audio-graph-card2",
+ .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-card2");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASoC Audio Graph Card2");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c
new file mode 100644
index 000000000..5f3423129
--- /dev/null
+++ b/sound/soc/generic/simple-card-utils.c
@@ -0,0 +1,1251 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// simple-card-utils.c
+//
+// Copyright (c) 2016 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+
+#include <dt-bindings/sound/audio-graph.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include <sound/simple_card_utils.h>
+
+#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);
+}
+
+int simple_util_get_sample_fmt(struct simple_util_data *data)
+{
+ int i;
+ int val = -EINVAL;
+
+ struct {
+ char *fmt;
+ u32 val;
+ } of_sample_fmt_table[] = {
+ { "s8", SNDRV_PCM_FORMAT_S8},
+ { "s16_le", SNDRV_PCM_FORMAT_S16_LE},
+ { "s24_le", SNDRV_PCM_FORMAT_S24_LE},
+ { "s24_3le", SNDRV_PCM_FORMAT_S24_3LE},
+ { "s32_le", SNDRV_PCM_FORMAT_S32_LE},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(of_sample_fmt_table); i++) {
+ if (!strcmp(data->convert_sample_format,
+ of_sample_fmt_table[i].fmt)) {
+ val = of_sample_fmt_table[i].val;
+ break;
+ }
+ }
+ return val;
+}
+EXPORT_SYMBOL_GPL(simple_util_get_sample_fmt);
+
+static void simple_fixup_sample_fmt(struct simple_util_data *data,
+ struct snd_pcm_hw_params *params)
+{
+ int val;
+ struct snd_mask *mask = hw_param_mask(params,
+ SNDRV_PCM_HW_PARAM_FORMAT);
+
+ val = simple_util_get_sample_fmt(data);
+ if (val >= 0) {
+ snd_mask_none(mask);
+ snd_mask_set(mask, val);
+ }
+}
+
+void simple_util_parse_convert(struct device_node *np,
+ char *prefix,
+ struct simple_util_data *data)
+{
+ char prop[128];
+
+ if (!np)
+ return;
+
+ if (!prefix)
+ prefix = "";
+
+ /* sampling rate convert */
+ snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-rate");
+ of_property_read_u32(np, prop, &data->convert_rate);
+
+ /* channels transfer */
+ snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-channels");
+ of_property_read_u32(np, prop, &data->convert_channels);
+
+ /* convert sample format */
+ snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-sample-format");
+ of_property_read_string(np, prop, &data->convert_sample_format);
+}
+EXPORT_SYMBOL_GPL(simple_util_parse_convert);
+
+/**
+ * simple_util_is_convert_required() - Query if HW param conversion was requested
+ * @data: Link data.
+ *
+ * Returns true if any HW param conversion was requested for this DAI link with
+ * any "convert-xxx" properties.
+ */
+bool simple_util_is_convert_required(const struct simple_util_data *data)
+{
+ return data->convert_rate ||
+ data->convert_channels ||
+ data->convert_sample_format;
+}
+EXPORT_SYMBOL_GPL(simple_util_is_convert_required);
+
+int simple_util_parse_daifmt(struct device *dev,
+ struct device_node *node,
+ struct device_node *codec,
+ char *prefix,
+ unsigned int *retfmt)
+{
+ struct device_node *bitclkmaster = NULL;
+ struct device_node *framemaster = NULL;
+ unsigned int daifmt;
+
+ daifmt = snd_soc_daifmt_parse_format(node, prefix);
+
+ snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster);
+ if (!bitclkmaster && !framemaster) {
+ /*
+ * No dai-link level and master setting was not found from
+ * sound node level, revert back to legacy DT parsing and
+ * take the settings from codec node.
+ */
+ dev_dbg(dev, "Revert to legacy daifmt parsing\n");
+
+ daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL);
+ } else {
+ daifmt |= snd_soc_daifmt_clock_provider_from_bitmap(
+ ((codec == bitclkmaster) << 4) | (codec == framemaster));
+ }
+
+ of_node_put(bitclkmaster);
+ of_node_put(framemaster);
+
+ *retfmt = daifmt;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simple_util_parse_daifmt);
+
+int simple_util_parse_tdm_width_map(struct simple_util_priv *priv, struct device_node *np,
+ struct simple_util_dai *dai)
+{
+ struct device *dev = simple_priv_to_dev(priv);
+ int n, i, ret;
+ u32 *p;
+
+ /*
+ * NOTE
+ *
+ * Clang doesn't allow to use "goto end" before calling __free(),
+ * because it bypasses the initialization. Use simple_ret() directly.
+ */
+
+ n = of_property_count_elems_of_size(np, "dai-tdm-slot-width-map", sizeof(u32));
+ if (n <= 0)
+ return 0;
+
+ if (n % 3) {
+ dev_err(dev, "Invalid number of cells for dai-tdm-slot-width-map\n");
+ return simple_ret(priv, -EINVAL); /* see NOTE */
+ }
+
+ ret = -ENOMEM;
+ dai->tdm_width_map = devm_kcalloc(dev, n, sizeof(*dai->tdm_width_map), GFP_KERNEL);
+ if (!dai->tdm_width_map)
+ return simple_ret(priv, ret); /* see NOTE */
+
+ u32 *array_values __free(kfree) = kzalloc_objs(*array_values, n);
+ if (!array_values)
+ goto end;
+
+ ret = of_property_read_u32_array(np, "dai-tdm-slot-width-map", array_values, n);
+ if (ret < 0) {
+ dev_err(dev, "Could not read dai-tdm-slot-width-map: %d\n", ret);
+ goto end;
+ }
+
+ p = array_values;
+ for (i = 0; i < n / 3; ++i) {
+ dai->tdm_width_map[i].sample_bits = *p++;
+ dai->tdm_width_map[i].slot_width = *p++;
+ dai->tdm_width_map[i].slot_count = *p++;
+ }
+
+ dai->n_tdm_widths = i;
+ ret = 0;
+end:
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(simple_util_parse_tdm_width_map);
+
+int simple_util_set_dailink_name(struct simple_util_priv *priv,
+ struct snd_soc_dai_link *dai_link,
+ const char *fmt, ...)
+{
+ struct device *dev = simple_priv_to_dev(priv);
+ va_list ap;
+ char *name = NULL;
+ int ret = -ENOMEM;
+
+ va_start(ap, fmt);
+ name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
+ va_end(ap);
+
+ if (name) {
+ ret = 0;
+
+ dai_link->name = name;
+ dai_link->stream_name = name;
+ }
+
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(simple_util_set_dailink_name);
+
+int simple_util_parse_property(struct simple_util_priv *priv,
+ int (*func)(struct snd_soc_card *card, const char *propname),
+ char *prefix, char *property)
+{
+ struct snd_soc_card *card = simple_priv_to_card(priv);
+ struct device_node *node = card->dev->of_node;
+ char prop[128];
+
+ if (!prefix)
+ prefix = "";
+
+ snprintf(prop, sizeof(prop), "%s%s", prefix, property);
+
+ /* no property is not error */
+ if (!of_property_present(node, prop))
+ return 0;
+
+ return func(card, prop);
+}
+EXPORT_SYMBOL_GPL(simple_util_parse_property);
+
+int simple_util_parse_card_name(struct simple_util_priv *priv,
+ char *prefix)
+{
+ struct snd_soc_card *card = simple_priv_to_card(priv);
+ int ret;
+
+ if (!prefix)
+ prefix = "";
+
+ /* Parse the card name from DT */
+ ret = snd_soc_of_parse_card_name(card, "label");
+ if (ret < 0 || !card->name) {
+ char prop[128];
+
+ snprintf(prop, sizeof(prop), "%sname", prefix);
+ ret = snd_soc_of_parse_card_name(card, prop);
+ if (ret < 0)
+ goto end;
+ }
+
+ if (!card->name && card->dai_link)
+ card->name = card->dai_link->name;
+end:
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(simple_util_parse_card_name);
+
+static int simple_clk_enable(struct simple_util_dai *dai)
+{
+ if (dai)
+ return clk_prepare_enable(dai->clk);
+
+ return 0;
+}
+
+static void simple_clk_disable(struct simple_util_dai *dai)
+{
+ if (dai)
+ clk_disable_unprepare(dai->clk);
+}
+
+int simple_util_parse_clk(struct device *dev,
+ struct device_node *node,
+ struct simple_util_dai *simple_dai,
+ struct snd_soc_dai_link_component *dlc)
+{
+ struct clk *clk;
+ u32 val;
+
+ /*
+ * Parse dai->sysclk come from "clocks = <&xxx>"
+ * (if system has common clock)
+ * or "system-clock-frequency = <xxx>"
+ * or device's module clock.
+ */
+ clk = devm_get_clk_from_child(dev, node, NULL);
+ simple_dai->clk_fixed = of_property_read_bool(
+ node, "system-clock-fixed");
+ if (!IS_ERR(clk)) {
+ simple_dai->sysclk = clk_get_rate(clk);
+
+ simple_dai->clk = clk;
+ } else if (!of_property_read_u32(node, "system-clock-frequency", &val)) {
+ simple_dai->sysclk = val;
+ simple_dai->clk_fixed = true;
+ } else {
+ clk = devm_get_clk_from_child(dev, dlc->of_node, NULL);
+ if (!IS_ERR(clk))
+ simple_dai->sysclk = clk_get_rate(clk);
+ }
+
+ if (of_property_read_bool(node, "system-clock-direction-out"))
+ simple_dai->clk_direction = SND_SOC_CLOCK_OUT;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simple_util_parse_clk);
+
+static int simple_check_fixed_sysclk(struct device *dev,
+ struct simple_util_dai *dai,
+ unsigned int *fixed_sysclk)
+{
+ if (dai->clk_fixed) {
+ if (*fixed_sysclk && *fixed_sysclk != dai->sysclk) {
+ dev_err(dev, "inconsistent fixed sysclk rates (%u vs %u)\n",
+ *fixed_sysclk, dai->sysclk);
+ return -EINVAL;
+ }
+ *fixed_sysclk = dai->sysclk;
+ }
+
+ return 0;
+}
+
+int simple_util_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 simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
+ struct simple_util_dai *dai;
+ unsigned int fixed_sysclk = 0;
+ int i1, i2, i;
+ int ret;
+
+ for_each_prop_dai_cpu(props, i1, dai) {
+ ret = simple_clk_enable(dai);
+ if (ret)
+ goto cpu_err;
+ ret = simple_check_fixed_sysclk(rtd->dev, dai, &fixed_sysclk);
+ if (ret)
+ goto cpu_err;
+ }
+
+ for_each_prop_dai_codec(props, i2, dai) {
+ ret = simple_clk_enable(dai);
+ if (ret)
+ goto codec_err;
+ ret = simple_check_fixed_sysclk(rtd->dev, dai, &fixed_sysclk);
+ if (ret)
+ goto codec_err;
+ }
+
+ if (fixed_sysclk && props->mclk_fs) {
+ unsigned int fixed_rate = fixed_sysclk / props->mclk_fs;
+
+ if (fixed_sysclk % props->mclk_fs) {
+ dev_err(rtd->dev, "fixed sysclk %u not divisible by mclk_fs %u\n",
+ fixed_sysclk, props->mclk_fs);
+ ret = -EINVAL;
+ goto codec_err;
+ }
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_RATE,
+ fixed_rate, fixed_rate);
+ if (ret < 0)
+ goto codec_err;
+ }
+
+ return 0;
+
+codec_err:
+ for_each_prop_dai_codec(props, i, dai) {
+ if (i >= i2)
+ break;
+ simple_clk_disable(dai);
+ }
+cpu_err:
+ for_each_prop_dai_cpu(props, i, dai) {
+ if (i >= i1)
+ break;
+ simple_clk_disable(dai);
+ }
+
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(simple_util_startup);
+
+void simple_util_shutdown(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 simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
+ struct simple_util_dai *dai;
+ int i;
+
+ for_each_prop_dai_cpu(props, i, dai) {
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, i);
+
+ if (props->mclk_fs && !dai->clk_fixed && !snd_soc_dai_active(cpu_dai))
+ snd_soc_dai_set_sysclk(cpu_dai, 0, 0, dai->clk_direction);
+
+ simple_clk_disable(dai);
+ }
+ for_each_prop_dai_codec(props, i, dai) {
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, i);
+
+ if (props->mclk_fs && !dai->clk_fixed && !snd_soc_dai_active(codec_dai))
+ snd_soc_dai_set_sysclk(codec_dai, 0, 0, dai->clk_direction);
+
+ simple_clk_disable(dai);
+ }
+}
+EXPORT_SYMBOL_GPL(simple_util_shutdown);
+
+static int simple_set_clk_rate(struct simple_util_priv *priv,
+ struct simple_util_dai *simple_dai,
+ unsigned long rate)
+{
+ struct device *dev = simple_priv_to_dev(priv);
+ int ret = -EINVAL;
+
+ if (!simple_dai)
+ return 0;
+
+ if (simple_dai->clk_fixed && rate != simple_dai->sysclk) {
+ dev_err(dev, "dai %s invalid clock rate %lu\n", simple_dai->name, rate);
+ goto end;
+ }
+
+ if (!simple_dai->clk)
+ return 0;
+
+ if (clk_get_rate(simple_dai->clk) == rate)
+ return 0;
+
+ ret = clk_set_rate(simple_dai->clk, rate);
+end:
+ return simple_ret(priv, ret);
+}
+
+static int simple_set_tdm(struct simple_util_priv *priv,
+ struct snd_soc_dai *dai,
+ struct simple_util_dai *simple_dai,
+ struct snd_pcm_hw_params *params)
+{
+ int sample_bits = params_width(params);
+ int slot_width, slot_count;
+ int i, ret;
+
+ if (!simple_dai || !simple_dai->tdm_width_map)
+ return 0;
+
+ slot_width = simple_dai->slot_width;
+ slot_count = simple_dai->slots;
+
+ if (slot_width == 0)
+ slot_width = sample_bits;
+
+ for (i = 0; i < simple_dai->n_tdm_widths; ++i) {
+ if (simple_dai->tdm_width_map[i].sample_bits == sample_bits) {
+ slot_width = simple_dai->tdm_width_map[i].slot_width;
+ slot_count = simple_dai->tdm_width_map[i].slot_count;
+ break;
+ }
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(dai,
+ simple_dai->tx_slot_mask,
+ simple_dai->rx_slot_mask,
+ slot_count,
+ slot_width);
+
+ return simple_ret(priv, ret);
+}
+
+int simple_util_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct simple_util_dai *pdai;
+ struct snd_soc_dai *sdai;
+ struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
+ enum simple_util_sysclk_order order = props->sysclk_order;
+ unsigned int mclk, mclk_fs = 0;
+ int i, ret;
+
+ if (props->mclk_fs)
+ mclk_fs = props->mclk_fs;
+
+ if (mclk_fs) {
+ struct snd_soc_component *component;
+ mclk = params_rate(params) * mclk_fs;
+
+ for_each_prop_dai_codec(props, i, pdai) {
+ ret = simple_set_clk_rate(priv, pdai, mclk);
+ if (ret < 0)
+ goto end;
+ }
+
+ for_each_prop_dai_cpu(props, i, pdai) {
+ ret = simple_set_clk_rate(priv, pdai, mclk);
+ if (ret < 0)
+ goto end;
+ }
+
+ /* Ensure sysclk is set on all components in case any
+ * (such as platform components) are missed by calls to
+ * snd_soc_dai_set_sysclk.
+ */
+ for_each_rtd_components(rtd, i, component) {
+ ret = snd_soc_component_set_sysclk(component, 0, 0,
+ mclk, SND_SOC_CLOCK_IN);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+
+ if (order == SIMPLE_SYSCLK_ORDER_CPU_FIRST) {
+ /* CPU first */
+ for_each_rtd_cpu_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_cpu(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_codec(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+ } else {
+ /* default: codec first */
+ for_each_rtd_codec_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_codec(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+
+ for_each_rtd_cpu_dais(rtd, i, sdai) {
+ pdai = simple_props_to_dai_cpu(props, i);
+ ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);
+ if (ret && ret != -ENOTSUPP)
+ goto end;
+ }
+ }
+ }
+
+ for_each_prop_dai_codec(props, i, pdai) {
+ sdai = snd_soc_rtd_to_codec(rtd, i);
+ ret = simple_set_tdm(priv, sdai, pdai, params);
+ if (ret < 0)
+ goto end;
+ }
+
+ for_each_prop_dai_cpu(props, i, pdai) {
+ sdai = snd_soc_rtd_to_cpu(rtd, i);
+ ret = simple_set_tdm(priv, sdai, pdai, params);
+ if (ret < 0)
+ goto end;
+ }
+ ret = 0;
+end:
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(simple_util_hw_params);
+
+int simple_util_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct simple_dai_props *dai_props = runtime_simple_priv_to_props(priv, rtd);
+ struct simple_util_data *data = &dai_props->adata;
+ struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ if (data->convert_rate)
+ rate->min =
+ rate->max = data->convert_rate;
+
+ if (data->convert_channels)
+ channels->min =
+ channels->max = data->convert_channels;
+
+ if (data->convert_sample_format)
+ simple_fixup_sample_fmt(data, params);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simple_util_be_hw_params_fixup);
+
+static int simple_init_dai(struct simple_util_priv *priv,
+ struct snd_soc_dai *dai, struct simple_util_dai *simple_dai)
+{
+ int ret;
+
+ if (!simple_dai)
+ return 0;
+
+ if (simple_dai->sysclk) {
+ ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk,
+ simple_dai->clk_direction);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dai->dev, "simple-card: set_sysclk error\n");
+ goto end;
+ }
+ }
+
+ if (simple_dai->slots) {
+ ret = snd_soc_dai_set_tdm_slot(dai,
+ simple_dai->tx_slot_mask,
+ simple_dai->rx_slot_mask,
+ simple_dai->slots,
+ simple_dai->slot_width);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dai->dev, "simple-card: set_tdm_slot error\n");
+ goto end;
+ }
+ }
+ ret = 0;
+end:
+ return simple_ret(priv, ret);
+}
+
+static inline int simple_component_is_codec(struct snd_soc_component *component)
+{
+ return component->driver->endianness;
+}
+
+static int simple_init_for_codec2codec(struct simple_util_priv *priv,
+ struct snd_soc_pcm_runtime *rtd,
+ struct simple_dai_props *dai_props)
+{
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+ struct snd_soc_component *component;
+ struct snd_soc_pcm_stream *c2c_params;
+ struct snd_pcm_hardware hw;
+ int i, ret, stream;
+
+ /* Do nothing if it already has Codec2Codec settings */
+ if (dai_link->c2c_params)
+ return 0;
+
+ /* Do nothing if it was DPCM :: BE */
+ if (dai_link->no_pcm)
+ return 0;
+
+ /* Only Codecs */
+ for_each_rtd_components(rtd, i, component) {
+ if (!simple_component_is_codec(component))
+ return 0;
+ }
+
+ /* Assumes the capabilities are the same for all supported streams */
+ for_each_pcm_streams(stream) {
+ ret = snd_soc_runtime_calc_hw(rtd, &hw, stream);
+ if (ret == 0)
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "simple-card: no valid dai_link params\n");
+ goto end;
+ }
+
+ ret = -ENOMEM;
+ c2c_params = devm_kzalloc(rtd->dev, sizeof(*c2c_params), GFP_KERNEL);
+ if (!c2c_params)
+ goto end;
+
+ c2c_params->formats = hw.formats;
+ c2c_params->rates = hw.rates;
+ c2c_params->rate_min = hw.rate_min;
+ c2c_params->rate_max = hw.rate_max;
+ c2c_params->channels_min = hw.channels_min;
+ c2c_params->channels_max = hw.channels_max;
+
+ dai_link->c2c_params = c2c_params;
+ dai_link->num_c2c_params = 1;
+
+ ret = 0;
+end:
+ return simple_ret(priv, ret);
+}
+
+int simple_util_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
+ struct simple_util_dai *dai;
+ int i, ret;
+
+ for_each_prop_dai_codec(props, i, dai) {
+ ret = simple_init_dai(priv, snd_soc_rtd_to_codec(rtd, i), dai);
+ if (ret < 0)
+ goto end;
+ }
+ for_each_prop_dai_cpu(props, i, dai) {
+ ret = simple_init_dai(priv, snd_soc_rtd_to_cpu(rtd, i), dai);
+ if (ret < 0)
+ goto end;
+ }
+
+ ret = simple_init_for_codec2codec(priv, rtd, props);
+end:
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(simple_util_dai_init);
+
+void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platforms,
+ struct snd_soc_dai_link_component *cpus)
+{
+ /*
+ * Assumes Platform == CPU
+ *
+ * Some CPU might be using soc-generic-dmaengine-pcm. This means CPU and Platform
+ * are different Component, but are sharing same component->dev.
+ *
+ * Let's assume Platform is same as CPU if it doesn't identify Platform on DT.
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
+ if (!platforms->of_node)
+ snd_soc_dlc_use_cpu_as_platform(platforms, cpus);
+}
+EXPORT_SYMBOL_GPL(simple_util_canonicalize_platform);
+
+void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus,
+ int is_single_links)
+{
+ /*
+ * In snd_soc_add_pcm_runtime() will check cpu name after
+ * of_node matching if dai_link has cpu_dai_name.
+ * but, it will never match if name was created by
+ * fmt_single_name() remove cpu_dai_name if cpu_args
+ * was 0. See:
+ * fmt_single_name()
+ * fmt_multiple_name()
+ */
+ if (is_single_links)
+ cpus->dai_name = NULL;
+}
+EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu);
+
+void simple_util_clean_reference(struct simple_util_priv *priv)
+{
+ struct snd_soc_dai_link *dai_link;
+ struct snd_soc_dai_link_component *cpu;
+ struct snd_soc_dai_link_component *codec;
+ struct snd_soc_card *card = simple_priv_to_card(priv);
+ int i, j;
+
+ for_each_card_prelinks(card, i, dai_link) {
+ for_each_link_cpus(dai_link, j, cpu)
+ of_node_put(cpu->of_node);
+ for_each_link_codecs(dai_link, j, codec)
+ of_node_put(codec->of_node);
+ }
+}
+EXPORT_SYMBOL_GPL(simple_util_clean_reference);
+
+int simple_util_init_jack(struct snd_soc_card *card,
+ struct simple_util_jack *sjack,
+ int is_hp, char *prefix,
+ char *pin)
+{
+ struct device *dev = card->dev;
+ struct gpio_desc *desc;
+ char prop[128];
+ char *pin_name;
+ char *gpio_name;
+ int mask;
+ int error;
+
+ if (!prefix)
+ prefix = "";
+
+ if (is_hp) {
+ snprintf(prop, sizeof(prop), "%shp-det", prefix);
+ pin_name = pin ? pin : "Headphones";
+ gpio_name = "Headphone detection";
+ mask = SND_JACK_HEADPHONE;
+ } else {
+ snprintf(prop, sizeof(prop), "%smic-det", prefix);
+ pin_name = pin ? pin : "Mic Jack";
+ gpio_name = "Mic detection";
+ mask = SND_JACK_MICROPHONE;
+ }
+
+ desc = gpiod_get_optional(dev, prop, GPIOD_IN);
+ error = PTR_ERR_OR_ZERO(desc);
+ if (error)
+ return error;
+
+ if (desc) {
+ error = gpiod_set_consumer_name(desc, gpio_name);
+ if (error)
+ return error;
+
+ sjack->pin.pin = pin_name;
+ sjack->pin.mask = mask;
+
+ sjack->gpio.name = gpio_name;
+ sjack->gpio.report = mask;
+ sjack->gpio.desc = desc;
+ sjack->gpio.debounce_time = 150;
+
+ snd_soc_card_jack_new_pins(card, pin_name, mask, &sjack->jack,
+ &sjack->pin, 1);
+
+ snd_soc_jack_add_gpios(&sjack->jack, 1, &sjack->gpio);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simple_util_init_jack);
+
+int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix)
+{
+ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+ int found_jack_index = 0;
+ int type = 0;
+ int num = 0;
+ int ret;
+
+ if (priv->aux_jacks)
+ return 0;
+
+ for_each_card_auxs(card, component) {
+ type = snd_soc_component_get_jack_type(component);
+ if (type > 0)
+ num++;
+ }
+ if (num < 1)
+ return 0;
+
+ priv->aux_jacks = devm_kcalloc(card->dev, num,
+ sizeof(struct snd_soc_jack), GFP_KERNEL);
+ if (!priv->aux_jacks)
+ return simple_ret(priv, -ENOMEM);
+
+ for_each_card_auxs(card, component) {
+ char id[128];
+ struct snd_soc_jack *jack;
+
+ if (found_jack_index >= num)
+ break;
+
+ type = snd_soc_component_get_jack_type(component);
+ if (type <= 0)
+ continue;
+
+ /* create jack */
+ jack = &(priv->aux_jacks[found_jack_index++]);
+ snprintf(id, sizeof(id), "%s-jack", component->name);
+ ret = snd_soc_card_jack_new(card, id, type, jack);
+ if (ret)
+ continue;
+
+ (void)snd_soc_component_set_jack(component, jack, NULL);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simple_util_init_aux_jacks);
+
+static struct simple_util_dai dummy_util_dais = {
+ .name = "dummy_util_dais",
+};
+
+int simple_util_init_priv(struct simple_util_priv *priv,
+ struct link_info *li)
+{
+ struct snd_soc_card *card = simple_priv_to_card(priv);
+ struct device *dev = simple_priv_to_dev(priv);
+ struct snd_soc_dai_link *dai_link;
+ struct simple_dai_props *dai_props;
+ struct simple_util_dai *dais;
+ struct snd_soc_dai_link_component *dlcs;
+ struct snd_soc_codec_conf *cconf = NULL;
+ int i, dai_num = 0, dlc_num = 0, cnf_num = 0;
+
+ dai_props = devm_kcalloc(dev, li->link, sizeof(*dai_props), GFP_KERNEL);
+ dai_link = devm_kcalloc(dev, li->link, sizeof(*dai_link), GFP_KERNEL);
+ if (!dai_props || !dai_link)
+ return -ENOMEM;
+
+ /*
+ * dais (= CPU+Codec)
+ * dlcs (= CPU+Codec+Platform)
+ */
+ for (i = 0; i < li->link; i++) {
+ int cc = li->num[i].cpus + li->num[i].codecs;
+
+ dai_num += cc;
+ dlc_num += cc + li->num[i].platforms;
+
+ if (!li->num[i].cpus)
+ cnf_num += li->num[i].codecs;
+ }
+
+ dais = devm_kcalloc(dev, dai_num, sizeof(*dais), GFP_KERNEL);
+ dlcs = devm_kcalloc(dev, dlc_num, sizeof(*dlcs), GFP_KERNEL);
+ if (!dais || !dlcs)
+ return -ENOMEM;
+
+ if (cnf_num) {
+ cconf = devm_kcalloc(dev, cnf_num, sizeof(*cconf), GFP_KERNEL);
+ if (!cconf)
+ return -ENOMEM;
+ }
+
+ dev_dbg(dev, "link %d, dais %d, ccnf %d\n",
+ li->link, dai_num, cnf_num);
+
+ priv->dai_props = dai_props;
+ priv->dai_link = dai_link;
+ priv->dais = dais;
+ priv->dlcs = dlcs;
+ priv->codec_conf = cconf;
+
+ card->dai_link = priv->dai_link;
+ card->num_links = li->link;
+ card->codec_conf = cconf;
+ card->num_configs = cnf_num;
+
+ for (i = 0; i < li->link; i++) {
+ if (li->num[i].cpus) {
+ /* Normal CPU */
+ dai_link[i].cpus = dlcs;
+ dai_props[i].num.cpus =
+ dai_link[i].num_cpus = li->num[i].cpus;
+ dai_props[i].cpu_dai = dais;
+
+ dlcs += li->num[i].cpus;
+ dais += li->num[i].cpus;
+ } else {
+ /* DPCM Be's CPU = dummy */
+ dai_link[i].cpus = &snd_soc_dummy_dlc;
+ dai_props[i].num.cpus =
+ dai_link[i].num_cpus = 1;
+ dai_props[i].cpu_dai = &dummy_util_dais;
+ }
+
+ if (li->num[i].codecs) {
+ /* Normal Codec */
+ dai_link[i].codecs = dlcs;
+ dai_props[i].num.codecs =
+ dai_link[i].num_codecs = li->num[i].codecs;
+ dai_props[i].codec_dai = dais;
+
+ dlcs += li->num[i].codecs;
+ dais += li->num[i].codecs;
+
+ if (!li->num[i].cpus) {
+ /* DPCM Be's Codec */
+ dai_props[i].codec_conf = cconf;
+ cconf += li->num[i].codecs;
+ }
+ } else {
+ /* DPCM Fe's Codec = dummy */
+ dai_link[i].codecs = &snd_soc_dummy_dlc;
+ dai_props[i].num.codecs =
+ dai_link[i].num_codecs = 1;
+ dai_props[i].codec_dai = &dummy_util_dais;
+ }
+
+ if (li->num[i].platforms) {
+ /* Have Platform */
+ dai_link[i].platforms = dlcs;
+ dai_props[i].num.platforms =
+ dai_link[i].num_platforms = li->num[i].platforms;
+
+ dlcs += li->num[i].platforms;
+ } else {
+ /* Doesn't have Platform */
+ dai_link[i].platforms = NULL;
+ dai_props[i].num.platforms =
+ dai_link[i].num_platforms = 0;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simple_util_init_priv);
+
+void simple_util_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card);
+
+ simple_util_clean_reference(priv);
+}
+EXPORT_SYMBOL_GPL(simple_util_remove);
+
+int graph_util_card_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, NULL);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_init_mic(card, &priv->mic_jack, NULL);
+end:
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(graph_util_card_probe);
+
+int graph_util_is_ports0(struct device_node *np)
+{
+ struct device_node *parent __free(device_node) = of_get_parent(np);
+ struct device_node *port;
+
+ /* np is "endpoint" or "port" */
+ if (of_node_name_eq(np, "endpoint"))
+ port = parent;
+ else
+ port = np;
+
+ struct device_node *ports __free(device_node) = of_get_parent(port);
+ const char *at = strchr(kbasename(ports->full_name), '@');
+
+ /*
+ * Since child iteration order may differ
+ * between a base DT and DT overlays,
+ * string match "ports" or "ports@0" in the node name instead.
+ */
+ return !at || !strcmp(at, "@0");
+}
+EXPORT_SYMBOL_GPL(graph_util_is_ports0);
+
+static int graph_get_dai_id(struct device_node *ep)
+{
+ struct device_node *node __free(device_node) = of_graph_get_port_parent(ep);
+ struct device_node *port __free(device_node) = of_get_parent(ep);
+ struct of_endpoint info;
+ int i, id;
+ int ret;
+
+ /* use driver specified DAI ID if exist */
+ ret = snd_soc_get_dai_id(ep);
+ if (ret != -ENOTSUPP)
+ return ret;
+
+ /* use endpoint/port reg if exist */
+ ret = of_graph_parse_endpoint(ep, &info);
+ if (ret == 0) {
+ /*
+ * Because it will count port/endpoint if it doesn't have "reg".
+ * But, we can't judge whether it has "no reg", or "reg = <0>"
+ * only of_graph_parse_endpoint().
+ * We need to check "reg" property
+ */
+
+ /* check port first */
+ ret = of_property_present(port, "reg");
+ if (ret)
+ return info.port;
+
+ /* check endpoint 2nd as backup */
+ if (of_property_present(ep, "reg"))
+ return info.id;
+ }
+
+ /*
+ * Non HDMI sound case, counting port/endpoint on its DT
+ * is enough. Let's count it.
+ */
+ i = 0;
+ id = -1;
+ for_each_of_graph_port(node, p) {
+ if (port == p) {
+ id = i;
+ break;
+ }
+ i++;
+ }
+
+ if (id < 0)
+ return -ENODEV;
+
+ return id;
+}
+
+int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep,
+ struct snd_soc_dai_link_component *dlc, int *is_single_link)
+{
+ struct device *dev = simple_priv_to_dev(priv);
+ struct device_node *node;
+ 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 (!ep)
+ return 0;
+
+ node = of_graph_get_port_parent(ep);
+
+ /*
+ * Try to find from DAI node
+ */
+ args.np = ep;
+ dai = snd_soc_get_dai_via_args(&args);
+ if (dai) {
+ const char *dai_name = snd_soc_dai_name_get(dai);
+ const struct of_phandle_args *dai_args = snd_soc_copy_dai_args(dev, &args);
+
+ ret = -ENOMEM;
+ if (!dai_args)
+ goto err;
+
+ dlc->of_node = node;
+ dlc->dai_name = dai_name;
+ dlc->dai_args = dai_args;
+ } else {
+ /* Get dai->name */
+ args.np = node;
+ args.args[0] = graph_get_dai_id(ep);
+ args.args_count = (of_graph_get_endpoint_count(node) > 1);
+
+ ret = snd_soc_get_dlc(&args, &resolved_dlc);
+ if (ret < 0)
+ goto err;
+
+ /* 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)
+ goto err;
+ }
+
+ 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 = of_graph_get_endpoint_count(node) == 1;
+ ret = 0;
+err:
+ if (ret < 0)
+ of_node_put(node);
+
+ return simple_ret(priv, ret);
+}
+EXPORT_SYMBOL_GPL(graph_util_parse_dai);
+
+void graph_util_parse_link_direction(struct device_node *np,
+ bool *playback_only, bool *capture_only)
+{
+ bool is_playback_only = of_property_read_bool(np, "playback-only");
+ bool is_capture_only = of_property_read_bool(np, "capture-only");
+
+ if (playback_only && is_playback_only)
+ *playback_only = is_playback_only;
+ if (capture_only && is_capture_only)
+ *capture_only = is_capture_only;
+}
+EXPORT_SYMBOL_GPL(graph_util_parse_link_direction);
+
+static enum snd_soc_trigger_order
+__graph_util_parse_trigger_order(struct simple_util_priv *priv,
+ struct device_node *np,
+ const char *prop)
+{
+ u32 val[SND_SOC_TRIGGER_SIZE];
+ int ret;
+
+ ret = of_property_read_u32_array(np, prop, val, SND_SOC_TRIGGER_SIZE);
+ if (ret == 0) {
+ struct device *dev = simple_priv_to_dev(priv);
+ u32 order = (val[0] << 8) +
+ (val[1] << 4) +
+ (val[2]);
+
+ switch (order) {
+ case (SND_SOC_TRIGGER_LINK << 8) +
+ (SND_SOC_TRIGGER_COMPONENT << 4) +
+ (SND_SOC_TRIGGER_DAI):
+ return SND_SOC_TRIGGER_ORDER_DEFAULT;
+
+ case (SND_SOC_TRIGGER_LINK << 8) +
+ (SND_SOC_TRIGGER_DAI << 4) +
+ (SND_SOC_TRIGGER_COMPONENT):
+ return SND_SOC_TRIGGER_ORDER_LDC;
+
+ default:
+ dev_err(dev, "unsupported trigger order [0x%x]\n", order);
+ }
+ }
+
+ /* SND_SOC_TRIGGER_ORDER_MAX means error */
+ return SND_SOC_TRIGGER_ORDER_MAX;
+}
+
+void graph_util_parse_trigger_order(struct simple_util_priv *priv,
+ struct device_node *np,
+ enum snd_soc_trigger_order *trigger_start,
+ enum snd_soc_trigger_order *trigger_stop)
+{
+ static enum snd_soc_trigger_order order;
+
+ /*
+ * We can use it like below
+ *
+ * #include <dt-bindings/sound/audio-graph.h>
+ *
+ * link-trigger-order = <SND_SOC_TRIGGER_LINK
+ * SND_SOC_TRIGGER_COMPONENT
+ * SND_SOC_TRIGGER_DAI>;
+ */
+
+ order = __graph_util_parse_trigger_order(priv, np, "link-trigger-order");
+ if (order < SND_SOC_TRIGGER_ORDER_MAX) {
+ *trigger_start = order;
+ *trigger_stop = order;
+ }
+
+ order = __graph_util_parse_trigger_order(priv, np, "link-trigger-order-start");
+ if (order < SND_SOC_TRIGGER_ORDER_MAX)
+ *trigger_start = order;
+
+ order = __graph_util_parse_trigger_order(priv, np, "link-trigger-order-stop");
+ if (order < SND_SOC_TRIGGER_ORDER_MAX)
+ *trigger_stop = order;
+
+ return;
+}
+EXPORT_SYMBOL_GPL(graph_util_parse_trigger_order);
+
+/* Module information */
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
+MODULE_DESCRIPTION("ALSA SoC Simple Card Utils");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c
new file mode 100644
index 000000000..e7ad7af04
--- /dev/null
+++ b/sound/soc/generic/simple-card.c
@@ -0,0 +1,785 @@
+// 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_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(card, PREFIX);
+end:
+ return simple_ret(priv, ret);
+}
+
+static int simple_parse_of(struct simple_util_priv *priv)
+{
+ struct snd_soc_card *card = simple_priv_to_card(priv);
+ struct device *dev = card->dev;
+ int ret = -EINVAL;
+
+ if (!dev)
+ return simple_ret(priv, ret);
+
+ 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_util_parse_widgets(priv, PREFIX);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_routing(priv, PREFIX);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_pin_switches(priv, PREFIX);
+ if (ret < 0)
+ goto end;
+
+ ret = simple_util_parse_aux_devs(priv, PREFIX);
+ if (ret < 0)
+ goto err;
+
+ /* 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 err;
+
+ /* Card name should be set after simple_for_each_link() */
+ ret = simple_util_parse_card_name(priv, PREFIX);
+ if (ret < 0)
+ goto err;
+
+ ret = simple_populate_aux(priv);
+ 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);
+err:
+ if (ret < 0) {
+ simple_util_clean_reference(priv);
+ return dev_err_probe(dev, ret, "parse error\n");
+ }
+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;
+
+ /* 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";
+
+ return simple_parse_of(priv);
+}
+
+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>");
diff --git a/sound/soc/generic/test-component.c b/sound/soc/generic/test-component.c
new file mode 100644
index 000000000..6f9f498c4
--- /dev/null
+++ b/sound/soc/generic/test-component.c
@@ -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");