您的位置:首页 > 其它

alsa声卡驱动分析总结 (二)

2016-02-26 17:44 357 查看

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 = 
4000
"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部分分析完成了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: