162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// ASoC audio graph sound card support
462306a36Sopenharmony_ci//
562306a36Sopenharmony_ci// Copyright (C) 2016 Renesas Solutions Corp.
662306a36Sopenharmony_ci// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
762306a36Sopenharmony_ci//
862306a36Sopenharmony_ci// based on ${LINUX}/sound/soc/generic/simple-card.c
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/clk.h>
1162306a36Sopenharmony_ci#include <linux/device.h>
1262306a36Sopenharmony_ci#include <linux/gpio.h>
1362306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/of.h>
1662306a36Sopenharmony_ci#include <linux/of_device.h>
1762306a36Sopenharmony_ci#include <linux/of_gpio.h>
1862306a36Sopenharmony_ci#include <linux/of_graph.h>
1962306a36Sopenharmony_ci#include <linux/platform_device.h>
2062306a36Sopenharmony_ci#include <linux/string.h>
2162306a36Sopenharmony_ci#include <sound/graph_card.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define DPCM_SELECTABLE 1
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic int graph_outdrv_event(struct snd_soc_dapm_widget *w,
2662306a36Sopenharmony_ci			      struct snd_kcontrol *kcontrol,
2762306a36Sopenharmony_ci			      int event)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	struct snd_soc_dapm_context *dapm = w->dapm;
3062306a36Sopenharmony_ci	struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(dapm->card);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	switch (event) {
3362306a36Sopenharmony_ci	case SND_SOC_DAPM_POST_PMU:
3462306a36Sopenharmony_ci		gpiod_set_value_cansleep(priv->pa_gpio, 1);
3562306a36Sopenharmony_ci		break;
3662306a36Sopenharmony_ci	case SND_SOC_DAPM_PRE_PMD:
3762306a36Sopenharmony_ci		gpiod_set_value_cansleep(priv->pa_gpio, 0);
3862306a36Sopenharmony_ci		break;
3962306a36Sopenharmony_ci	default:
4062306a36Sopenharmony_ci		return -EINVAL;
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return 0;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic const struct snd_soc_dapm_widget graph_dapm_widgets[] = {
4762306a36Sopenharmony_ci	SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM,
4862306a36Sopenharmony_ci			       0, 0, NULL, 0, graph_outdrv_event,
4962306a36Sopenharmony_ci			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic const struct snd_soc_ops graph_ops = {
5362306a36Sopenharmony_ci	.startup	= asoc_simple_startup,
5462306a36Sopenharmony_ci	.shutdown	= asoc_simple_shutdown,
5562306a36Sopenharmony_ci	.hw_params	= asoc_simple_hw_params,
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (dai && (dai->component->driver->pcm_construct ||
6362306a36Sopenharmony_ci		    (dai->driver->ops && dai->driver->ops->pcm_new)))
6462306a36Sopenharmony_ci		return true;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return false;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic void graph_parse_convert(struct device *dev,
7062306a36Sopenharmony_ci				struct device_node *ep,
7162306a36Sopenharmony_ci				struct asoc_simple_data *adata)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct device_node *top = dev->of_node;
7462306a36Sopenharmony_ci	struct device_node *port = of_get_parent(ep);
7562306a36Sopenharmony_ci	struct device_node *ports = of_get_parent(port);
7662306a36Sopenharmony_ci	struct device_node *node = of_graph_get_port_parent(ep);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	asoc_simple_parse_convert(top,   NULL,   adata);
7962306a36Sopenharmony_ci	if (of_node_name_eq(ports, "ports"))
8062306a36Sopenharmony_ci		asoc_simple_parse_convert(ports, NULL, adata);
8162306a36Sopenharmony_ci	asoc_simple_parse_convert(port,  NULL,   adata);
8262306a36Sopenharmony_ci	asoc_simple_parse_convert(ep,    NULL,   adata);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	of_node_put(port);
8562306a36Sopenharmony_ci	of_node_put(ports);
8662306a36Sopenharmony_ci	of_node_put(node);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic void graph_parse_mclk_fs(struct device_node *top,
9062306a36Sopenharmony_ci				struct device_node *ep,
9162306a36Sopenharmony_ci				struct simple_dai_props *props)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct device_node *port	= of_get_parent(ep);
9462306a36Sopenharmony_ci	struct device_node *ports	= of_get_parent(port);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	of_property_read_u32(top,	"mclk-fs", &props->mclk_fs);
9762306a36Sopenharmony_ci	if (of_node_name_eq(ports, "ports"))
9862306a36Sopenharmony_ci		of_property_read_u32(ports, "mclk-fs", &props->mclk_fs);
9962306a36Sopenharmony_ci	of_property_read_u32(port,	"mclk-fs", &props->mclk_fs);
10062306a36Sopenharmony_ci	of_property_read_u32(ep,	"mclk-fs", &props->mclk_fs);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	of_node_put(port);
10362306a36Sopenharmony_ci	of_node_put(ports);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int graph_parse_node(struct asoc_simple_priv *priv,
10762306a36Sopenharmony_ci			    struct device_node *ep,
10862306a36Sopenharmony_ci			    struct link_info *li,
10962306a36Sopenharmony_ci			    int *cpu)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
11262306a36Sopenharmony_ci	struct device_node *top = dev->of_node;
11362306a36Sopenharmony_ci	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
11462306a36Sopenharmony_ci	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
11562306a36Sopenharmony_ci	struct snd_soc_dai_link_component *dlc;
11662306a36Sopenharmony_ci	struct asoc_simple_dai *dai;
11762306a36Sopenharmony_ci	int ret;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (cpu) {
12062306a36Sopenharmony_ci		dlc = asoc_link_to_cpu(dai_link, 0);
12162306a36Sopenharmony_ci		dai = simple_props_to_dai_cpu(dai_props, 0);
12262306a36Sopenharmony_ci	} else {
12362306a36Sopenharmony_ci		dlc = asoc_link_to_codec(dai_link, 0);
12462306a36Sopenharmony_ci		dai = simple_props_to_dai_codec(dai_props, 0);
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	graph_parse_mclk_fs(top, ep, dai_props);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	ret = asoc_graph_parse_dai(dev, ep, dlc, cpu);
13062306a36Sopenharmony_ci	if (ret < 0)
13162306a36Sopenharmony_ci		return ret;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	ret = asoc_simple_parse_tdm(ep, dai);
13462306a36Sopenharmony_ci	if (ret < 0)
13562306a36Sopenharmony_ci		return ret;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	ret = asoc_simple_parse_clk(dev, ep, dai, dlc);
13862306a36Sopenharmony_ci	if (ret < 0)
13962306a36Sopenharmony_ci		return ret;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int graph_link_init(struct asoc_simple_priv *priv,
14562306a36Sopenharmony_ci			   struct device_node *cpu_ep,
14662306a36Sopenharmony_ci			   struct device_node *codec_ep,
14762306a36Sopenharmony_ci			   struct link_info *li,
14862306a36Sopenharmony_ci			   char *name)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
15162306a36Sopenharmony_ci	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
15262306a36Sopenharmony_ci	int ret;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep,
15562306a36Sopenharmony_ci				       NULL, &dai_link->dai_fmt);
15662306a36Sopenharmony_ci	if (ret < 0)
15762306a36Sopenharmony_ci		return ret;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	dai_link->init		= asoc_simple_dai_init;
16062306a36Sopenharmony_ci	dai_link->ops		= &graph_ops;
16162306a36Sopenharmony_ci	if (priv->ops)
16262306a36Sopenharmony_ci		dai_link->ops	= priv->ops;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return asoc_simple_set_dailink_name(dev, dai_link, name);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv,
16862306a36Sopenharmony_ci				  struct device_node *cpu_ep,
16962306a36Sopenharmony_ci				  struct device_node *codec_ep,
17062306a36Sopenharmony_ci				  struct link_info *li)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
17362306a36Sopenharmony_ci	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
17462306a36Sopenharmony_ci	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
17562306a36Sopenharmony_ci	struct device_node *top = dev->of_node;
17662306a36Sopenharmony_ci	struct device_node *ep = li->cpu ? cpu_ep : codec_ep;
17762306a36Sopenharmony_ci	char dai_name[64];
17862306a36Sopenharmony_ci	int ret;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	dev_dbg(dev, "link_of DPCM (%pOF)\n", ep);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (li->cpu) {
18362306a36Sopenharmony_ci		struct snd_soc_card *card = simple_priv_to_card(priv);
18462306a36Sopenharmony_ci		struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);
18562306a36Sopenharmony_ci		struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0);
18662306a36Sopenharmony_ci		int is_single_links = 0;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci		/* Codec is dummy */
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		/* FE settings */
19162306a36Sopenharmony_ci		dai_link->dynamic		= 1;
19262306a36Sopenharmony_ci		dai_link->dpcm_merged_format	= 1;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci		ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
19562306a36Sopenharmony_ci		if (ret)
19662306a36Sopenharmony_ci			return ret;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		snprintf(dai_name, sizeof(dai_name),
19962306a36Sopenharmony_ci			 "fe.%pOFP.%s", cpus->of_node, cpus->dai_name);
20062306a36Sopenharmony_ci		/*
20162306a36Sopenharmony_ci		 * In BE<->BE connections it is not required to create
20262306a36Sopenharmony_ci		 * PCM devices at CPU end of the dai link and thus 'no_pcm'
20362306a36Sopenharmony_ci		 * flag needs to be set. It is useful when there are many
20462306a36Sopenharmony_ci		 * BE components and some of these have to be connected to
20562306a36Sopenharmony_ci		 * form a valid audio path.
20662306a36Sopenharmony_ci		 *
20762306a36Sopenharmony_ci		 * For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where
20862306a36Sopenharmony_ci		 * there are 'n' BE components in the path.
20962306a36Sopenharmony_ci		 */
21062306a36Sopenharmony_ci		if (card->component_chaining && !soc_component_is_pcm(cpus)) {
21162306a36Sopenharmony_ci			dai_link->no_pcm = 1;
21262306a36Sopenharmony_ci			dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup;
21362306a36Sopenharmony_ci		}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci		asoc_simple_canonicalize_cpu(cpus, is_single_links);
21662306a36Sopenharmony_ci		asoc_simple_canonicalize_platform(platforms, cpus);
21762306a36Sopenharmony_ci	} else {
21862306a36Sopenharmony_ci		struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, 0);
21962306a36Sopenharmony_ci		struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
22062306a36Sopenharmony_ci		struct device_node *port;
22162306a36Sopenharmony_ci		struct device_node *ports;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		/* CPU is dummy */
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		/* BE settings */
22662306a36Sopenharmony_ci		dai_link->no_pcm		= 1;
22762306a36Sopenharmony_ci		dai_link->be_hw_params_fixup	= asoc_simple_be_hw_params_fixup;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci		ret = graph_parse_node(priv, codec_ep, li, NULL);
23062306a36Sopenharmony_ci		if (ret < 0)
23162306a36Sopenharmony_ci			return ret;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		snprintf(dai_name, sizeof(dai_name),
23462306a36Sopenharmony_ci			 "be.%pOFP.%s", codecs->of_node, codecs->dai_name);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci		/* check "prefix" from top node */
23762306a36Sopenharmony_ci		port = of_get_parent(ep);
23862306a36Sopenharmony_ci		ports = of_get_parent(port);
23962306a36Sopenharmony_ci		snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node,
24062306a36Sopenharmony_ci					      "prefix");
24162306a36Sopenharmony_ci		if (of_node_name_eq(ports, "ports"))
24262306a36Sopenharmony_ci			snd_soc_of_parse_node_prefix(ports, cconf, codecs->of_node, "prefix");
24362306a36Sopenharmony_ci		snd_soc_of_parse_node_prefix(port, cconf, codecs->of_node,
24462306a36Sopenharmony_ci					     "prefix");
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci		of_node_put(ports);
24762306a36Sopenharmony_ci		of_node_put(port);
24862306a36Sopenharmony_ci	}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	graph_parse_convert(dev, ep, &dai_props->adata);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	snd_soc_dai_link_set_capabilities(dai_link);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	li->link++;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return ret;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic int graph_dai_link_of(struct asoc_simple_priv *priv,
26262306a36Sopenharmony_ci			     struct device_node *cpu_ep,
26362306a36Sopenharmony_ci			     struct device_node *codec_ep,
26462306a36Sopenharmony_ci			     struct link_info *li)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
26762306a36Sopenharmony_ci	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
26862306a36Sopenharmony_ci	struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);
26962306a36Sopenharmony_ci	struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
27062306a36Sopenharmony_ci	struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0);
27162306a36Sopenharmony_ci	char dai_name[64];
27262306a36Sopenharmony_ci	int ret, is_single_links = 0;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	dev_dbg(dev, "link_of (%pOF)\n", cpu_ep);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
27762306a36Sopenharmony_ci	if (ret < 0)
27862306a36Sopenharmony_ci		return ret;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	ret = graph_parse_node(priv, codec_ep, li, NULL);
28162306a36Sopenharmony_ci	if (ret < 0)
28262306a36Sopenharmony_ci		return ret;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	snprintf(dai_name, sizeof(dai_name),
28562306a36Sopenharmony_ci		 "%s-%s", cpus->dai_name, codecs->dai_name);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	asoc_simple_canonicalize_cpu(cpus, is_single_links);
28862306a36Sopenharmony_ci	asoc_simple_canonicalize_platform(platforms, cpus);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
29162306a36Sopenharmony_ci	if (ret < 0)
29262306a36Sopenharmony_ci		return ret;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	li->link++;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	return 0;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic inline bool parse_as_dpcm_link(struct asoc_simple_priv *priv,
30062306a36Sopenharmony_ci				      struct device_node *codec_port,
30162306a36Sopenharmony_ci				      struct asoc_simple_data *adata)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	if (priv->force_dpcm)
30462306a36Sopenharmony_ci		return true;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (!priv->dpcm_selectable)
30762306a36Sopenharmony_ci		return false;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	/*
31062306a36Sopenharmony_ci	 * It is DPCM
31162306a36Sopenharmony_ci	 * if Codec port has many endpoints,
31262306a36Sopenharmony_ci	 * or has convert-xxx property
31362306a36Sopenharmony_ci	 */
31462306a36Sopenharmony_ci	if ((of_get_child_count(codec_port) > 1) ||
31562306a36Sopenharmony_ci	    asoc_simple_is_convert_required(adata))
31662306a36Sopenharmony_ci		return true;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	return false;
31962306a36Sopenharmony_ci}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cistatic int __graph_for_each_link(struct asoc_simple_priv *priv,
32262306a36Sopenharmony_ci			struct link_info *li,
32362306a36Sopenharmony_ci			int (*func_noml)(struct asoc_simple_priv *priv,
32462306a36Sopenharmony_ci					 struct device_node *cpu_ep,
32562306a36Sopenharmony_ci					 struct device_node *codec_ep,
32662306a36Sopenharmony_ci					 struct link_info *li),
32762306a36Sopenharmony_ci			int (*func_dpcm)(struct asoc_simple_priv *priv,
32862306a36Sopenharmony_ci					 struct device_node *cpu_ep,
32962306a36Sopenharmony_ci					 struct device_node *codec_ep,
33062306a36Sopenharmony_ci					 struct link_info *li))
33162306a36Sopenharmony_ci{
33262306a36Sopenharmony_ci	struct of_phandle_iterator it;
33362306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
33462306a36Sopenharmony_ci	struct device_node *node = dev->of_node;
33562306a36Sopenharmony_ci	struct device_node *cpu_port;
33662306a36Sopenharmony_ci	struct device_node *cpu_ep;
33762306a36Sopenharmony_ci	struct device_node *codec_ep;
33862306a36Sopenharmony_ci	struct device_node *codec_port;
33962306a36Sopenharmony_ci	struct device_node *codec_port_old = NULL;
34062306a36Sopenharmony_ci	struct asoc_simple_data adata;
34162306a36Sopenharmony_ci	int rc, ret = 0;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	/* loop for all listed CPU port */
34462306a36Sopenharmony_ci	of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
34562306a36Sopenharmony_ci		cpu_port = it.node;
34662306a36Sopenharmony_ci		cpu_ep	 = NULL;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci		/* loop for all CPU endpoint */
34962306a36Sopenharmony_ci		while (1) {
35062306a36Sopenharmony_ci			cpu_ep = of_get_next_child(cpu_port, cpu_ep);
35162306a36Sopenharmony_ci			if (!cpu_ep)
35262306a36Sopenharmony_ci				break;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci			/* get codec */
35562306a36Sopenharmony_ci			codec_ep = of_graph_get_remote_endpoint(cpu_ep);
35662306a36Sopenharmony_ci			codec_port = of_get_parent(codec_ep);
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci			/* get convert-xxx property */
35962306a36Sopenharmony_ci			memset(&adata, 0, sizeof(adata));
36062306a36Sopenharmony_ci			graph_parse_convert(dev, codec_ep, &adata);
36162306a36Sopenharmony_ci			graph_parse_convert(dev, cpu_ep,   &adata);
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci			/* check if link requires DPCM parsing */
36462306a36Sopenharmony_ci			if (parse_as_dpcm_link(priv, codec_port, &adata)) {
36562306a36Sopenharmony_ci				/*
36662306a36Sopenharmony_ci				 * Codec endpoint can be NULL for pluggable audio HW.
36762306a36Sopenharmony_ci				 * Platform DT can populate the Codec endpoint depending on the
36862306a36Sopenharmony_ci				 * plugged HW.
36962306a36Sopenharmony_ci				 */
37062306a36Sopenharmony_ci				/* Do it all CPU endpoint, and 1st Codec endpoint */
37162306a36Sopenharmony_ci				if (li->cpu ||
37262306a36Sopenharmony_ci				    ((codec_port_old != codec_port) && codec_ep))
37362306a36Sopenharmony_ci					ret = func_dpcm(priv, cpu_ep, codec_ep, li);
37462306a36Sopenharmony_ci			/* else normal sound */
37562306a36Sopenharmony_ci			} else {
37662306a36Sopenharmony_ci				if (li->cpu)
37762306a36Sopenharmony_ci					ret = func_noml(priv, cpu_ep, codec_ep, li);
37862306a36Sopenharmony_ci			}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci			of_node_put(codec_ep);
38162306a36Sopenharmony_ci			of_node_put(codec_port);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci			if (ret < 0) {
38462306a36Sopenharmony_ci				of_node_put(cpu_ep);
38562306a36Sopenharmony_ci				return ret;
38662306a36Sopenharmony_ci			}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci			codec_port_old = codec_port;
38962306a36Sopenharmony_ci		}
39062306a36Sopenharmony_ci	}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	return 0;
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cistatic int graph_for_each_link(struct asoc_simple_priv *priv,
39662306a36Sopenharmony_ci			       struct link_info *li,
39762306a36Sopenharmony_ci			       int (*func_noml)(struct asoc_simple_priv *priv,
39862306a36Sopenharmony_ci						struct device_node *cpu_ep,
39962306a36Sopenharmony_ci						struct device_node *codec_ep,
40062306a36Sopenharmony_ci						struct link_info *li),
40162306a36Sopenharmony_ci			       int (*func_dpcm)(struct asoc_simple_priv *priv,
40262306a36Sopenharmony_ci						struct device_node *cpu_ep,
40362306a36Sopenharmony_ci						struct device_node *codec_ep,
40462306a36Sopenharmony_ci						struct link_info *li))
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci	int ret;
40762306a36Sopenharmony_ci	/*
40862306a36Sopenharmony_ci	 * Detect all CPU first, and Detect all Codec 2nd.
40962306a36Sopenharmony_ci	 *
41062306a36Sopenharmony_ci	 * In Normal sound case, all DAIs are detected
41162306a36Sopenharmony_ci	 * as "CPU-Codec".
41262306a36Sopenharmony_ci	 *
41362306a36Sopenharmony_ci	 * In DPCM sound case,
41462306a36Sopenharmony_ci	 * all CPUs   are detected as "CPU-dummy", and
41562306a36Sopenharmony_ci	 * all Codecs are detected as "dummy-Codec".
41662306a36Sopenharmony_ci	 * To avoid random sub-device numbering,
41762306a36Sopenharmony_ci	 * detect "dummy-Codec" in last;
41862306a36Sopenharmony_ci	 */
41962306a36Sopenharmony_ci	for (li->cpu = 1; li->cpu >= 0; li->cpu--) {
42062306a36Sopenharmony_ci		ret = __graph_for_each_link(priv, li, func_noml, func_dpcm);
42162306a36Sopenharmony_ci		if (ret < 0)
42262306a36Sopenharmony_ci			break;
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	return ret;
42662306a36Sopenharmony_ci}
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_cistatic int graph_count_noml(struct asoc_simple_priv *priv,
42962306a36Sopenharmony_ci			    struct device_node *cpu_ep,
43062306a36Sopenharmony_ci			    struct device_node *codec_ep,
43162306a36Sopenharmony_ci			    struct link_info *li)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	if (li->link >= SNDRV_MAX_LINKS) {
43662306a36Sopenharmony_ci		dev_err(dev, "too many links\n");
43762306a36Sopenharmony_ci		return -EINVAL;
43862306a36Sopenharmony_ci	}
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	/*
44162306a36Sopenharmony_ci	 * DON'T REMOVE platforms
44262306a36Sopenharmony_ci	 * see
44362306a36Sopenharmony_ci	 *	simple-card.c :: simple_count_noml()
44462306a36Sopenharmony_ci	 */
44562306a36Sopenharmony_ci	li->num[li->link].cpus		= 1;
44662306a36Sopenharmony_ci	li->num[li->link].platforms     = 1;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	li->num[li->link].codecs	= 1;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	li->link += 1; /* 1xCPU-Codec */
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	dev_dbg(dev, "Count As Normal\n");
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	return 0;
45562306a36Sopenharmony_ci}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_cistatic int graph_count_dpcm(struct asoc_simple_priv *priv,
45862306a36Sopenharmony_ci			    struct device_node *cpu_ep,
45962306a36Sopenharmony_ci			    struct device_node *codec_ep,
46062306a36Sopenharmony_ci			    struct link_info *li)
46162306a36Sopenharmony_ci{
46262306a36Sopenharmony_ci	struct device *dev = simple_priv_to_dev(priv);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (li->link >= SNDRV_MAX_LINKS) {
46562306a36Sopenharmony_ci		dev_err(dev, "too many links\n");
46662306a36Sopenharmony_ci		return -EINVAL;
46762306a36Sopenharmony_ci	}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	if (li->cpu) {
47062306a36Sopenharmony_ci		/*
47162306a36Sopenharmony_ci		 * DON'T REMOVE platforms
47262306a36Sopenharmony_ci		 * see
47362306a36Sopenharmony_ci		 *	simple-card.c :: simple_count_noml()
47462306a36Sopenharmony_ci		 */
47562306a36Sopenharmony_ci		li->num[li->link].cpus		= 1;
47662306a36Sopenharmony_ci		li->num[li->link].platforms     = 1;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci		li->link++; /* 1xCPU-dummy */
47962306a36Sopenharmony_ci	} else {
48062306a36Sopenharmony_ci		li->num[li->link].codecs	= 1;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci		li->link++; /* 1xdummy-Codec */
48362306a36Sopenharmony_ci	}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	dev_dbg(dev, "Count As DPCM\n");
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	return 0;
48862306a36Sopenharmony_ci}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_cistatic int graph_get_dais_count(struct asoc_simple_priv *priv,
49162306a36Sopenharmony_ci				struct link_info *li)
49262306a36Sopenharmony_ci{
49362306a36Sopenharmony_ci	/*
49462306a36Sopenharmony_ci	 * link_num :	number of links.
49562306a36Sopenharmony_ci	 *		CPU-Codec / CPU-dummy / dummy-Codec
49662306a36Sopenharmony_ci	 * dais_num :	number of DAIs
49762306a36Sopenharmony_ci	 * ccnf_num :	number of codec_conf
49862306a36Sopenharmony_ci	 *		same number for "dummy-Codec"
49962306a36Sopenharmony_ci	 *
50062306a36Sopenharmony_ci	 * ex1)
50162306a36Sopenharmony_ci	 * CPU0 --- Codec0	link : 5
50262306a36Sopenharmony_ci	 * CPU1 --- Codec1	dais : 7
50362306a36Sopenharmony_ci	 * CPU2 -/		ccnf : 1
50462306a36Sopenharmony_ci	 * CPU3 --- Codec2
50562306a36Sopenharmony_ci	 *
50662306a36Sopenharmony_ci	 *	=> 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
50762306a36Sopenharmony_ci	 *	=> 7 DAIs  = 4xCPU + 3xCodec
50862306a36Sopenharmony_ci	 *	=> 1 ccnf  = 1xdummy-Codec
50962306a36Sopenharmony_ci	 *
51062306a36Sopenharmony_ci	 * ex2)
51162306a36Sopenharmony_ci	 * CPU0 --- Codec0	link : 5
51262306a36Sopenharmony_ci	 * CPU1 --- Codec1	dais : 6
51362306a36Sopenharmony_ci	 * CPU2 -/		ccnf : 1
51462306a36Sopenharmony_ci	 * CPU3 -/
51562306a36Sopenharmony_ci	 *
51662306a36Sopenharmony_ci	 *	=> 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
51762306a36Sopenharmony_ci	 *	=> 6 DAIs  = 4xCPU + 2xCodec
51862306a36Sopenharmony_ci	 *	=> 1 ccnf  = 1xdummy-Codec
51962306a36Sopenharmony_ci	 *
52062306a36Sopenharmony_ci	 * ex3)
52162306a36Sopenharmony_ci	 * CPU0 --- Codec0	link : 6
52262306a36Sopenharmony_ci	 * CPU1 -/		dais : 6
52362306a36Sopenharmony_ci	 * CPU2 --- Codec1	ccnf : 2
52462306a36Sopenharmony_ci	 * CPU3 -/
52562306a36Sopenharmony_ci	 *
52662306a36Sopenharmony_ci	 *	=> 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
52762306a36Sopenharmony_ci	 *	=> 6 DAIs  = 4xCPU + 2xCodec
52862306a36Sopenharmony_ci	 *	=> 2 ccnf  = 2xdummy-Codec
52962306a36Sopenharmony_ci	 *
53062306a36Sopenharmony_ci	 * ex4)
53162306a36Sopenharmony_ci	 * CPU0 --- Codec0 (convert-rate)	link : 3
53262306a36Sopenharmony_ci	 * CPU1 --- Codec1			dais : 4
53362306a36Sopenharmony_ci	 *					ccnf : 1
53462306a36Sopenharmony_ci	 *
53562306a36Sopenharmony_ci	 *	=> 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec
53662306a36Sopenharmony_ci	 *	=> 4 DAIs  = 2xCPU + 2xCodec
53762306a36Sopenharmony_ci	 *	=> 1 ccnf  = 1xdummy-Codec
53862306a36Sopenharmony_ci	 */
53962306a36Sopenharmony_ci	return graph_for_each_link(priv, li,
54062306a36Sopenharmony_ci				   graph_count_noml,
54162306a36Sopenharmony_ci				   graph_count_dpcm);
54262306a36Sopenharmony_ci}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ciint audio_graph_parse_of(struct asoc_simple_priv *priv, struct device *dev)
54562306a36Sopenharmony_ci{
54662306a36Sopenharmony_ci	struct snd_soc_card *card = simple_priv_to_card(priv);
54762306a36Sopenharmony_ci	struct link_info *li;
54862306a36Sopenharmony_ci	int ret;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL);
55162306a36Sopenharmony_ci	if (!li)
55262306a36Sopenharmony_ci		return -ENOMEM;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	card->owner = THIS_MODULE;
55562306a36Sopenharmony_ci	card->dev = dev;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	ret = graph_get_dais_count(priv, li);
55862306a36Sopenharmony_ci	if (ret < 0)
55962306a36Sopenharmony_ci		return ret;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	if (!li->link)
56262306a36Sopenharmony_ci		return -EINVAL;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	ret = asoc_simple_init_priv(priv, li);
56562306a36Sopenharmony_ci	if (ret < 0)
56662306a36Sopenharmony_ci		return ret;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);
56962306a36Sopenharmony_ci	if (IS_ERR(priv->pa_gpio)) {
57062306a36Sopenharmony_ci		ret = PTR_ERR(priv->pa_gpio);
57162306a36Sopenharmony_ci		dev_err(dev, "failed to get amplifier gpio: %d\n", ret);
57262306a36Sopenharmony_ci		return ret;
57362306a36Sopenharmony_ci	}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	ret = asoc_simple_parse_widgets(card, NULL);
57662306a36Sopenharmony_ci	if (ret < 0)
57762306a36Sopenharmony_ci		return ret;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	ret = asoc_simple_parse_routing(card, NULL);
58062306a36Sopenharmony_ci	if (ret < 0)
58162306a36Sopenharmony_ci		return ret;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	memset(li, 0, sizeof(*li));
58462306a36Sopenharmony_ci	ret = graph_for_each_link(priv, li,
58562306a36Sopenharmony_ci				  graph_dai_link_of,
58662306a36Sopenharmony_ci				  graph_dai_link_of_dpcm);
58762306a36Sopenharmony_ci	if (ret < 0)
58862306a36Sopenharmony_ci		goto err;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	ret = asoc_simple_parse_card_name(card, NULL);
59162306a36Sopenharmony_ci	if (ret < 0)
59262306a36Sopenharmony_ci		goto err;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	snd_soc_card_set_drvdata(card, priv);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	asoc_simple_debug_info(priv);
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	ret = devm_snd_soc_register_card(dev, card);
59962306a36Sopenharmony_ci	if (ret < 0)
60062306a36Sopenharmony_ci		goto err;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	devm_kfree(dev, li);
60362306a36Sopenharmony_ci	return 0;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_cierr:
60662306a36Sopenharmony_ci	asoc_simple_clean_reference(card);
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	return dev_err_probe(dev, ret, "parse error\n");
60962306a36Sopenharmony_ci}
61062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(audio_graph_parse_of);
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_cistatic int graph_probe(struct platform_device *pdev)
61362306a36Sopenharmony_ci{
61462306a36Sopenharmony_ci	struct asoc_simple_priv *priv;
61562306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
61662306a36Sopenharmony_ci	struct snd_soc_card *card;
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	/* Allocate the private data and the DAI link array */
61962306a36Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
62062306a36Sopenharmony_ci	if (!priv)
62162306a36Sopenharmony_ci		return -ENOMEM;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	card = simple_priv_to_card(priv);
62462306a36Sopenharmony_ci	card->dapm_widgets	= graph_dapm_widgets;
62562306a36Sopenharmony_ci	card->num_dapm_widgets	= ARRAY_SIZE(graph_dapm_widgets);
62662306a36Sopenharmony_ci	card->probe		= asoc_graph_card_probe;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	if (of_device_get_match_data(dev))
62962306a36Sopenharmony_ci		priv->dpcm_selectable = 1;
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	return audio_graph_parse_of(priv, dev);
63262306a36Sopenharmony_ci}
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_cistatic const struct of_device_id graph_of_match[] = {
63562306a36Sopenharmony_ci	{ .compatible = "audio-graph-card", },
63662306a36Sopenharmony_ci	{ .compatible = "audio-graph-scu-card",
63762306a36Sopenharmony_ci	  .data = (void *)DPCM_SELECTABLE },
63862306a36Sopenharmony_ci	{},
63962306a36Sopenharmony_ci};
64062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, graph_of_match);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_cistatic struct platform_driver graph_card = {
64362306a36Sopenharmony_ci	.driver = {
64462306a36Sopenharmony_ci		.name = "asoc-audio-graph-card",
64562306a36Sopenharmony_ci		.pm = &snd_soc_pm_ops,
64662306a36Sopenharmony_ci		.of_match_table = graph_of_match,
64762306a36Sopenharmony_ci	},
64862306a36Sopenharmony_ci	.probe = graph_probe,
64962306a36Sopenharmony_ci	.remove = asoc_simple_remove,
65062306a36Sopenharmony_ci};
65162306a36Sopenharmony_cimodule_platform_driver(graph_card);
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ciMODULE_ALIAS("platform:asoc-audio-graph-card");
65462306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
65562306a36Sopenharmony_ciMODULE_DESCRIPTION("ASoC Audio Graph Sound Card");
65662306a36Sopenharmony_ciMODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
657