ASoc是ALSA针对嵌入式设备进行的一次封装。
这里通过分析smdk_wm8994的驱动洞悉Asoc的框架。
源码:smdk_wm8994.c (sound/soc/samsung)
驱动程序的入口是:smdk_audio_init
static int __init smdk_audio_init(void)
{
int ret;
smdk_snd_device = platform_device_alloc("soc-audio", -1);
if (!smdk_snd_device)
return -ENOMEM;
platform_set_drvdata(smdk_snd_device, &smdk);
ret = platform_device_add(smdk_snd_device);
if (ret)
platform_device_put(smdk_snd_device);
return ret;
}
函数分配了一个平台设备的结构体指针,并将smdk这个设置为platform_device的私有数据,然后直接调用platform_device_add添加这个平台设备。
根据内核的套路,既然添加的名字是“soc-audio”的平台设备,就一定存在对应的平台驱动。故,在内核代码中找出该platform_driver结构体。
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
这个结构体存在于Soc-core.c (sound/soc)中。根据内核的匹配规则,当smdk_wm8994.c调用platform_device_add函数向内核添加平台设备的时候,会导致soc_probe函数被调用。
我们来看看这个函数的调用过程。
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
int ret = 0;
/*
* 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;
ret = snd_soc_register_card(card);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
在soc_probe函数中,调用了platform_get_drvdata函数获取到了一个snd_soc_card指针。很显然,这个指针就是指向smdk这个结构体的,因为这次的soc_probe是由于smdk_wm8994.c中调用了platform_device_add函数导致的。所以这里的*card=&smdk。然后就是调用snd_soc_register_card。
int snd_soc_register_card(struct snd_soc_card *card)
{
int i;
if (!card->name || !card->dev)
return -EINVAL;
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
/*
* Codec must be specified by 1 of name or OF node,
* not both or neither.
*/
if (!!link->codec_name == !!link->codec_of_node) {
dev_err(card->dev,
"Neither/both codec name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/*
* 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,
"Both platform name/of_node are set for %s\n", link->name);
return -EINVAL;
}
/*
* CPU DAI must be specified by 1 of name or OF node,
* not both or neither.
*/
if (!!link->cpu_dai_name == !!link->cpu_dai_of_node) {
dev_err(card->dev,
"Neither/both cpu_dai name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
}
dev_set_drvdata(card->dev, card);
snd_soc_initialize_card_lists(card);
soc_init_card_debugfs(card);
/* 分配一个card->rtd指针,这里注意分配的大小,相当于card->num_links + card->num_aux_devs个snd_soc_pcm_runtime数组 */
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].dai_link = &card->dai_link[i]; // 这里指向smdk_dai,即将每一个smdk_dai成员分别用card->rtd[i].dai_link指针来表示
INIT_LIST_HEAD(&card->list);
INIT_LIST_HEAD(&card->dapm_dirty);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
mutex_lock(&client_mutex);
list_add(&card->list, &card_list); // 将crad指针存到card_list链表中
snd_soc_instantiate_cards(); // 核心, 实例化一个声卡
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
snd_soc_register_card函数前面进行了一系列的判断,从注释上可以看出,内核是检查传进来的card指针是否满足相应的要求。然后把card添加到card_list链表中,调用snd_soc_instantiate_cards去“实例化”一个声卡。
static void snd_soc_instantiate_cards(void)
{
struct snd_soc_card *card;
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
}
因为刚刚已经把crad指针放入了链表,所以snd_soc_instantiate_cards函数中,从链表card_list中取出每一个card指针,分别调用snd_soc_instantiate_card进行实例化。
下面继续分析snd_soc_instantiate_card函数。
函数进来的第一个操作就是DAI绑定。绑定的意思就是说,指定cpu侧使用的DAI接口是哪一个,指定codec侧的DAI接口是哪一个,毕竟,只有保证两边使用相同类型的接口,才能正常的通讯。
/* bind DAIs */
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i);
函数中通过一个for循环,对于每一个link都调用soc_bind_dai_link函数进行绑定。
绑定的原理:先在dai_list中找到cpu侧的cpu_dai,然后再从codec_list中找到指定的codec, 然后再找codec侧的dai, 最后找platform, 把找到的信息全部保存再rtd中。
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *codec_dai, *cpu_dai;
const char *platform_name;
if (rtd->complete)
return 1;
dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);
/* do we already have the CPU DAI for this link ? */
if (rtd->cpu_dai) {
goto find_codec;
}
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(cpu_dai, &dai_list, list) { // 在dai_list链表中,对于每一个cpu_dai,找到与cpu_dai_name相同名字的cpu_dai,例如:"samsung-i2s.0"
if (dai_link->cpu_dai_of_node) {
if (cpu_dai->dev->of_node != dai_link->cpu_dai_of_node)
continue;
} else {
if (strcmp(cpu_dai->name, dai_link->cpu_dai_name)) //比较名字
continue;
}
rtd->cpu_dai = cpu_dai; // 把找到的cpu_dai保存到rtd中。
goto find_codec;
}
dev_dbg(card->dev, "CPU DAI %s not registered\n",
dai_link->cpu_dai_name);
find_codec:
/* do we already have the CODEC for this link ? */
if (rtd->codec) {
goto find_platform;
}
/* no, then find CODEC from registered CODECs*/
list_for_each_entry(codec, &codec_list, list) { // 在codec_list中,对于每一个codec,找到与codec_name相同名字的codec,这里是: "wm8994-codec"
if (dai_link->codec_of_node) {
if (codec->dev->of_node != dai_link->codec_of_node)
continue;
} else {
if (strcmp(codec->name, dai_link->codec_name)) // 还是比较名字
continue;
}
rtd->codec = codec; // 把找到的codec保存在rtd中。
/*
* CODEC found, so find CODEC DAI from registered DAIs from
* this CODEC
*/
list_for_each_entry(codec_dai, &dai_list, list) { // 既然找到了codec, 那就还要找codec中的DAI,这里是: "wm8994-aif1"
if (codec->dev == codec_dai->dev &&
!strcmp(codec_dai->name,
dai_link->codec_dai_name)) {
rtd->codec_dai = codec_dai; // 把找到的dai保存到rtd中
goto find_platform;
}
}
dev_dbg(card->dev, "CODEC DAI %s not registered\n",
dai_link->codec_dai_name);
goto find_platform;
}
dev_dbg(card->dev, "CODEC %s not registered\n",
dai_link->codec_name);
find_platform:
/* do we need a platform? */
if (rtd->platform)
goto out;
/* if there's no platform we match on the empty platform */
platform_name = dai_link->platform_name; // 这里是: "samsung-audio"
if (!platform_name && !dai_link->platform_of_node)
platform_name = "snd-soc-dummy";
/* no, then find one from the set of registered platforms */
list_for_each_entry(platform, &platform_list, list) { // 在platform_list中,寻找一个名字是"samsung-audio"的platform
if (dai_link->platform_of_node) {
if (platform->dev->of_node !=
dai_link->platform_of_node)
continue;
} else {
if (strcmp(platform->name, platform_name))
continue;
}
rtd->platform = platform; // 把找到的platform保存到rtd
goto out;
}
dev_dbg(card->dev, "platform %s not registered\n",
dai_link->platform_name);
return 0;
out:
/* mark rtd as complete if we found all 4 of our client devices */
if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {
rtd->complete = 1;
card->num_rtd++;
}
return 1;
}
DAI绑定完成后,接着调用snd_soc_init_codec_cache进行对codec的寄存器进行一些默认的设置,这里不同的codec有不同的init方法,大概就是设置一些寄存器的默认值等等,这里不是重点。
接着就是使用一个循环,对每一个DAI进行早期的初始化。
/* early DAI link probe */
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_dai_link(card, i, order);
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}
}
其中soc_probe_dai_link函数中就是分别调用上面找到的cpu_dai的probe函数,调用找到的codec的probe函数,调用找到platform的probe函数, 调用codec侧的DAI的probe函数。然后就是调用soc_new_pcm接口创建对应的PCM逻辑设备。
早期的初始化完成后, 还会调用snd_soc_dai_set_fmt函数对各个dai接口进行一些格式的设置。
for (i = 0; i < card->num_links; i++) {
dai_link = &card->dai_link[i];
if (dai_link->dai_fmt) {
ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai,
dai_link->dai_fmt);
if (ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].codec_dai->dev,
"Failed to set DAI format: %d\n",
ret);
ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
dai_link->dai_fmt);
if (ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].cpu_dai->dev,
"Failed to set DAI format: %d\n",
ret);
}
}
设置完成后,就是调用ALSA的注册函数进行声卡的注册了。
ret = snd_card_register(card->snd_card);
if (ret < 0) {
pr_err("asoc: failed to register soundcard for %s: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}