brintos

brintos / linux-shallow public Read only

0
0
Text · 18.0 KiB · 4b38447 Raw
675 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// ASoC audio graph sound card support4//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.c9 10#include <linux/cleanup.h>11#include <linux/clk.h>12#include <linux/device.h>13#include <linux/gpio/consumer.h>14#include <linux/module.h>15#include <linux/of.h>16#include <linux/of_graph.h>17#include <linux/platform_device.h>18#include <linux/string.h>19#include <sound/graph_card.h>20 21#define DPCM_SELECTABLE 122 23#define ep_to_port(ep)	of_get_parent(ep)24static struct device_node *port_to_ports(struct device_node *port)25{26	struct device_node *ports = of_get_parent(port);27 28	if (!of_node_name_eq(ports, "ports")) {29		of_node_put(ports);30		return NULL;31	}32	return ports;33}34 35static int graph_outdrv_event(struct snd_soc_dapm_widget *w,36			      struct snd_kcontrol *kcontrol,37			      int event)38{39	struct snd_soc_dapm_context *dapm = w->dapm;40	struct simple_util_priv *priv = snd_soc_card_get_drvdata(dapm->card);41 42	switch (event) {43	case SND_SOC_DAPM_POST_PMU:44		gpiod_set_value_cansleep(priv->pa_gpio, 1);45		break;46	case SND_SOC_DAPM_PRE_PMD:47		gpiod_set_value_cansleep(priv->pa_gpio, 0);48		break;49	default:50		return -EINVAL;51	}52 53	return 0;54}55 56static const struct snd_soc_dapm_widget graph_dapm_widgets[] = {57	SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM,58			       0, 0, NULL, 0, graph_outdrv_event,59			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),60};61 62static const struct snd_soc_ops graph_ops = {63	.startup	= simple_util_startup,64	.shutdown	= simple_util_shutdown,65	.hw_params	= simple_util_hw_params,66};67 68static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc)69{70	struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc);71 72	if (dai && (dai->component->driver->pcm_construct ||73		    (dai->driver->ops && dai->driver->ops->pcm_new)))74		return true;75 76	return false;77}78 79static void graph_parse_convert(struct device *dev,80				struct device_node *ep,81				struct simple_util_data *adata)82{83	struct device_node *top = dev->of_node;84	struct device_node *port = ep_to_port(ep);85	struct device_node *ports = port_to_ports(port);86	struct device_node *node = of_graph_get_port_parent(ep);87 88	simple_util_parse_convert(top,   NULL,   adata);89	simple_util_parse_convert(ports, NULL,   adata);90	simple_util_parse_convert(port,  NULL,   adata);91	simple_util_parse_convert(ep,    NULL,   adata);92 93	of_node_put(port);94	of_node_put(ports);95	of_node_put(node);96}97 98static int graph_parse_node(struct simple_util_priv *priv,99			    struct device_node *ep,100			    struct link_info *li,101			    int *cpu)102{103	struct device *dev = simple_priv_to_dev(priv);104	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);105	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);106	struct snd_soc_dai_link_component *dlc;107	struct simple_util_dai *dai;108	int ret;109 110	if (cpu) {111		dlc = snd_soc_link_to_cpu(dai_link, 0);112		dai = simple_props_to_dai_cpu(dai_props, 0);113	} else {114		dlc = snd_soc_link_to_codec(dai_link, 0);115		dai = simple_props_to_dai_codec(dai_props, 0);116	}117 118	ret = graph_util_parse_dai(dev, ep, dlc, cpu);119	if (ret < 0)120		return ret;121 122	ret = simple_util_parse_tdm(ep, dai);123	if (ret < 0)124		return ret;125 126	ret = simple_util_parse_clk(dev, ep, dai, dlc);127	if (ret < 0)128		return ret;129 130	return 0;131}132 133static int graph_link_init(struct simple_util_priv *priv,134			   struct device_node *ep_cpu,135			   struct device_node *ep_codec,136			   struct link_info *li,137			   char *name)138{139	struct device *dev = simple_priv_to_dev(priv);140	struct device_node *top = dev->of_node;141	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);142	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);143	struct device_node *port_cpu = ep_to_port(ep_cpu);144	struct device_node *port_codec = ep_to_port(ep_codec);145	struct device_node *ports_cpu = port_to_ports(port_cpu);146	struct device_node *ports_codec = port_to_ports(port_codec);147	enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;148	enum snd_soc_trigger_order trigger_stop  = SND_SOC_TRIGGER_ORDER_DEFAULT;149	bool playback_only = 0, capture_only = 0;150	int ret;151 152	ret = simple_util_parse_daifmt(dev, ep_cpu, ep_codec,153				       NULL, &dai_link->dai_fmt);154	if (ret < 0)155		goto init_end;156 157	graph_util_parse_link_direction(top,		&playback_only, &capture_only);158	graph_util_parse_link_direction(port_cpu,	&playback_only, &capture_only);159	graph_util_parse_link_direction(port_codec,	&playback_only, &capture_only);160	graph_util_parse_link_direction(ep_cpu,		&playback_only, &capture_only);161	graph_util_parse_link_direction(ep_codec,	&playback_only, &capture_only);162 163	of_property_read_u32(top,		"mclk-fs", &dai_props->mclk_fs);164	of_property_read_u32(ports_cpu,		"mclk-fs", &dai_props->mclk_fs);165	of_property_read_u32(ports_codec,	"mclk-fs", &dai_props->mclk_fs);166	of_property_read_u32(port_cpu,		"mclk-fs", &dai_props->mclk_fs);167	of_property_read_u32(port_codec,	"mclk-fs", &dai_props->mclk_fs);168	of_property_read_u32(ep_cpu,		"mclk-fs", &dai_props->mclk_fs);169	of_property_read_u32(ep_codec,		"mclk-fs", &dai_props->mclk_fs);170 171	graph_util_parse_trigger_order(priv, top,		&trigger_start, &trigger_stop);172	graph_util_parse_trigger_order(priv, ports_cpu,		&trigger_start, &trigger_stop);173	graph_util_parse_trigger_order(priv, ports_codec,	&trigger_start, &trigger_stop);174	graph_util_parse_trigger_order(priv, port_cpu,		&trigger_start, &trigger_stop);175	graph_util_parse_trigger_order(priv, port_cpu,		&trigger_start, &trigger_stop);176	graph_util_parse_trigger_order(priv, ep_cpu,		&trigger_start, &trigger_stop);177	graph_util_parse_trigger_order(priv, ep_codec,		&trigger_start, &trigger_stop);178 179	dai_link->playback_only	= playback_only;180	dai_link->capture_only	= capture_only;181 182	dai_link->trigger_start	= trigger_start;183	dai_link->trigger_stop	= trigger_stop;184 185	dai_link->init		= simple_util_dai_init;186	dai_link->ops		= &graph_ops;187	if (priv->ops)188		dai_link->ops	= priv->ops;189 190	ret = simple_util_set_dailink_name(dev, dai_link, name);191init_end:192	of_node_put(ports_cpu);193	of_node_put(ports_codec);194	of_node_put(port_cpu);195	of_node_put(port_codec);196 197	return ret;198}199 200static int graph_dai_link_of_dpcm(struct simple_util_priv *priv,201				  struct device_node *cpu_ep,202				  struct device_node *codec_ep,203				  struct link_info *li)204{205	struct device *dev = simple_priv_to_dev(priv);206	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);207	struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);208	struct device_node *top = dev->of_node;209	struct device_node *ep = li->cpu ? cpu_ep : codec_ep;210	char dai_name[64];211	int ret;212 213	dev_dbg(dev, "link_of DPCM (%pOF)\n", ep);214 215	if (li->cpu) {216		struct snd_soc_card *card = simple_priv_to_card(priv);217		struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(dai_link, 0);218		struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, 0);219		int is_single_links = 0;220 221		/* Codec is dummy */222 223		/* FE settings */224		dai_link->dynamic		= 1;225		dai_link->dpcm_merged_format	= 1;226 227		ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);228		if (ret)229			return ret;230 231		snprintf(dai_name, sizeof(dai_name),232			 "fe.%pOFP.%s", cpus->of_node, cpus->dai_name);233		/*234		 * In BE<->BE connections it is not required to create235		 * PCM devices at CPU end of the dai link and thus 'no_pcm'236		 * flag needs to be set. It is useful when there are many237		 * BE components and some of these have to be connected to238		 * form a valid audio path.239		 *240		 * For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where241		 * there are 'n' BE components in the path.242		 */243		if (card->component_chaining && !soc_component_is_pcm(cpus)) {244			dai_link->no_pcm = 1;245			dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;246		}247 248		simple_util_canonicalize_cpu(cpus, is_single_links);249		simple_util_canonicalize_platform(platforms, cpus);250	} else {251		struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, 0);252		struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, 0);253		struct device_node *port;254		struct device_node *ports;255 256		/* CPU is dummy */257 258		/* BE settings */259		dai_link->no_pcm		= 1;260		dai_link->be_hw_params_fixup	= simple_util_be_hw_params_fixup;261 262		ret = graph_parse_node(priv, codec_ep, li, NULL);263		if (ret < 0)264			return ret;265 266		snprintf(dai_name, sizeof(dai_name),267			 "be.%pOFP.%s", codecs->of_node, codecs->dai_name);268 269		/* check "prefix" from top node */270		port  = ep_to_port(ep);271		ports = port_to_ports(port);272		snd_soc_of_parse_node_prefix(top,   cconf, codecs->of_node, "prefix");273		snd_soc_of_parse_node_prefix(ports, cconf, codecs->of_node, "prefix");274		snd_soc_of_parse_node_prefix(port,  cconf, codecs->of_node, "prefix");275 276		of_node_put(ports);277		of_node_put(port);278	}279 280	graph_parse_convert(dev, ep, &dai_props->adata);281 282	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);283 284	li->link++;285 286	return ret;287}288 289static int graph_dai_link_of(struct simple_util_priv *priv,290			     struct device_node *cpu_ep,291			     struct device_node *codec_ep,292			     struct link_info *li)293{294	struct device *dev = simple_priv_to_dev(priv);295	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);296	struct snd_soc_dai_link_component *cpus = snd_soc_link_to_cpu(dai_link, 0);297	struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, 0);298	struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, 0);299	char dai_name[64];300	int ret, is_single_links = 0;301 302	dev_dbg(dev, "link_of (%pOF)\n", cpu_ep);303 304	ret = graph_parse_node(priv, cpu_ep, li, &is_single_links);305	if (ret < 0)306		return ret;307 308	ret = graph_parse_node(priv, codec_ep, li, NULL);309	if (ret < 0)310		return ret;311 312	snprintf(dai_name, sizeof(dai_name),313		 "%s-%s", cpus->dai_name, codecs->dai_name);314 315	simple_util_canonicalize_cpu(cpus, is_single_links);316	simple_util_canonicalize_platform(platforms, cpus);317 318	ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name);319	if (ret < 0)320		return ret;321 322	li->link++;323 324	return 0;325}326 327static inline bool parse_as_dpcm_link(struct simple_util_priv *priv,328				      struct device_node *codec_port,329				      struct simple_util_data *adata)330{331	if (priv->force_dpcm)332		return true;333 334	if (!priv->dpcm_selectable)335		return false;336 337	/*338	 * It is DPCM339	 * if Codec port has many endpoints,340	 * or has convert-xxx property341	 */342	if ((of_get_child_count(codec_port) > 1) ||343	    simple_util_is_convert_required(adata))344		return true;345 346	return false;347}348 349static int __graph_for_each_link(struct simple_util_priv *priv,350			struct link_info *li,351			int (*func_noml)(struct simple_util_priv *priv,352					 struct device_node *cpu_ep,353					 struct device_node *codec_ep,354					 struct link_info *li),355			int (*func_dpcm)(struct simple_util_priv *priv,356					 struct device_node *cpu_ep,357					 struct device_node *codec_ep,358					 struct link_info *li))359{360	struct of_phandle_iterator it;361	struct device *dev = simple_priv_to_dev(priv);362	struct device_node *node = dev->of_node;363	struct device_node *cpu_port;364	struct device_node *codec_ep;365	struct device_node *codec_port;366	struct device_node *codec_port_old = NULL;367	struct simple_util_data adata;368	int rc, ret = 0;369 370	/* loop for all listed CPU port */371	of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {372		cpu_port = it.node;373 374		/* loop for all CPU endpoint */375		for_each_child_of_node_scoped(cpu_port, cpu_ep) {376			/* get codec */377			codec_ep = of_graph_get_remote_endpoint(cpu_ep);378			codec_port = ep_to_port(codec_ep);379 380			/* get convert-xxx property */381			memset(&adata, 0, sizeof(adata));382			graph_parse_convert(dev, codec_ep, &adata);383			graph_parse_convert(dev, cpu_ep,   &adata);384 385			/* check if link requires DPCM parsing */386			if (parse_as_dpcm_link(priv, codec_port, &adata)) {387				/*388				 * Codec endpoint can be NULL for pluggable audio HW.389				 * Platform DT can populate the Codec endpoint depending on the390				 * plugged HW.391				 */392				/* Do it all CPU endpoint, and 1st Codec endpoint */393				if (li->cpu ||394				    ((codec_port_old != codec_port) && codec_ep))395					ret = func_dpcm(priv, cpu_ep, codec_ep, li);396			/* else normal sound */397			} else {398				if (li->cpu)399					ret = func_noml(priv, cpu_ep, codec_ep, li);400			}401 402			of_node_put(codec_ep);403			of_node_put(codec_port);404 405			if (ret < 0)406				return ret;407 408			codec_port_old = codec_port;409		}410	}411 412	return 0;413}414 415static int graph_for_each_link(struct simple_util_priv *priv,416			       struct link_info *li,417			       int (*func_noml)(struct simple_util_priv *priv,418						struct device_node *cpu_ep,419						struct device_node *codec_ep,420						struct link_info *li),421			       int (*func_dpcm)(struct simple_util_priv *priv,422						struct device_node *cpu_ep,423						struct device_node *codec_ep,424						struct link_info *li))425{426	int ret;427	/*428	 * Detect all CPU first, and Detect all Codec 2nd.429	 *430	 * In Normal sound case, all DAIs are detected431	 * as "CPU-Codec".432	 *433	 * In DPCM sound case,434	 * all CPUs   are detected as "CPU-dummy", and435	 * all Codecs are detected as "dummy-Codec".436	 * To avoid random sub-device numbering,437	 * detect "dummy-Codec" in last;438	 */439	for (li->cpu = 1; li->cpu >= 0; li->cpu--) {440		ret = __graph_for_each_link(priv, li, func_noml, func_dpcm);441		if (ret < 0)442			break;443	}444 445	return ret;446}447 448static int graph_count_noml(struct simple_util_priv *priv,449			    struct device_node *cpu_ep,450			    struct device_node *codec_ep,451			    struct link_info *li)452{453	struct device *dev = simple_priv_to_dev(priv);454 455	if (li->link >= SNDRV_MAX_LINKS) {456		dev_err(dev, "too many links\n");457		return -EINVAL;458	}459 460	/*461	 * DON'T REMOVE platforms462	 * see463	 *	simple-card.c :: simple_count_noml()464	 */465	li->num[li->link].cpus		= 1;466	li->num[li->link].platforms     = 1;467 468	li->num[li->link].codecs	= 1;469 470	li->link += 1; /* 1xCPU-Codec */471 472	dev_dbg(dev, "Count As Normal\n");473 474	return 0;475}476 477static int graph_count_dpcm(struct simple_util_priv *priv,478			    struct device_node *cpu_ep,479			    struct device_node *codec_ep,480			    struct link_info *li)481{482	struct device *dev = simple_priv_to_dev(priv);483 484	if (li->link >= SNDRV_MAX_LINKS) {485		dev_err(dev, "too many links\n");486		return -EINVAL;487	}488 489	if (li->cpu) {490		/*491		 * DON'T REMOVE platforms492		 * see493		 *	simple-card.c :: simple_count_noml()494		 */495		li->num[li->link].cpus		= 1;496		li->num[li->link].platforms     = 1;497 498		li->link++; /* 1xCPU-dummy */499	} else {500		li->num[li->link].codecs	= 1;501 502		li->link++; /* 1xdummy-Codec */503	}504 505	dev_dbg(dev, "Count As DPCM\n");506 507	return 0;508}509 510static int graph_get_dais_count(struct simple_util_priv *priv,511				struct link_info *li)512{513	/*514	 * link_num :	number of links.515	 *		CPU-Codec / CPU-dummy / dummy-Codec516	 * dais_num :	number of DAIs517	 * ccnf_num :	number of codec_conf518	 *		same number for "dummy-Codec"519	 *520	 * ex1)521	 * CPU0 --- Codec0	link : 5522	 * CPU1 --- Codec1	dais : 7523	 * CPU2 -/		ccnf : 1524	 * CPU3 --- Codec2525	 *526	 *	=> 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec527	 *	=> 7 DAIs  = 4xCPU + 3xCodec528	 *	=> 1 ccnf  = 1xdummy-Codec529	 *530	 * ex2)531	 * CPU0 --- Codec0	link : 5532	 * CPU1 --- Codec1	dais : 6533	 * CPU2 -/		ccnf : 1534	 * CPU3 -/535	 *536	 *	=> 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec537	 *	=> 6 DAIs  = 4xCPU + 2xCodec538	 *	=> 1 ccnf  = 1xdummy-Codec539	 *540	 * ex3)541	 * CPU0 --- Codec0	link : 6542	 * CPU1 -/		dais : 6543	 * CPU2 --- Codec1	ccnf : 2544	 * CPU3 -/545	 *546	 *	=> 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec547	 *	=> 6 DAIs  = 4xCPU + 2xCodec548	 *	=> 2 ccnf  = 2xdummy-Codec549	 *550	 * ex4)551	 * CPU0 --- Codec0 (convert-rate)	link : 3552	 * CPU1 --- Codec1			dais : 4553	 *					ccnf : 1554	 *555	 *	=> 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec556	 *	=> 4 DAIs  = 2xCPU + 2xCodec557	 *	=> 1 ccnf  = 1xdummy-Codec558	 */559	return graph_for_each_link(priv, li,560				   graph_count_noml,561				   graph_count_dpcm);562}563 564int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev)565{566	struct snd_soc_card *card = simple_priv_to_card(priv);567	int ret;568 569	struct link_info *li __free(kfree) = kzalloc(sizeof(*li), GFP_KERNEL);570	if (!li)571		return -ENOMEM;572 573	card->owner = THIS_MODULE;574	card->dev = dev;575 576	ret = graph_get_dais_count(priv, li);577	if (ret < 0)578		return ret;579 580	if (!li->link)581		return -EINVAL;582 583	ret = simple_util_init_priv(priv, li);584	if (ret < 0)585		return ret;586 587	priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);588	if (IS_ERR(priv->pa_gpio)) {589		ret = PTR_ERR(priv->pa_gpio);590		dev_err(dev, "failed to get amplifier gpio: %d\n", ret);591		return ret;592	}593 594	ret = simple_util_parse_widgets(card, NULL);595	if (ret < 0)596		return ret;597 598	ret = simple_util_parse_routing(card, NULL);599	if (ret < 0)600		return ret;601 602	memset(li, 0, sizeof(*li));603	ret = graph_for_each_link(priv, li,604				  graph_dai_link_of,605				  graph_dai_link_of_dpcm);606	if (ret < 0)607		goto err;608 609	ret = simple_util_parse_card_name(card, NULL);610	if (ret < 0)611		goto err;612 613	snd_soc_card_set_drvdata(card, priv);614 615	simple_util_debug_info(priv);616 617	ret = devm_snd_soc_register_card(dev, card);618	if (ret < 0)619		goto err;620 621	return 0;622 623err:624	simple_util_clean_reference(card);625 626	return dev_err_probe(dev, ret, "parse error\n");627}628EXPORT_SYMBOL_GPL(audio_graph_parse_of);629 630static int graph_probe(struct platform_device *pdev)631{632	struct simple_util_priv *priv;633	struct device *dev = &pdev->dev;634	struct snd_soc_card *card;635 636	/* Allocate the private data and the DAI link array */637	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);638	if (!priv)639		return -ENOMEM;640 641	card = simple_priv_to_card(priv);642	card->dapm_widgets	= graph_dapm_widgets;643	card->num_dapm_widgets	= ARRAY_SIZE(graph_dapm_widgets);644	card->probe		= graph_util_card_probe;645 646	if (of_device_get_match_data(dev))647		priv->dpcm_selectable = 1;648 649	return audio_graph_parse_of(priv, dev);650}651 652static const struct of_device_id graph_of_match[] = {653	{ .compatible = "audio-graph-card", },654	{ .compatible = "audio-graph-scu-card",655	  .data = (void *)DPCM_SELECTABLE },656	{},657};658MODULE_DEVICE_TABLE(of, graph_of_match);659 660static struct platform_driver graph_card = {661	.driver = {662		.name = "asoc-audio-graph-card",663		.pm = &snd_soc_pm_ops,664		.of_match_table = graph_of_match,665	},666	.probe = graph_probe,667	.remove = simple_util_remove,668};669module_platform_driver(graph_card);670 671MODULE_ALIAS("platform:asoc-audio-graph-card");672MODULE_LICENSE("GPL v2");673MODULE_DESCRIPTION("ASoC Audio Graph Sound Card");674MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");675