cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

audio-graph-card.c (19176B)


      1// SPDX-License-Identifier: GPL-2.0
      2//
      3// ASoC audio graph sound card support
      4//
      5// Copyright (C) 2016 Renesas Solutions Corp.
      6// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
      7//
      8// based on ${LINUX}/sound/soc/generic/simple-card.c
      9
     10#include <linux/clk.h>
     11#include <linux/device.h>
     12#include <linux/gpio.h>
     13#include <linux/gpio/consumer.h>
     14#include <linux/module.h>
     15#include <linux/of.h>
     16#include <linux/of_device.h>
     17#include <linux/of_gpio.h>
     18#include <linux/of_graph.h>
     19#include <linux/platform_device.h>
     20#include <linux/string.h>
     21#include <sound/graph_card.h>
     22
     23#define DPCM_SELECTABLE 1
     24
     25static int graph_outdrv_event(struct snd_soc_dapm_widget *w,
     26			      struct snd_kcontrol *kcontrol,
     27			      int event)
     28{
     29	struct snd_soc_dapm_context *dapm = w->dapm;
     30	struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(dapm->card);
     31
     32	switch (event) {
     33	case SND_SOC_DAPM_POST_PMU:
     34		gpiod_set_value_cansleep(priv->pa_gpio, 1);
     35		break;
     36	case SND_SOC_DAPM_PRE_PMD:
     37		gpiod_set_value_cansleep(priv->pa_gpio, 0);
     38		break;
     39	default:
     40		return -EINVAL;
     41	}
     42
     43	return 0;
     44}
     45
     46static const struct snd_soc_dapm_widget graph_dapm_widgets[] = {
     47	SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM,
     48			       0, 0, NULL, 0, graph_outdrv_event,
     49			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
     50};
     51
     52static const struct snd_soc_ops graph_ops = {
     53	.startup	= asoc_simple_startup,
     54	.shutdown	= asoc_simple_shutdown,
     55	.hw_params	= asoc_simple_hw_params,
     56};
     57
     58static int graph_get_dai_id(struct device_node *ep)
     59{
     60	struct device_node *node;
     61	struct device_node *endpoint;
     62	struct of_endpoint info;
     63	int i, id;
     64	const u32 *reg;
     65	int ret;
     66
     67	/* use driver specified DAI ID if exist */
     68	ret = snd_soc_get_dai_id(ep);
     69	if (ret != -ENOTSUPP)
     70		return ret;
     71
     72	/* use endpoint/port reg if exist */
     73	ret = of_graph_parse_endpoint(ep, &info);
     74	if (ret == 0) {
     75		/*
     76		 * Because it will count port/endpoint if it doesn't have "reg".
     77		 * But, we can't judge whether it has "no reg", or "reg = <0>"
     78		 * only of_graph_parse_endpoint().
     79		 * We need to check "reg" property
     80		 */
     81		if (of_get_property(ep,   "reg", NULL))
     82			return info.id;
     83
     84		node = of_get_parent(ep);
     85		reg = of_get_property(node, "reg", NULL);
     86		of_node_put(node);
     87		if (reg)
     88			return info.port;
     89	}
     90	node = of_graph_get_port_parent(ep);
     91
     92	/*
     93	 * Non HDMI sound case, counting port/endpoint on its DT
     94	 * is enough. Let's count it.
     95	 */
     96	i = 0;
     97	id = -1;
     98	for_each_endpoint_of_node(node, endpoint) {
     99		if (endpoint == ep)
    100			id = i;
    101		i++;
    102	}
    103
    104	of_node_put(node);
    105
    106	if (id < 0)
    107		return -ENODEV;
    108
    109	return id;
    110}
    111
    112static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc)
    113{
    114	struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc);
    115
    116	if (dai && (dai->component->driver->pcm_construct ||
    117		    dai->driver->pcm_new))
    118		return true;
    119
    120	return false;
    121}
    122
    123static int asoc_simple_parse_dai(struct device_node *ep,
    124				 struct snd_soc_dai_link_component *dlc,
    125				 int *is_single_link)
    126{
    127	struct device_node *node;
    128	struct of_phandle_args args;
    129	int ret;
    130
    131	if (!ep)
    132		return 0;
    133
    134	node = of_graph_get_port_parent(ep);
    135
    136	/* Get dai->name */
    137	args.np		= node;
    138	args.args[0]	= graph_get_dai_id(ep);
    139	args.args_count	= (of_graph_get_endpoint_count(node) > 1);
    140
    141	/*
    142	 * FIXME
    143	 *
    144	 * Here, dlc->dai_name is pointer to CPU/Codec DAI name.
    145	 * If user unbinded CPU or Codec driver, but not for Sound Card,
    146	 * dlc->dai_name is keeping unbinded CPU or Codec
    147	 * driver's pointer.
    148	 *
    149	 * If user re-bind CPU or Codec driver again, ALSA SoC will try
    150	 * to rebind Card via snd_soc_try_rebind_card(), but because of
    151	 * above reason, it might can't bind Sound Card.
    152	 * Because Sound Card is pointing to released dai_name pointer.
    153	 *
    154	 * To avoid this rebind Card issue,
    155	 * 1) It needs to alloc memory to keep dai_name eventhough
    156	 *    CPU or Codec driver was unbinded, or
    157	 * 2) user need to rebind Sound Card everytime
    158	 *    if he unbinded CPU or Codec.
    159	 */
    160	ret = snd_soc_get_dai_name(&args, &dlc->dai_name);
    161	if (ret < 0)
    162		return ret;
    163
    164	dlc->of_node = node;
    165
    166	if (is_single_link)
    167		*is_single_link = of_graph_get_endpoint_count(node) == 1;
    168
    169	return 0;
    170}
    171
    172static void graph_parse_convert(struct device *dev,
    173				struct device_node *ep,
    174				struct asoc_simple_data *adata)
    175{
    176	struct device_node *top = dev->of_node;
    177	struct device_node *port = of_get_parent(ep);
    178	struct device_node *ports = of_get_parent(port);
    179	struct device_node *node = of_graph_get_port_parent(ep);
    180
    181	asoc_simple_parse_convert(top,   NULL,   adata);
    182	if (of_node_name_eq(ports, "ports"))
    183		asoc_simple_parse_convert(ports, NULL, adata);
    184	asoc_simple_parse_convert(port,  NULL,   adata);
    185	asoc_simple_parse_convert(ep,    NULL,   adata);
    186
    187	of_node_put(port);
    188	of_node_put(ports);
    189	of_node_put(node);
    190}
    191
    192static void graph_parse_mclk_fs(struct device_node *top,
    193				struct device_node *ep,
    194				struct simple_dai_props *props)
    195{
    196	struct device_node *port	= of_get_parent(ep);
    197	struct device_node *ports	= of_get_parent(port);
    198
    199	of_property_read_u32(top,	"mclk-fs", &props->mclk_fs);
    200	if (of_node_name_eq(ports, "ports"))
    201		of_property_read_u32(ports, "mclk-fs", &props->mclk_fs);
    202	of_property_read_u32(port,	"mclk-fs", &props->mclk_fs);
    203	of_property_read_u32(ep,	"mclk-fs", &props->mclk_fs);
    204
    205	of_node_put(port);
    206	of_node_put(ports);
    207}
    208
    209static int graph_parse_node(struct asoc_simple_priv *priv,
    210			    struct device_node *ep,
    211			    struct link_info *li,
    212			    int *cpu)
    213{
    214	struct device *dev = simple_priv_to_dev(priv);
    215	struct device_node *top = dev->of_node;
    216	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
    217	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
    218	struct snd_soc_dai_link_component *dlc;
    219	struct asoc_simple_dai *dai;
    220	int ret;
    221
    222	if (cpu) {
    223		dlc = asoc_link_to_cpu(dai_link, 0);
    224		dai = simple_props_to_dai_cpu(dai_props, 0);
    225	} else {
    226		dlc = asoc_link_to_codec(dai_link, 0);
    227		dai = simple_props_to_dai_codec(dai_props, 0);
    228	}
    229
    230	graph_parse_mclk_fs(top, ep, dai_props);
    231
    232	ret = asoc_simple_parse_dai(ep, dlc, cpu);
    233	if (ret < 0)
    234		return ret;
    235
    236	ret = asoc_simple_parse_tdm(ep, dai);
    237	if (ret < 0)
    238		return ret;
    239
    240	ret = asoc_simple_parse_clk(dev, ep, dai, dlc);
    241	if (ret < 0)
    242		return ret;
    243
    244	return 0;
    245}
    246
    247static int graph_link_init(struct asoc_simple_priv *priv,
    248			   struct device_node *cpu_ep,
    249			   struct device_node *codec_ep,
    250			   struct link_info *li,
    251			   char *name)
    252{
    253	struct device *dev = simple_priv_to_dev(priv);
    254	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
    255	int ret;
    256
    257	ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep,
    258				       NULL, &dai_link->dai_fmt);
    259	if (ret < 0)
    260		return ret;
    261
    262	dai_link->init		= asoc_simple_dai_init;
    263	dai_link->ops		= &graph_ops;
    264	if (priv->ops)
    265		dai_link->ops	= priv->ops;
    266
    267	return asoc_simple_set_dailink_name(dev, dai_link, name);
    268}
    269
    270static int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv,
    271				  struct device_node *cpu_ep,
    272				  struct device_node *codec_ep,
    273				  struct link_info *li)
    274{
    275	struct device *dev = simple_priv_to_dev(priv);
    276	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
    277	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
    278	struct device_node *top = dev->of_node;
    279	struct device_node *ep = li->cpu ? cpu_ep : codec_ep;
    280	char dai_name[64];
    281	int ret;
    282
    283	dev_dbg(dev, "link_of DPCM (%pOF)\n", ep);
    284
    285	if (li->cpu) {
    286		struct snd_soc_card *card = simple_priv_to_card(priv);
    287		struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);
    288		struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0);
    289		int is_single_links = 0;
    290
    291		/* Codec is dummy */
    292
    293		/* FE settings */
    294		dai_link->dynamic		= 1;
    295		dai_link->dpcm_merged_format	= 1;
    296
    297		ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
    298		if (ret)
    299			return ret;
    300
    301		snprintf(dai_name, sizeof(dai_name),
    302			 "fe.%pOFP.%s", cpus->of_node, cpus->dai_name);
    303		/*
    304		 * In BE<->BE connections it is not required to create
    305		 * PCM devices at CPU end of the dai link and thus 'no_pcm'
    306		 * flag needs to be set. It is useful when there are many
    307		 * BE components and some of these have to be connected to
    308		 * form a valid audio path.
    309		 *
    310		 * For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where
    311		 * there are 'n' BE components in the path.
    312		 */
    313		if (card->component_chaining && !soc_component_is_pcm(cpus)) {
    314			dai_link->no_pcm = 1;
    315			dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup;
    316		}
    317
    318		asoc_simple_canonicalize_cpu(cpus, is_single_links);
    319		asoc_simple_canonicalize_platform(platforms, cpus);
    320	} else {
    321		struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, 0);
    322		struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
    323		struct device_node *port;
    324		struct device_node *ports;
    325
    326		/* CPU is dummy */
    327
    328		/* BE settings */
    329		dai_link->no_pcm		= 1;
    330		dai_link->be_hw_params_fixup	= asoc_simple_be_hw_params_fixup;
    331
    332		ret = graph_parse_node(priv, codec_ep, li, NULL);
    333		if (ret < 0)
    334			return ret;
    335
    336		snprintf(dai_name, sizeof(dai_name),
    337			 "be.%pOFP.%s", codecs->of_node, codecs->dai_name);
    338
    339		/* check "prefix" from top node */
    340		port = of_get_parent(ep);
    341		ports = of_get_parent(port);
    342		snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node,
    343					      "prefix");
    344		if (of_node_name_eq(ports, "ports"))
    345			snd_soc_of_parse_node_prefix(ports, cconf, codecs->of_node, "prefix");
    346		snd_soc_of_parse_node_prefix(port, cconf, codecs->of_node,
    347					     "prefix");
    348
    349		of_node_put(ports);
    350		of_node_put(port);
    351	}
    352
    353	graph_parse_convert(dev, ep, &dai_props->adata);
    354
    355	snd_soc_dai_link_set_capabilities(dai_link);
    356
    357	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
    358
    359	li->link++;
    360
    361	return ret;
    362}
    363
    364static int graph_dai_link_of(struct asoc_simple_priv *priv,
    365			     struct device_node *cpu_ep,
    366			     struct device_node *codec_ep,
    367			     struct link_info *li)
    368{
    369	struct device *dev = simple_priv_to_dev(priv);
    370	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
    371	struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);
    372	struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
    373	struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0);
    374	char dai_name[64];
    375	int ret, is_single_links = 0;
    376
    377	dev_dbg(dev, "link_of (%pOF)\n", cpu_ep);
    378
    379	ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
    380	if (ret < 0)
    381		return ret;
    382
    383	ret = graph_parse_node(priv, codec_ep, li, NULL);
    384	if (ret < 0)
    385		return ret;
    386
    387	snprintf(dai_name, sizeof(dai_name),
    388		 "%s-%s", cpus->dai_name, codecs->dai_name);
    389
    390	asoc_simple_canonicalize_cpu(cpus, is_single_links);
    391	asoc_simple_canonicalize_platform(platforms, cpus);
    392
    393	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
    394	if (ret < 0)
    395		return ret;
    396
    397	li->link++;
    398
    399	return 0;
    400}
    401
    402static inline bool parse_as_dpcm_link(struct asoc_simple_priv *priv,
    403				      struct device_node *codec_port,
    404				      struct asoc_simple_data *adata)
    405{
    406	if (priv->force_dpcm)
    407		return true;
    408
    409	if (!priv->dpcm_selectable)
    410		return false;
    411
    412	/*
    413	 * It is DPCM
    414	 * if Codec port has many endpoints,
    415	 * or has convert-xxx property
    416	 */
    417	if ((of_get_child_count(codec_port) > 1) ||
    418	    (adata->convert_rate || adata->convert_channels))
    419		return true;
    420
    421	return false;
    422}
    423
    424static int __graph_for_each_link(struct asoc_simple_priv *priv,
    425			struct link_info *li,
    426			int (*func_noml)(struct asoc_simple_priv *priv,
    427					 struct device_node *cpu_ep,
    428					 struct device_node *codec_ep,
    429					 struct link_info *li),
    430			int (*func_dpcm)(struct asoc_simple_priv *priv,
    431					 struct device_node *cpu_ep,
    432					 struct device_node *codec_ep,
    433					 struct link_info *li))
    434{
    435	struct of_phandle_iterator it;
    436	struct device *dev = simple_priv_to_dev(priv);
    437	struct device_node *node = dev->of_node;
    438	struct device_node *cpu_port;
    439	struct device_node *cpu_ep;
    440	struct device_node *codec_ep;
    441	struct device_node *codec_port;
    442	struct device_node *codec_port_old = NULL;
    443	struct asoc_simple_data adata;
    444	int rc, ret = 0;
    445
    446	/* loop for all listed CPU port */
    447	of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
    448		cpu_port = it.node;
    449		cpu_ep	 = NULL;
    450
    451		/* loop for all CPU endpoint */
    452		while (1) {
    453			cpu_ep = of_get_next_child(cpu_port, cpu_ep);
    454			if (!cpu_ep)
    455				break;
    456
    457			/* get codec */
    458			codec_ep = of_graph_get_remote_endpoint(cpu_ep);
    459			codec_port = of_get_parent(codec_ep);
    460
    461			/* get convert-xxx property */
    462			memset(&adata, 0, sizeof(adata));
    463			graph_parse_convert(dev, codec_ep, &adata);
    464			graph_parse_convert(dev, cpu_ep,   &adata);
    465
    466			/* check if link requires DPCM parsing */
    467			if (parse_as_dpcm_link(priv, codec_port, &adata)) {
    468				/*
    469				 * Codec endpoint can be NULL for pluggable audio HW.
    470				 * Platform DT can populate the Codec endpoint depending on the
    471				 * plugged HW.
    472				 */
    473				/* Do it all CPU endpoint, and 1st Codec endpoint */
    474				if (li->cpu ||
    475				    ((codec_port_old != codec_port) && codec_ep))
    476					ret = func_dpcm(priv, cpu_ep, codec_ep, li);
    477			/* else normal sound */
    478			} else {
    479				if (li->cpu)
    480					ret = func_noml(priv, cpu_ep, codec_ep, li);
    481			}
    482
    483			of_node_put(codec_ep);
    484			of_node_put(codec_port);
    485
    486			if (ret < 0)
    487				return ret;
    488
    489			codec_port_old = codec_port;
    490		}
    491	}
    492
    493	return 0;
    494}
    495
    496static int graph_for_each_link(struct asoc_simple_priv *priv,
    497			       struct link_info *li,
    498			       int (*func_noml)(struct asoc_simple_priv *priv,
    499						struct device_node *cpu_ep,
    500						struct device_node *codec_ep,
    501						struct link_info *li),
    502			       int (*func_dpcm)(struct asoc_simple_priv *priv,
    503						struct device_node *cpu_ep,
    504						struct device_node *codec_ep,
    505						struct link_info *li))
    506{
    507	int ret;
    508	/*
    509	 * Detect all CPU first, and Detect all Codec 2nd.
    510	 *
    511	 * In Normal sound case, all DAIs are detected
    512	 * as "CPU-Codec".
    513	 *
    514	 * In DPCM sound case,
    515	 * all CPUs   are detected as "CPU-dummy", and
    516	 * all Codecs are detected as "dummy-Codec".
    517	 * To avoid random sub-device numbering,
    518	 * detect "dummy-Codec" in last;
    519	 */
    520	for (li->cpu = 1; li->cpu >= 0; li->cpu--) {
    521		ret = __graph_for_each_link(priv, li, func_noml, func_dpcm);
    522		if (ret < 0)
    523			break;
    524	}
    525
    526	return ret;
    527}
    528
    529static int graph_get_dais_count(struct asoc_simple_priv *priv,
    530				struct link_info *li);
    531
    532int audio_graph_parse_of(struct asoc_simple_priv *priv, struct device *dev)
    533{
    534	struct snd_soc_card *card = simple_priv_to_card(priv);
    535	struct link_info *li;
    536	int ret;
    537
    538	li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL);
    539	if (!li)
    540		return -ENOMEM;
    541
    542	card->owner = THIS_MODULE;
    543	card->dev = dev;
    544
    545	ret = graph_get_dais_count(priv, li);
    546	if (ret < 0)
    547		return ret;
    548
    549	if (!li->link)
    550		return -EINVAL;
    551
    552	ret = asoc_simple_init_priv(priv, li);
    553	if (ret < 0)
    554		return ret;
    555
    556	priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);
    557	if (IS_ERR(priv->pa_gpio)) {
    558		ret = PTR_ERR(priv->pa_gpio);
    559		dev_err(dev, "failed to get amplifier gpio: %d\n", ret);
    560		return ret;
    561	}
    562
    563	ret = asoc_simple_parse_widgets(card, NULL);
    564	if (ret < 0)
    565		return ret;
    566
    567	ret = asoc_simple_parse_routing(card, NULL);
    568	if (ret < 0)
    569		return ret;
    570
    571	memset(li, 0, sizeof(*li));
    572	ret = graph_for_each_link(priv, li,
    573				  graph_dai_link_of,
    574				  graph_dai_link_of_dpcm);
    575	if (ret < 0)
    576		goto err;
    577
    578	ret = asoc_simple_parse_card_name(card, NULL);
    579	if (ret < 0)
    580		goto err;
    581
    582	snd_soc_card_set_drvdata(card, priv);
    583
    584	asoc_simple_debug_info(priv);
    585
    586	ret = devm_snd_soc_register_card(dev, card);
    587	if (ret < 0)
    588		goto err;
    589
    590	devm_kfree(dev, li);
    591	return 0;
    592
    593err:
    594	asoc_simple_clean_reference(card);
    595
    596	return dev_err_probe(dev, ret, "parse error\n");
    597}
    598EXPORT_SYMBOL_GPL(audio_graph_parse_of);
    599
    600static int graph_count_noml(struct asoc_simple_priv *priv,
    601			    struct device_node *cpu_ep,
    602			    struct device_node *codec_ep,
    603			    struct link_info *li)
    604{
    605	struct device *dev = simple_priv_to_dev(priv);
    606
    607	if (li->link >= SNDRV_MAX_LINKS) {
    608		dev_err(dev, "too many links\n");
    609		return -EINVAL;
    610	}
    611
    612	li->num[li->link].cpus		= 1;
    613	li->num[li->link].codecs	= 1;
    614	li->num[li->link].platforms     = 1;
    615
    616	li->link += 1; /* 1xCPU-Codec */
    617
    618	dev_dbg(dev, "Count As Normal\n");
    619
    620	return 0;
    621}
    622
    623static int graph_count_dpcm(struct asoc_simple_priv *priv,
    624			    struct device_node *cpu_ep,
    625			    struct device_node *codec_ep,
    626			    struct link_info *li)
    627{
    628	struct device *dev = simple_priv_to_dev(priv);
    629
    630	if (li->link >= SNDRV_MAX_LINKS) {
    631		dev_err(dev, "too many links\n");
    632		return -EINVAL;
    633	}
    634
    635	if (li->cpu) {
    636		li->num[li->link].cpus		= 1;
    637		li->num[li->link].platforms     = 1;
    638
    639		li->link++; /* 1xCPU-dummy */
    640	} else {
    641		li->num[li->link].codecs	= 1;
    642
    643		li->link++; /* 1xdummy-Codec */
    644	}
    645
    646	dev_dbg(dev, "Count As DPCM\n");
    647
    648	return 0;
    649}
    650
    651static int graph_get_dais_count(struct asoc_simple_priv *priv,
    652				struct link_info *li)
    653{
    654	/*
    655	 * link_num :	number of links.
    656	 *		CPU-Codec / CPU-dummy / dummy-Codec
    657	 * dais_num :	number of DAIs
    658	 * ccnf_num :	number of codec_conf
    659	 *		same number for "dummy-Codec"
    660	 *
    661	 * ex1)
    662	 * CPU0 --- Codec0	link : 5
    663	 * CPU1 --- Codec1	dais : 7
    664	 * CPU2 -/		ccnf : 1
    665	 * CPU3 --- Codec2
    666	 *
    667	 *	=> 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
    668	 *	=> 7 DAIs  = 4xCPU + 3xCodec
    669	 *	=> 1 ccnf  = 1xdummy-Codec
    670	 *
    671	 * ex2)
    672	 * CPU0 --- Codec0	link : 5
    673	 * CPU1 --- Codec1	dais : 6
    674	 * CPU2 -/		ccnf : 1
    675	 * CPU3 -/
    676	 *
    677	 *	=> 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
    678	 *	=> 6 DAIs  = 4xCPU + 2xCodec
    679	 *	=> 1 ccnf  = 1xdummy-Codec
    680	 *
    681	 * ex3)
    682	 * CPU0 --- Codec0	link : 6
    683	 * CPU1 -/		dais : 6
    684	 * CPU2 --- Codec1	ccnf : 2
    685	 * CPU3 -/
    686	 *
    687	 *	=> 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
    688	 *	=> 6 DAIs  = 4xCPU + 2xCodec
    689	 *	=> 2 ccnf  = 2xdummy-Codec
    690	 *
    691	 * ex4)
    692	 * CPU0 --- Codec0 (convert-rate)	link : 3
    693	 * CPU1 --- Codec1			dais : 4
    694	 *					ccnf : 1
    695	 *
    696	 *	=> 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec
    697	 *	=> 4 DAIs  = 2xCPU + 2xCodec
    698	 *	=> 1 ccnf  = 1xdummy-Codec
    699	 */
    700	return graph_for_each_link(priv, li,
    701				   graph_count_noml,
    702				   graph_count_dpcm);
    703}
    704
    705static int graph_probe(struct platform_device *pdev)
    706{
    707	struct asoc_simple_priv *priv;
    708	struct device *dev = &pdev->dev;
    709	struct snd_soc_card *card;
    710
    711	/* Allocate the private data and the DAI link array */
    712	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    713	if (!priv)
    714		return -ENOMEM;
    715
    716	card = simple_priv_to_card(priv);
    717	card->dapm_widgets	= graph_dapm_widgets;
    718	card->num_dapm_widgets	= ARRAY_SIZE(graph_dapm_widgets);
    719	card->probe		= asoc_graph_card_probe;
    720
    721	if (of_device_get_match_data(dev))
    722		priv->dpcm_selectable = 1;
    723
    724	return audio_graph_parse_of(priv, dev);
    725}
    726
    727static const struct of_device_id graph_of_match[] = {
    728	{ .compatible = "audio-graph-card", },
    729	{ .compatible = "audio-graph-scu-card",
    730	  .data = (void *)DPCM_SELECTABLE },
    731	{},
    732};
    733MODULE_DEVICE_TABLE(of, graph_of_match);
    734
    735static struct platform_driver graph_card = {
    736	.driver = {
    737		.name = "asoc-audio-graph-card",
    738		.pm = &snd_soc_pm_ops,
    739		.of_match_table = graph_of_match,
    740	},
    741	.probe = graph_probe,
    742	.remove = asoc_simple_remove,
    743};
    744module_platform_driver(graph_card);
    745
    746MODULE_ALIAS("platform:asoc-audio-graph-card");
    747MODULE_LICENSE("GPL v2");
    748MODULE_DESCRIPTION("ASoC Audio Graph Sound Card");
    749MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");