我是靠谱客的博主 喜悦台灯,最近开发中收集的这篇文章主要介绍ASoC之Machine,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Machine是ASoC架构中的关键部分,没有Machine,Codec和Platform是无法工作的。Machine所做的主要工作,就是提供一个平台,把Codec和Platform(SoC)联系起来。Machine驱动开发的主要内容,就是向内核注册一个snd_soc_card声卡。本文简述其注册流程(内核版本为4.4)。

在soc-core.c(/kernel/sound/soc/)中,Linux内核有为我们默认注册了一个soc_driver平台设备驱动:

/* ASoC platform driver */
static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.pm		= &snd_soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	/*
	 * no card, so machine driver should be registering card
	 * we should not be here in that case so ret error
	 */
	if (!card)
		return -EINVAL;

	dev_warn(&pdev->dev,
		 "ASoC: machine %s should use snd_soc_register_card()n",
		 card->name);

	/* Bodge while we unpick instantiation */
	card->dev = &pdev->dev;

	return snd_soc_register_card(card);
}

可以看到,只要有合适的平台设备注册,匹配到soc_driver,在probe函数中便会注册该平台设备所包含的声卡。
既然如此,我们只需注册名字为"soc-audio"的平台设备即可:

static struct snd_soc_card xxx_card = {
......
};

static struct platform_device *snd_dev;

static int __init xxx_init(void)
{
	int ret;

	snd_dev= platform_device_alloc("soc-audio", -1);
	if (!snd_dev)
		return -ENOMEM;

	platform_set_drvdata(snd_dev, &xxx_card);
	ret= platform_device_add(snd_dev);
	if (ret) {
		pr_err("failed to add soc-audio devn");
		return ret;
	}

	return 0;
}

static void __exit xxx_exit(void)
{
	platform_device_unregister(snd_dev);
}

module_init(xxx_init);
module_exit(xxx_exit);

驱动主要完成的事情,便是填充snd_soc_card结构。
如果不使用内核默认的驱动,可自己定义platform_driver和platform_device,但最终的操作还是填充并注册声卡。

声卡注册接口:int snd_soc_register_card(struct snd_soc_card *card),参数为snd_soc_card结构。
下面分步对几点重要内容进行说明:
1.card必须要对name和dev成员进行赋值

	if (!card->name || !card->dev)
		return -EINVAL;

name在结构体中定义或用strcpy拷贝,dev一般直接写card->dev = &pdev->dev;

2.使用for循环对card中的dai_link数组每一个成员做一系列的判断

	for (i = 0; i < card->num_links; i++) {
		struct snd_soc_dai_link *link = &card->dai_link[i];
		......
	}

具体来看其中内容:
2-1)codec端的判断

		ret = snd_soc_init_multicodec(card, link);
		if (ret) {
			dev_err(card->dev, "ASoC: failed to init multicodecn");
			return ret;
		}

		for (j = 0; j < link->num_codecs; j++) {
			/*
			 * Codec must be specified by 1 of name or OF node,
			 * not both or neither.
			 */
			if (!!link->codecs[j].name ==
			    !!link->codecs[j].of_node) {
				dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %sn",
					link->name);
				return -EINVAL;
			}
			/* Codec DAI name must be specified */
			if (!link->codecs[j].dai_name) {
				dev_err(card->dev, "ASoC: codec_dai_name not set for %sn",
					link->name);
				return -EINVAL;
			}
		}

snd_soc_init_multicodec函数为每一个dai_link的codecs成员申请空间和赋值。codec_name和codec_of_node必须要指定,但不能同时指定,而codec_dai_name必须指定。

2-2)platform端的判断

		/*
		 * Platform may be specified by either name or OF node, but
		 * can be left unspecified, and a dummy platform will be used.
		 */
		if (link->platform_name && link->platform_of_node) {
			dev_err(card->dev,
				"ASoC: Both platform name/of_node are set for %sn",
				link->name);
			return -EINVAL;
		}

platform_name和platform_of_node不能同时指定,但可以均不指定,不指定会被设置为snd-soc-dummy。

2-3)cpu端的判断

		/*
		 * CPU device may be specified by either name or OF node, but
		 * can be left unspecified, and will be matched based on DAI
		 * name alone..
		 */
		if (link->cpu_name && link->cpu_of_node) {
			dev_err(card->dev,
				"ASoC: Neither/both cpu name/of_node are set for %sn",
				link->name);
			return -EINVAL;
		}
		/*
		 * At least one of CPU DAI name or CPU device name/node must be
		 * specified
		 */
		if (!link->cpu_dai_name &&
		    !(link->cpu_name || link->cpu_of_node)) {
			dev_err(card->dev,
				"ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %sn",
				link->name);
			return -EINVAL;
		}

cpu_name和cpu_of_node不能同时指定,但cpu_name/cpu_of_node和cpu_dai_name至少要指定一个。

3.为card中rtd申请空间并且复制dai_link信息

	card->rtd = devm_kzalloc(card->dev,
				 sizeof(struct snd_soc_pcm_runtime) *
				 (card->num_links + card->num_aux_devs),
				 GFP_KERNEL);
	if (card->rtd == NULL)
		return -ENOMEM;
	card->num_rtd = 0;
	card->rtd_aux = &card->rtd[card->num_links];

	for (i = 0; i < card->num_links; i++) {
		card->rtd[i].card = card;
		card->rtd[i].dai_link = &card->dai_link[i];
		card->rtd[i].codec_dais = devm_kzalloc(card->dev,
					sizeof(struct snd_soc_dai *) *
					(card->rtd[i].dai_link->num_codecs),
					GFP_KERNEL);
		if (card->rtd[i].codec_dais == NULL)
			return -ENOMEM;
	}

	for (i = 0; i < card->num_aux_devs; i++)
		card->rtd_aux[i].card = card;

除了复制dai_link内容,还为rtd的每一个codec_dais申请空间,codec_dais形式上是一个2元数组。

4.实例化card

	ret = snd_soc_instantiate_card(card);
	if (ret != 0)
		return ret;

实例化是注册函数最核心的部分,接下来具体分析。

实例化实现:static int snd_soc_instantiate_card(struct snd_soc_card *card)
snd_soc_instantiate_card函数完成了整个注册中大部分的工作:
1.绑定dai

	/* bind DAIs */
	for (i = 0; i < card->num_links; i++) {
		ret = soc_bind_dai_link(card, i);
		if (ret != 0)
			goto base_error;
	}

for循环遍历每一个dai_link,调用soc_bind_dai_link分别进行cpu、codec、platform的dai绑定工作:
1-1)cpu绑定

	cpu_dai_component.name = dai_link->cpu_name;
	cpu_dai_component.of_node = dai_link->cpu_of_node;
	cpu_dai_component.dai_name = dai_link->cpu_dai_name;
	rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
	if (!rtd->cpu_dai) {
		dev_err(card->dev, "ASoC: CPU DAI %s not registeredn",
			dai_link->cpu_dai_name);
		return -EPROBE_DEFER;
	}

snd_soc_find_dai根据所传入component的of_node和name遍历component_list,找到匹配的component后再遍历其dai_list链表找到对应的dai。通过snd_soc_register_component注册component时会添加到component_list。

1-2)codec绑定

	/* Find CODEC from registered CODECs */
	for (i = 0; i < rtd->num_codecs; i++) {
		codec_dais[i] = snd_soc_find_dai(&codecs[i]);
		if (!codec_dais[i]) {
			dev_err(card->dev, "ASoC: CODEC DAI %s not registeredn",
				codecs[i].dai_name);
			return -EPROBE_DEFER;
		}
	}

	/* Single codec links expect codec and codec_dai in runtime data */
	rtd->codec_dai = codec_dais[0];
	rtd->codec = rtd->codec_dai->codec;

codec的绑定流程跟cpu的类似,通过snd_soc_register_codec注册codec时也会添加到component_list。

1-3)platform绑定

	/* if there's no platform we match on the empty platform */
	platform_name = dai_link->platform_name;
	if (!platform_name && !dai_link->platform_of_node)
		platform_name = "snd-soc-dummy";

	/* find one from the set of registered platforms */
	list_for_each_entry(platform, &platform_list, list) {
		if (dai_link->platform_of_node) {
			if (platform->dev->of_node !=
			    dai_link->platform_of_node)
				continue;
		} else {
			if (strcmp(platform->component.name, platform_name))
				continue;
		}

		rtd->platform = platform;
	}

上面有提到当platform_name和platform_of_node均没有指定时会被设置为snd-soc-dummy,便是在这里实现。platform的绑定通过遍历platform_list得到,而platform_list是在调用snd_soc_register_platform时被填充。

2.创建声卡

	/* card bind complete so register a sound card */
	ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
			card->owner, 0, &card->snd_card);
	if (ret < 0) {
		dev_err(card->dev,
			"ASoC: can't create sound card for card %s: %dn",
			card->name, ret);
		goto base_error;
	}

idx为-1,xid为NULL,由内核自动生成索引编号,第一个注册声卡idx为0,后续注册的+1。

3.添加dapm widgets

	if (card->dapm_widgets)
		snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
					  card->num_dapm_widgets);

	if (card->of_dapm_widgets)
		snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
					  card->num_of_dapm_widgets);

这部分是直接定义在card结构中的,通常是针对平台端有实际物理连接的输入/输出接口,如耳机,扬声器,麦克风,所用到的宏定义也一般是SND_SOC_DAPM_MIC、SND_SOC_DAPM_SPK、SND_SOC_DAPM_SWITCH等等。

4.执行各个结构中的probe:

	/* initialise the sound card only once */
	if (card->probe) {
		ret = card->probe(card);
		if (ret < 0)
			goto card_probe_error;
	}

	/* probe all components used by DAI links on this card */
	for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
			order++) {
		for (i = 0; i < card->num_links; i++) {
			ret = soc_probe_link_components(card, i, order);
			if (ret < 0) {
				dev_err(card->dev,
					"ASoC: failed to instantiate card %dn",
					ret);
				goto probe_dai_err;
			}
		}
	}

	/* probe all DAI links on this card */
	for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
			order++) {
		for (i = 0; i < card->num_links; i++) {
			ret = soc_probe_link_dais(card, i, order);
			if (ret < 0) {
				dev_err(card->dev,
					"ASoC: failed to instantiate card %dn",
					ret);
				goto probe_dai_err;
			}
		}
	}

4-1)soc_probe_link_components
soc_probe_link_components函数针对cpu侧、codec侧和paltform的components,均通过soc_probe_component进行解析,添加其组件中包含的dapm widgets和controls等,同时执行component的probe实现。

4-2)soc_probe_link_dais
soc_probe_link_dais依次执行cpu侧和codec侧dai link的probe实现,但最重要的一点是调用了soc_new_pcm创建pcm设备。

	if (cpu_dai->driver->compress_new) {
		······
	} else {

		if (!dai_link->params) {
			/* create the pcm */
			ret = soc_new_pcm(rtd, num);
			if (ret < 0) {
				dev_err(card->dev, "ASoC: can't create pcm %s :%dn",
				       dai_link->stream_name, ret);
				return ret;
			}
		} else {
			······
		}
	}

5.添加controls

	if (card->controls)
		snd_soc_add_card_controls(card, card->controls, card->num_controls);

	if (card->dapm_routes)
		snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
					card->num_dapm_routes);

	if (card->of_dapm_routes)
		snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
					card->num_of_dapm_routes);

6.注册声卡

	ret = snd_card_register(card->snd_card);
	if (ret < 0) {
		dev_err(card->dev, "ASoC: failed to register soundcard %dn",
				ret);
		goto probe_aux_dev_err;
	}

至此,声卡的注册就全部完成。
我们可以通过cat /proc/asound/cards来确认声卡是否有正确注册。

我们可概括Machine driver的编写内容:
1.注册名为"soc-audio"的平台设备(或自行搭建platform device和plarform driver框架);
2.定义struct snd_soc_card结构体,设置好其中的dai_link等成员;
3.将struct snd_soc_card结构通过platform_set_drvdata设置到平台设备dev成员的私有数据中;
4.调用snd_soc_register_card向系统注册声卡。

当然,一般情况下,Machine端的部分是由各平台厂商完成的,不要驱动工程师编写Machine driver,我们只需大概了解声卡注册流程,但需要重点关注card中的dai_link定义。

最后

以上就是喜悦台灯为你收集整理的ASoC之Machine的全部内容,希望文章能够帮你解决ASoC之Machine所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部