Linux ALSA声卡驱动之六:ASoC架构中的Machine

来源:互联网 发布:北京朝阳有线网络电视 编辑:程序博客网 时间:2024/05/01 19:00

前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧。


/********************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/********************************************************************************************/

1. 注册Platform Device

ASoC把声卡注册为Platform Device,我们以装配有WM8994的一款Samsung的开发板SMDK为例子做说明,WM8994是一颗Wolfson生产的多功能Codec芯片。

代码的位于:/sound/soc/samsung/smdk_wm8994.c,我们关注模块的初始化函数:

[cpp] view plaincopy
  1. static int __init smdk_audio_init(void)  
  2. {  
  3.     int ret;  
  4.   
  5.     smdk_snd_device = platform_device_alloc("soc-audio", -1);  
  6.     if (!smdk_snd_device)  
  7.         return -ENOMEM;  
  8.   
  9.     platform_set_drvdata(smdk_snd_device, &smdk);  
  10.   
  11.     ret = platform_device_add(smdk_snd_device);  
  12.     if (ret)  
  13.         platform_device_put(smdk_snd_device);  
  14.   
  15.     return ret;  
  16. }  


由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把smdk设到platform_device结构的dev.drvdata字段中,这里引出了第一个数据结构snd_soc_card的实例smdk,他的定义如下:

[cpp] view plaincopy
  1. static struct snd_soc_dai_link smdk_dai[] = {  
  2.     { /* Primary DAI i/f */  
  3.         .name = "WM8994 AIF1",  
  4.         .stream_name = "Pri_Dai",  
  5.         .cpu_dai_name = "samsung-i2s.0",  
  6.         .codec_dai_name = "wm8994-aif1",  
  7.         .platform_name = "samsung-audio",  
  8.         .codec_name = "wm8994-codec",  
  9.         .init = smdk_wm8994_init_paiftx,  
  10.         .ops = &smdk_ops,  
  11.     }, { /* Sec_Fifo Playback i/f */  
  12.         .name = "Sec_FIFO TX",  
  13.         .stream_name = "Sec_Dai",  
  14.         .cpu_dai_name = "samsung-i2s.4",  
  15.         .codec_dai_name = "wm8994-aif1",  
  16.         .platform_name = "samsung-audio",  
  17.         .codec_name = "wm8994-codec",  
  18.         .ops = &smdk_ops,  
  19.     },  
  20. };  
  21.   
  22. static struct snd_soc_card smdk = {  
  23.     .name = "SMDK-I2S",  
  24.     .owner = THIS_MODULE,  
  25.     .dai_link = smdk_dai,  
  26.     .num_links = ARRAY_SIZE(smdk_dai),  
  27. };  

 

通过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,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params。

2. 注册Platform Driver

按照Linux的设备模型,有platform_device,就一定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。

还是先从模块的入口看起:

[cpp] view plaincopy
  1. static int __init snd_soc_init(void)  
  2. {  
  3.     ......  
  4.     return platform_driver_register(&soc_driver);  
  5. }  

soc_driver的定义如下:

[cpp] view plaincopy
  1. /* ASoC platform driver */  
  2. static struct platform_driver soc_driver = {  
  3.     .driver     = {  
  4.         .name       = "soc-audio",  
  5.         .owner      = THIS_MODULE,  
  6.         .pm     = &soc_pm_ops,  
  7.     },  
  8.     .probe      = soc_probe,  
  9.     .remove     = soc_remove,  
  10. };  

我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。

3. 初始化入口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的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。

[cpp] view plaincopy
  1. /* bind DAIs */  
  2. for (i = 0; i < card->num_links; i++)  
  3.     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驱动的信息。

snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例: 

[cpp] view plaincopy
  1. /* card bind complete so register a sound card */  
  2. ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,  
  3.         card->owner, 0, &card->snd_card);  
  4. card->snd_card->dev = card->dev;  
  5.   
  6. card->dapm.bias_level = SND_SOC_BIAS_OFF;  
  7. card->dapm.dev = card->dev;  
  8. card->dapm.card = card;  
  9. list_add(&card->dapm.list, &card->dapm_list);  


然后,依次调用各个子结构的probe函数:

[cpp] view plaincopy
  1. /* initialise the sound card only once */  
  2. if (card->probe) {  
  3.     ret = card->probe(card);  
  4.     if (ret < 0)  
  5.         goto card_probe_error;  
  6. }  
  7.   
  8. /* early DAI link probe */  
  9. for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;  
  10.         order++) {  
  11.     for (i = 0; i < card->num_links; i++) {  
  12.         ret = soc_probe_dai_link(card, i, order);  
  13.         if (ret < 0) {  
  14.             pr_err("asoc: failed to instantiate card %s: %d\n",  
  15.                card->name, ret);  
  16.             goto probe_dai_err;  
  17.         }  
  18.     }  
  19. }  
  20.   
  21. for (i = 0; i < card->num_aux_devs; i++) {  
  22.     ret = soc_probe_aux_dev(card, i);  
  23.     if (ret < 0) {  
  24.         pr_err("asoc: failed to add auxiliary devices %s: %d\n",  
  25.                card->name, ret);  
  26.         goto probe_aux_dev_err;  
  27.     }  
  28. }  

在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:


[cpp] view plaincopy
  1. static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)  
  2. {  
  3.         ......  
  4.     /* set default power off timeout */  
  5.     rtd->pmdown_time = pmdown_time;  
  6.   
  7.     /* probe the cpu_dai */  
  8.     if (!cpu_dai->probed &&  
  9.             cpu_dai->driver->probe_order == order) {  
  10.   
  11.         if (cpu_dai->driver->probe) {  
  12.             ret = cpu_dai->driver->probe(cpu_dai);  
  13.         }  
  14.         cpu_dai->probed = 1;  
  15.         /* mark cpu_dai as probed and add to card dai list */  
  16.         list_add(&cpu_dai->card_list, &card->dai_dev_list);  
  17.     }  
  18.   
  19.     /* probe the CODEC */  
  20.     if (!codec->probed &&  
  21.             codec->driver->probe_order == order) {  
  22.         ret = soc_probe_codec(card, codec);  
  23.     }  
  24.   
  25.     /* probe the platform */  
  26.     if (!platform->probed &&  
  27.             platform->driver->probe_order == order) {  
  28.         ret = soc_probe_platform(card, platform);  
  29.     }  
  30.   
  31.     /* probe the CODEC DAI */  
  32.     if (!codec_dai->probed && codec_dai->driver->probe_order == order) {  
  33.         if (codec_dai->driver->probe) {  
  34.             ret = codec_dai->driver->probe(codec_dai);  
  35.         }  
  36.   
  37.         /* mark codec_dai as probed and add to card dai list */  
  38.         codec_dai->probed = 1;  
  39.         list_add(&codec_dai->card_list, &card->dai_dev_list);  
  40.     }  
  41.   
  42.     /* complete DAI probe during last probe */  
  43.     if (order != SND_SOC_COMP_ORDER_LAST)  
  44.         return 0;  
  45.   
  46.     ret = soc_post_component_init(card, codec, num, 0);  
  47.     if (ret)  
  48.         return ret;  
  49.         ......  
  50.     /* create the pcm */  
  51.     ret = soc_new_pcm(rtd, num);  
  52.         ........  
  53.     return 0;  
  54. }  

该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:


 

[cpp] view plaincopy
  1. /* create a new pcm */  
  2. int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)  
  3. {  
  4.     ......  
  5.     struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;  
  6.   
  7.     soc_pcm_ops->open    = soc_pcm_open;  
  8.     soc_pcm_ops->close   = soc_pcm_close;  
  9.     soc_pcm_ops->hw_params   = soc_pcm_hw_params;  
  10.     soc_pcm_ops->hw_free = soc_pcm_hw_free;  
  11.     soc_pcm_ops->prepare = soc_pcm_prepare;  
  12.     soc_pcm_ops->trigger = soc_pcm_trigger;  
  13.     soc_pcm_ops->pointer = soc_pcm_pointer;  
  14.   
  15.     ret = snd_pcm_new(rtd->card->snd_card, new_name,  
  16.             num, playback, capture, &pcm);  
  17.   
  18.     /* DAPM dai link stream work */  
  19.     INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);  
  20.   
  21.     rtd->pcm = pcm;  
  22.     pcm->private_data = rtd;  
  23.     if (platform->driver->ops) {  
  24.         soc_pcm_ops->mmap = platform->driver->ops->mmap;  
  25.         soc_pcm_ops->pointer = platform->driver->ops->pointer;  
  26.         soc_pcm_ops->ioctl = platform->driver->ops->ioctl;  
  27.         soc_pcm_ops->copy = platform->driver->ops->copy;  
  28.         soc_pcm_ops->silence = platform->driver->ops->silence;  
  29.         soc_pcm_ops->ack = platform->driver->ops->ack;  
  30.         soc_pcm_ops->page = platform->driver->ops->page;  
  31.     }  
  32.   
  33.     if (playback)  
  34.         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);  
  35.   
  36.     if (capture)  
  37.         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);  
  38.   
  39.     if (platform->driver->pcm_new) {  
  40.         ret = platform->driver->pcm_new(rtd);  
  41.         if (ret < 0) {  
  42.             pr_err("asoc: platform pcm constructor failed\n");  
  43.             return ret;  
  44.         }  
  45.     }  
  46.   
  47.     pcm->private_free = platform->driver->pcm_free;  
  48.     return ret;  
  49. }  

该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。

回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:

[cpp] view plaincopy
  1. if (card->late_probe) {  
  2.     ret = card->late_probe(card);  
  3.     if (ret < 0) {  
  4.         dev_err(card->dev, "%s late_probe() failed: %d\n",  
  5.             card->name, ret);  
  6.         goto probe_aux_dev_err;  
  7.     }  
  8. }  
  9.   
  10. snd_soc_dapm_new_widgets(&card->dapm);  
  11.   
  12. if (card->fully_routed)  
  13.     list_for_each_entry(codec, &card->codec_dev_list, card_list)  
  14.         snd_soc_dapm_auto_nc_codec_pins(codec);  
  15.   
  16. ret = snd_card_register(card->snd_card);  
  17. if (ret < 0) {  
  18.     printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);  
  19.     goto probe_aux_dev_err;  
  20. }  


 至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用一下的序列图表示:


                                                                               图3.1  基于3.0内核  soc_probe序列图


下面的序列图是本文章第一个版本,基于内核2.6.35,大家也可以参考一下两个版本的差异:

                                                                               图3.2  基于2.6.35  soc_probe序列图
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 家里路由器网速一会快一会慢怎么办 用快看影视下载电影网速太慢怎么办 苹果手机下载东西网速特别慢怎么办 网上买重庆时时彩输了很多钱怎么办 找不到自己在哪个平台借过钱怎么办 九游账号绑定手机之前绑定的怎么办 九游充过钱的游戏忘了游戏名怎么办 百度网盘密码忘了申诉不了怎么办 手机号被别人注册了百度账号怎么办 快手被盗找回时出来重置密码怎么办 魅族账号密码和密保都忘记了怎么办 vivo账号的密保问题忘了怎么办 oppo账号密保问题忘了怎么办 小米手机刷了机忘了账号密码怎么办 忘了小米账号的密码是多少怎么办 千牛账号在手机上被限制登录怎么办 违规的千牛账号被限制登录了怎么办 苹果id和锁屏密码忘记了怎么办 感应门的编程密码忘记了怎么办 交易猫买的号被找回了怎么办 uc上我的小说看不了怎么办 微信零钱忘记密码没有银行卡怎么办 九游平台冻结提不了现怎么办 计算机考试报名登录名忘记了怎么办 云顶扑克提现怎么提不出来怎么办 微信正在下载一直0kb怎么办 守望先锋运行时出现意外错误怎么办 信用卡暂停使用怎么办还能恢复吗 新刷乳胶漆墙面一碰一个坑怎么办 夏天开空调冻着了头疼打喷嚏怎么办 桑蚕丝衣服被沐浴露退了色怎么办 空间被别人知道了密码登录了怎么办 三星手机显示解析包出现问题怎么办 三星手机下载解析包出现问题怎么办 两万的流动大棚给整坏了怎么办 劲舞团抽奖领了一样的衣服怎么办 win系统ps界面字体太小怎么办 任何网页都变成监控登录界面怎么办 微信启动录音的尝试被拒绝怎么办 微信传到电脑的文件打不开怎么办 转转网账号出租时遇到防沉迷怎么办