概述
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所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复