alsa声卡驱动分析总结
来自:http://blog.chinaunix.net/uid-20672559-id-3515392.html
现在我们开始分析ASOC:
ASoC被分为Machine、Platform和Codec三大部分。其中的Machine驱动负责Platform和Codec之间的耦合和设备或板子特定的代码。
看起来挺复杂,其实需要我们做的事情并不多,大部分内核已经完成。下面我们分析哪些是我们需要自己做的:
codec驱动:负责音频解码。这部分代码完全无平台无关,设备原厂提供,我们只需要把它加进内核编译就好了。
platform驱动:与处理器芯片相关,这部分代码在该芯片商用之前方案产商提供的demo板已完全确定了,也就是说我们只需要使用就可以了。
machine驱动:好了,到了最关键的地方了,machine驱动是耦合platform和codec驱动,同时与上层交互的代码。由于上层是标准的alsa架构,所以下层接口肯定要做了统一,所以我很负责的告诉你,这部分由machine本身的platform驱动和platform设备组成(请跟asoc的platform驱动区别),platform驱动内核帮我们完成了,所以你无须过多的关心你的驱动怎么跟上层alsa怎么衍接的问题,我们只需要注册一个machine的platform设备以及完成platform和codec耦合就ok
asoc的关系图如下:(以下适应于linux3.0。linux2.6会有所不同)
上图把asoc架构显示的淋漓尽致,如果你分析了asoc你就会发现上图描述的结构以及函数真的一个都跑不了。
Machie:
Machine platform device:(~/sound/soc/samsung/smdk_wm8994.c)
- 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);
- static struct snd_soc_dai_link smdk_dai[] = {
- { /* Primary DAI i/f */
- .name = "WM8994 AIF1",
- .stream_name = "Pri_Dai",
- .cpu_dai_name = "samsung-i2s.0",
- .codec_dai_name = "wm8994-aif1",
- .platform_name = "samsung-audio",
- .codec_name = "wm8994-codec",
- .init = smdk_wm8994_init_paiftx,
- .ops = &smdk_ops,
- }, { /* Sec_Fifo Playback i/f */
- .name = "Sec_FIFO TX",
- .stream_name = "Sec_Dai",
- .cpu_dai_name = "samsung-i2s.4",
- .codec_dai_name = "wm8994-aif1",
- .platform_name = "samsung-audio",
- .codec_name = "wm8994-codec",
- .ops = &smdk_ops,
- },
- };
- static struct snd_soc_card smdk = {
- .name = "SMDK-I2S",
- .owner = THIS_MODULE,
- .dai_link = smdk_dai,
- .num_links = ARRAY_SIZE(smdk_dai),
- };
通过snd_soc_card结构,又引出了Machine驱动的另外两个个数据结构:
- snd_soc_dai_link(实例:smdk_dai[] )
- snd_soc_ops(实例:smdk_ops )
snd_soc_dai_link看名字就知道,很明显它是起耦合链接作用的。它指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai。
snd_soc_ops连接Platform和Codec的dai_link对应的ops操作函数,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params。
到此为止,最主要的部分machine的平台设备注册我们完成了。
下面我们关注machine平台驱动部分(这部分内核不需要我们实现,但我们需要知道它是怎么工作的)
ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。
还是先从模块的入口看起:
[cpp] view plaincopy
- static int __init snd_soc_init(void)
- {
- ......
- return platform_driver_register(&soc_driver);
- }
soc_driver的定义如下:
[cpp] view plaincopy
- /* ASoC platform driver */
- static struct platform_driver soc_driver = {
- .driver = {
- .name = "soc-audio", //确保你注册machine平台设备和它保持一致
- .owner = THIS_MODULE,
- .pm = &soc_pm_ops,
- },
- .probe = soc_probe,
- .remove = soc_remove,
- };
初始化入口soc_probe()
soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:
该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card =platform_get_drvdata(pdev);//别忘记了machine的platform_set_drvdata //取出snd_soc_card
.............
ret =snd_soc_register_card(card);//注册
............
}
下面我们看snd_soc_register_card()函数:
int snd_soc_register_card(struct snd_soc_card *card)
{
。。。。。。。。
card->rtd =kzalloc(sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);//为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应一个snd_soc_pcm_runtime数组单元
。。。。。。。。
for (i = 0; i <card->num_links; i++)
card->rtd[i].dai_link= &card->dai_link[i];//把snd_soc_card中的dai_link复制到相应的snd_soc_pcm_runtime
。。。。。。。。
snd_soc_instantiate_cards();//将调用snd_soc_instantiate_card()//最为重要
}
下面我们分析snd_soc_instantiate_card()函数:
static void snd_soc_instantiate_card(struct snd_soc_card*card)
{
。。。。。。。。
if (card->instantiated) { //判断该卡是否已经实例化,如果是就返回
mutex_unlock(&card->mutex);
return;
}
/* bind DAIs */
for (i = 0; i <card->num_links; i++) //否则遍例每一对dai_link,进行codec,flatrom,dai的绑定工作
soc_bind_dai_link(card,i);
/*******************************************************************************************************************************************************************
ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
*****************************************************************************************************************************************************************/
/* bind completed ? */
if (card->num_rtd !=card->num_links) {
mutex_unlock(&card->mutex);
return;
}
。。。。。。。。。
/* card bind complete soregister a sound card */
ret =snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner,0, &card->snd_card);
。。。。。。。。。
card->snd_card->dev =card->dev;
card->dapm.bias_level =SND_SOC_BIAS_OFF;
card->dapm.dev =card->dev;
card->dapm.card = card;
list_add(&card->dapm.list,&card->dapm_list);//初始化codec缓存,创建声卡实例
。。。。。。。。。。。。。
//下面是最重要的probe匹配工作
if (card->probe) {
ret =card->probe(card);
if (ret < 0)
gotocard_probe_error;
}
。。。。。。。。。
ret =soc_probe_dai_link(card, i, order);//主要的耦合链接工作在此函数完成
。。。。。。。。。
ret =soc_probe_aux_dev(card, i);
。。。。。。。。。
if (card->late_probe) {//最后的声卡初始化工作,
ret =card->late_probe(card);
}
。。。。。。。。。
ret =snd_card_register(card->snd_card);//然后调用标准的alsa驱动的声卡函数进行声卡注册
。。。。。。。。。
}
到这里声卡已经注册好了,声卡可以正常工作了,我们再分析最后一个函数,也就是它们是怎么匹配的 soc_probe_dai_link():
static int soc_probe_dai_link(struct snd_soc_card *card,int num, int order)
{
。。。。。。。。。。
/* probe the cpu_dai */
if (!cpu_dai->probed&&
cpu_dai->driver->probe_order== order) {
if(!try_module_get(cpu_dai->dev->driver->owner))
return -ENODEV;
if(cpu_dai->driver->probe) {
ret =cpu_dai->driver->probe(cpu_dai);
if (ret < 0){
printk(KERN_ERR"asoc: failed to probe CPU DAI %s\n",
cpu_dai->name);
module_put(cpu_dai->dev->driver->owner);
returnret;
}
}
cpu_dai->probed = 1;
/* mark cpu_dai asprobed and add to card dai list */
list_add(&cpu_dai->card_list,&card->dai_dev_list);
}
/* probe the CODEC */
if (!codec->probed&&
codec->driver->probe_order== order) {
ret =soc_probe_codec(card, codec);
if (ret < 0)
return ret;
}
/* probe the platform */
if (!platform->probed&&
platform->driver->probe_order== order) {
ret = soc_probe_platform(card,platform);
if (ret < 0)
return ret;
}
/* probe the CODEC DAI */
if (!codec_dai->probed&& codec_dai->driver->probe_order == order) {
if(codec_dai->driver->probe) {
ret =codec_dai->driver->probe(codec_dai);
if (ret < 0){
printk(KERN_ERR"asoc: failed to probe CODEC DAI %s\n",
codec_dai->name);
returnret;
}
}
/* mark codec_dai asprobed and add to card dai list */
codec_dai->probed =1;
list_add(&codec_dai->card_list,&card->dai_dev_list);
}
/* complete DAI probe duringlast probe */
if (order !=SND_SOC_COMP_ORDER_LAST)
return 0;
。。。。。。。。。。
/* create the pcm */
ret = soc_new_pcm(rtd, num);//如果上面都匹配成功将创建标准的alsa的pcm逻辑设备
。。。。。。。。。。
}
好了,到此为止我们最主要的部分machine部分分析完成了。