Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.10.11.
  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		of_node_put(node);
163		return ret;
164	}
165
166	dlc->of_node = node;
167
168	if (is_single_link)
169		*is_single_link = of_graph_get_endpoint_count(node) == 1;
170
171	return 0;
172}
173
174static void graph_parse_convert(struct device *dev,
175				struct device_node *ep,
176				struct asoc_simple_data *adata)
177{
178	struct device_node *top = dev->of_node;
179	struct device_node *port = of_get_parent(ep);
180	struct device_node *ports = of_get_parent(port);
181	struct device_node *node = of_graph_get_port_parent(ep);
182
183	asoc_simple_parse_convert(top,   NULL,   adata);
184	if (of_node_name_eq(ports, "ports"))
185		asoc_simple_parse_convert(ports, NULL, adata);
186	asoc_simple_parse_convert(port,  NULL,   adata);
187	asoc_simple_parse_convert(ep,    NULL,   adata);
188
189	of_node_put(port);
190	of_node_put(ports);
191	of_node_put(node);
192}
193
194static void graph_parse_mclk_fs(struct device_node *top,
195				struct device_node *ep,
196				struct simple_dai_props *props)
197{
198	struct device_node *port	= of_get_parent(ep);
199	struct device_node *ports	= of_get_parent(port);
200
201	of_property_read_u32(top,	"mclk-fs", &props->mclk_fs);
202	if (of_node_name_eq(ports, "ports"))
203		of_property_read_u32(ports, "mclk-fs", &props->mclk_fs);
204	of_property_read_u32(port,	"mclk-fs", &props->mclk_fs);
205	of_property_read_u32(ep,	"mclk-fs", &props->mclk_fs);
206
207	of_node_put(port);
208	of_node_put(ports);
209}
210
211static int graph_parse_node(struct asoc_simple_priv *priv,
212			    struct device_node *ep,
213			    struct link_info *li,
214			    int *cpu)
215{
216	struct device *dev = simple_priv_to_dev(priv);
217	struct device_node *top = dev->of_node;
218	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
219	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
220	struct snd_soc_dai_link_component *dlc;
221	struct asoc_simple_dai *dai;
222	int ret;
223
224	if (cpu) {
225		dlc = asoc_link_to_cpu(dai_link, 0);
226		dai = simple_props_to_dai_cpu(dai_props, 0);
227	} else {
228		dlc = asoc_link_to_codec(dai_link, 0);
229		dai = simple_props_to_dai_codec(dai_props, 0);
230	}
231
232	graph_parse_mclk_fs(top, ep, dai_props);
233
234	ret = asoc_simple_parse_dai(ep, dlc, cpu);
235	if (ret < 0)
236		return ret;
237
238	ret = asoc_simple_parse_tdm(ep, dai);
239	if (ret < 0)
240		return ret;
241
242	ret = asoc_simple_parse_clk(dev, ep, dai, dlc);
243	if (ret < 0)
244		return ret;
245
246	return 0;
247}
248
249static int graph_link_init(struct asoc_simple_priv *priv,
250			   struct device_node *cpu_ep,
251			   struct device_node *codec_ep,
252			   struct link_info *li,
253			   char *name)
254{
255	struct device *dev = simple_priv_to_dev(priv);
256	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
257	int ret;
258
259	ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep,
260				       NULL, &dai_link->dai_fmt);
261	if (ret < 0)
262		return ret;
263
264	dai_link->init		= asoc_simple_dai_init;
265	dai_link->ops		= &graph_ops;
266	if (priv->ops)
267		dai_link->ops	= priv->ops;
268
269	return asoc_simple_set_dailink_name(dev, dai_link, name);
270}
271
272static int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv,
273				  struct device_node *cpu_ep,
274				  struct device_node *codec_ep,
275				  struct link_info *li)
276{
277	struct device *dev = simple_priv_to_dev(priv);
278	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
279	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
280	struct device_node *top = dev->of_node;
281	struct device_node *ep = li->cpu ? cpu_ep : codec_ep;
282	char dai_name[64];
283	int ret;
284
285	dev_dbg(dev, "link_of DPCM (%pOF)\n", ep);
286
287	if (li->cpu) {
288		struct snd_soc_card *card = simple_priv_to_card(priv);
289		struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);
290		struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0);
291		int is_single_links = 0;
292
293		/* Codec is dummy */
294
295		/* FE settings */
296		dai_link->dynamic		= 1;
297		dai_link->dpcm_merged_format	= 1;
298
299		ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
300		if (ret)
301			return ret;
302
303		snprintf(dai_name, sizeof(dai_name),
304			 "fe.%pOFP.%s", cpus->of_node, cpus->dai_name);
305		/*
306		 * In BE<->BE connections it is not required to create
307		 * PCM devices at CPU end of the dai link and thus 'no_pcm'
308		 * flag needs to be set. It is useful when there are many
309		 * BE components and some of these have to be connected to
310		 * form a valid audio path.
311		 *
312		 * For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where
313		 * there are 'n' BE components in the path.
314		 */
315		if (card->component_chaining && !soc_component_is_pcm(cpus)) {
316			dai_link->no_pcm = 1;
317			dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup;
318		}
319
320		asoc_simple_canonicalize_cpu(cpus, is_single_links);
321		asoc_simple_canonicalize_platform(platforms, cpus);
322	} else {
323		struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, 0);
324		struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
325		struct device_node *port;
326		struct device_node *ports;
327
328		/* CPU is dummy */
329
330		/* BE settings */
331		dai_link->no_pcm		= 1;
332		dai_link->be_hw_params_fixup	= asoc_simple_be_hw_params_fixup;
333
334		ret = graph_parse_node(priv, codec_ep, li, NULL);
335		if (ret < 0)
336			return ret;
337
338		snprintf(dai_name, sizeof(dai_name),
339			 "be.%pOFP.%s", codecs->of_node, codecs->dai_name);
340
341		/* check "prefix" from top node */
342		port = of_get_parent(ep);
343		ports = of_get_parent(port);
344		snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node,
345					      "prefix");
346		if (of_node_name_eq(ports, "ports"))
347			snd_soc_of_parse_node_prefix(ports, cconf, codecs->of_node, "prefix");
348		snd_soc_of_parse_node_prefix(port, cconf, codecs->of_node,
349					     "prefix");
350
351		of_node_put(ports);
352		of_node_put(port);
353	}
354
355	graph_parse_convert(dev, ep, &dai_props->adata);
356
357	snd_soc_dai_link_set_capabilities(dai_link);
358
359	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
360
361	li->link++;
362
363	return ret;
364}
365
366static int graph_dai_link_of(struct asoc_simple_priv *priv,
367			     struct device_node *cpu_ep,
368			     struct device_node *codec_ep,
369			     struct link_info *li)
370{
371	struct device *dev = simple_priv_to_dev(priv);
372	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
373	struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);
374	struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
375	struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0);
376	char dai_name[64];
377	int ret, is_single_links = 0;
378
379	dev_dbg(dev, "link_of (%pOF)\n", cpu_ep);
380
381	ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);
382	if (ret < 0)
383		return ret;
384
385	ret = graph_parse_node(priv, codec_ep, li, NULL);
386	if (ret < 0)
387		return ret;
388
389	snprintf(dai_name, sizeof(dai_name),
390		 "%s-%s", cpus->dai_name, codecs->dai_name);
391
392	asoc_simple_canonicalize_cpu(cpus, is_single_links);
393	asoc_simple_canonicalize_platform(platforms, cpus);
394
395	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);
396	if (ret < 0)
397		return ret;
398
399	li->link++;
400
401	return 0;
402}
403
404static inline bool parse_as_dpcm_link(struct asoc_simple_priv *priv,
405				      struct device_node *codec_port,
406				      struct asoc_simple_data *adata)
407{
408	if (priv->force_dpcm)
409		return true;
410
411	if (!priv->dpcm_selectable)
412		return false;
413
414	/*
415	 * It is DPCM
416	 * if Codec port has many endpoints,
417	 * or has convert-xxx property
418	 */
419	if ((of_get_child_count(codec_port) > 1) ||
420	    asoc_simple_is_convert_required(adata))
421		return true;
422
423	return false;
424}
425
426static int __graph_for_each_link(struct asoc_simple_priv *priv,
427			struct link_info *li,
428			int (*func_noml)(struct asoc_simple_priv *priv,
429					 struct device_node *cpu_ep,
430					 struct device_node *codec_ep,
431					 struct link_info *li),
432			int (*func_dpcm)(struct asoc_simple_priv *priv,
433					 struct device_node *cpu_ep,
434					 struct device_node *codec_ep,
435					 struct link_info *li))
436{
437	struct of_phandle_iterator it;
438	struct device *dev = simple_priv_to_dev(priv);
439	struct device_node *node = dev->of_node;
440	struct device_node *cpu_port;
441	struct device_node *cpu_ep;
442	struct device_node *codec_ep;
443	struct device_node *codec_port;
444	struct device_node *codec_port_old = NULL;
445	struct asoc_simple_data adata;
446	int rc, ret = 0;
447
448	/* loop for all listed CPU port */
449	of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
450		cpu_port = it.node;
451		cpu_ep	 = NULL;
452
453		/* loop for all CPU endpoint */
454		while (1) {
455			cpu_ep = of_get_next_child(cpu_port, cpu_ep);
456			if (!cpu_ep)
457				break;
458
459			/* get codec */
460			codec_ep = of_graph_get_remote_endpoint(cpu_ep);
461			codec_port = of_get_parent(codec_ep);
462
463			/* get convert-xxx property */
464			memset(&adata, 0, sizeof(adata));
465			graph_parse_convert(dev, codec_ep, &adata);
466			graph_parse_convert(dev, cpu_ep,   &adata);
467
468			/* check if link requires DPCM parsing */
469			if (parse_as_dpcm_link(priv, codec_port, &adata)) {
470				/*
471				 * Codec endpoint can be NULL for pluggable audio HW.
472				 * Platform DT can populate the Codec endpoint depending on the
473				 * plugged HW.
474				 */
475				/* Do it all CPU endpoint, and 1st Codec endpoint */
476				if (li->cpu ||
477				    ((codec_port_old != codec_port) && codec_ep))
478					ret = func_dpcm(priv, cpu_ep, codec_ep, li);
479			/* else normal sound */
480			} else {
481				if (li->cpu)
482					ret = func_noml(priv, cpu_ep, codec_ep, li);
483			}
484
485			of_node_put(codec_ep);
486			of_node_put(codec_port);
487
488			if (ret < 0) {
489				of_node_put(cpu_ep);
490				return ret;
491			}
492
493			codec_port_old = codec_port;
494		}
495	}
496
497	return 0;
498}
499
500static int graph_for_each_link(struct asoc_simple_priv *priv,
501			       struct link_info *li,
502			       int (*func_noml)(struct asoc_simple_priv *priv,
503						struct device_node *cpu_ep,
504						struct device_node *codec_ep,
505						struct link_info *li),
506			       int (*func_dpcm)(struct asoc_simple_priv *priv,
507						struct device_node *cpu_ep,
508						struct device_node *codec_ep,
509						struct link_info *li))
510{
511	int ret;
512	/*
513	 * Detect all CPU first, and Detect all Codec 2nd.
514	 *
515	 * In Normal sound case, all DAIs are detected
516	 * as "CPU-Codec".
517	 *
518	 * In DPCM sound case,
519	 * all CPUs   are detected as "CPU-dummy", and
520	 * all Codecs are detected as "dummy-Codec".
521	 * To avoid random sub-device numbering,
522	 * detect "dummy-Codec" in last;
523	 */
524	for (li->cpu = 1; li->cpu >= 0; li->cpu--) {
525		ret = __graph_for_each_link(priv, li, func_noml, func_dpcm);
526		if (ret < 0)
527			break;
528	}
529
530	return ret;
531}
532
533static int graph_get_dais_count(struct asoc_simple_priv *priv,
534				struct link_info *li);
535
536int audio_graph_parse_of(struct asoc_simple_priv *priv, struct device *dev)
537{
538	struct snd_soc_card *card = simple_priv_to_card(priv);
539	struct link_info *li;
540	int ret;
541
542	li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL);
543	if (!li)
544		return -ENOMEM;
545
546	card->owner = THIS_MODULE;
547	card->dev = dev;
548
549	ret = graph_get_dais_count(priv, li);
550	if (ret < 0)
551		return ret;
552
553	if (!li->link)
554		return -EINVAL;
555
556	ret = asoc_simple_init_priv(priv, li);
557	if (ret < 0)
558		return ret;
559
560	priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);
561	if (IS_ERR(priv->pa_gpio)) {
562		ret = PTR_ERR(priv->pa_gpio);
563		dev_err(dev, "failed to get amplifier gpio: %d\n", ret);
564		return ret;
565	}
566
567	ret = asoc_simple_parse_widgets(card, NULL);
568	if (ret < 0)
569		return ret;
570
571	ret = asoc_simple_parse_routing(card, NULL);
572	if (ret < 0)
573		return ret;
574
575	memset(li, 0, sizeof(*li));
576	ret = graph_for_each_link(priv, li,
577				  graph_dai_link_of,
578				  graph_dai_link_of_dpcm);
579	if (ret < 0)
580		goto err;
581
582	ret = asoc_simple_parse_card_name(card, NULL);
583	if (ret < 0)
584		goto err;
585
586	snd_soc_card_set_drvdata(card, priv);
587
588	asoc_simple_debug_info(priv);
589
590	ret = devm_snd_soc_register_card(dev, card);
591	if (ret < 0)
592		goto err;
593
594	devm_kfree(dev, li);
595	return 0;
596
597err:
598	asoc_simple_clean_reference(card);
599
600	return dev_err_probe(dev, ret, "parse error\n");
601}
602EXPORT_SYMBOL_GPL(audio_graph_parse_of);
603
604static int graph_count_noml(struct asoc_simple_priv *priv,
605			    struct device_node *cpu_ep,
606			    struct device_node *codec_ep,
607			    struct link_info *li)
608{
609	struct device *dev = simple_priv_to_dev(priv);
610
611	if (li->link >= SNDRV_MAX_LINKS) {
612		dev_err(dev, "too many links\n");
613		return -EINVAL;
614	}
615
616	li->num[li->link].cpus		= 1;
617	li->num[li->link].codecs	= 1;
618	li->num[li->link].platforms     = 1;
619
620	li->link += 1; /* 1xCPU-Codec */
621
622	dev_dbg(dev, "Count As Normal\n");
623
624	return 0;
625}
626
627static int graph_count_dpcm(struct asoc_simple_priv *priv,
628			    struct device_node *cpu_ep,
629			    struct device_node *codec_ep,
630			    struct link_info *li)
631{
632	struct device *dev = simple_priv_to_dev(priv);
633
634	if (li->link >= SNDRV_MAX_LINKS) {
635		dev_err(dev, "too many links\n");
636		return -EINVAL;
637	}
638
639	if (li->cpu) {
640		li->num[li->link].cpus		= 1;
641		li->num[li->link].platforms     = 1;
642
643		li->link++; /* 1xCPU-dummy */
644	} else {
645		li->num[li->link].codecs	= 1;
646
647		li->link++; /* 1xdummy-Codec */
648	}
649
650	dev_dbg(dev, "Count As DPCM\n");
651
652	return 0;
653}
654
655static int graph_get_dais_count(struct asoc_simple_priv *priv,
656				struct link_info *li)
657{
658	/*
659	 * link_num :	number of links.
660	 *		CPU-Codec / CPU-dummy / dummy-Codec
661	 * dais_num :	number of DAIs
662	 * ccnf_num :	number of codec_conf
663	 *		same number for "dummy-Codec"
664	 *
665	 * ex1)
666	 * CPU0 --- Codec0	link : 5
667	 * CPU1 --- Codec1	dais : 7
668	 * CPU2 -/		ccnf : 1
669	 * CPU3 --- Codec2
670	 *
671	 *	=> 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
672	 *	=> 7 DAIs  = 4xCPU + 3xCodec
673	 *	=> 1 ccnf  = 1xdummy-Codec
674	 *
675	 * ex2)
676	 * CPU0 --- Codec0	link : 5
677	 * CPU1 --- Codec1	dais : 6
678	 * CPU2 -/		ccnf : 1
679	 * CPU3 -/
680	 *
681	 *	=> 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
682	 *	=> 6 DAIs  = 4xCPU + 2xCodec
683	 *	=> 1 ccnf  = 1xdummy-Codec
684	 *
685	 * ex3)
686	 * CPU0 --- Codec0	link : 6
687	 * CPU1 -/		dais : 6
688	 * CPU2 --- Codec1	ccnf : 2
689	 * CPU3 -/
690	 *
691	 *	=> 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
692	 *	=> 6 DAIs  = 4xCPU + 2xCodec
693	 *	=> 2 ccnf  = 2xdummy-Codec
694	 *
695	 * ex4)
696	 * CPU0 --- Codec0 (convert-rate)	link : 3
697	 * CPU1 --- Codec1			dais : 4
698	 *					ccnf : 1
699	 *
700	 *	=> 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec
701	 *	=> 4 DAIs  = 2xCPU + 2xCodec
702	 *	=> 1 ccnf  = 1xdummy-Codec
703	 */
704	return graph_for_each_link(priv, li,
705				   graph_count_noml,
706				   graph_count_dpcm);
707}
708
709static int graph_probe(struct platform_device *pdev)
710{
711	struct asoc_simple_priv *priv;
712	struct device *dev = &pdev->dev;
713	struct snd_soc_card *card;
714
715	/* Allocate the private data and the DAI link array */
716	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
717	if (!priv)
718		return -ENOMEM;
719
720	card = simple_priv_to_card(priv);
721	card->dapm_widgets	= graph_dapm_widgets;
722	card->num_dapm_widgets	= ARRAY_SIZE(graph_dapm_widgets);
723	card->probe		= asoc_graph_card_probe;
724
725	if (of_device_get_match_data(dev))
726		priv->dpcm_selectable = 1;
727
728	return audio_graph_parse_of(priv, dev);
729}
730
731static const struct of_device_id graph_of_match[] = {
732	{ .compatible = "audio-graph-card", },
733	{ .compatible = "audio-graph-scu-card",
734	  .data = (void *)DPCM_SELECTABLE },
735	{},
736};
737MODULE_DEVICE_TABLE(of, graph_of_match);
738
739static struct platform_driver graph_card = {
740	.driver = {
741		.name = "asoc-audio-graph-card",
742		.pm = &snd_soc_pm_ops,
743		.of_match_table = graph_of_match,
744	},
745	.probe = graph_probe,
746	.remove = asoc_simple_remove,
747};
748module_platform_driver(graph_card);
749
750MODULE_ALIAS("platform:asoc-audio-graph-card");
751MODULE_LICENSE("GPL v2");
752MODULE_DESCRIPTION("ASoC Audio Graph Sound Card");
753MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");