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

转载 2018年04月17日 10:31:27

转载地址:https://blog.csdn.net/droidphone/article/details/7231605

前面一节的内容我们提到,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,我们关注模块的初始化函数:

  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,他的定义如下:

  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。

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

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

soc_driver的定义如下:

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

  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函数创建声卡实例: 

  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函数:

  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()函数中做了比较多的事情,把他展开继续讨论:


  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逻辑设备。现在把该函数的部分代码也贴出来:


 

  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驱动的声卡注册函数对声卡进行注册:

  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序列图

Linux ALSA框架之五:移动设备中的ALSA(ASoC)

1. ASoC的由来 ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系.在ASoc出现之前,内...
  • tangcong29
  • tangcong29
  • 2016-04-05 12:01:22
  • 310

Linux ASoC音频驱动架构 及 Machine驱动代码分析

【软件框架】     在对要做的事情一无所知的时候,从全局看看系统的拓扑图对我们认识新事物有很大的帮助。Audio 部分的驱动程序框架如下图所示:     这幅图明显地分为 3 级。     上方蓝...
  • Qidi_Huang
  • Qidi_Huang
  • 2016-07-18 00:33:11
  • 2367

alsa音频架构2-ASoc

设计ASoc的目的是为嵌入式系统片上处理器音频单元或外部的音频解码芯片提供更好的ALSA支持 ASoC有多个组件组成snd_soc_platform/snd_soc_codec/snd_soc_dai...
  • paomadi
  • paomadi
  • 2013-05-28 11:58:21
  • 4068

ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系

前面我们主要着重于codec、platform、machine驱动程序中如何使用和建立dapm所需要的widget,route,这些是音频驱动开发人员必须要了解的内容,经过前几章的介绍,我们应该知道如...
  • DroidPhone
  • DroidPhone
  • 2013-11-04 21:25:32
  • 21917

Linux ALSA声卡驱动之一:ALSA架构简介 分析 精辟

原文见链接Linux ALSA声卡驱动之一:ALSA架构简介 分析 精辟
  • zmbbb
  • zmbbb
  • 2017-04-24 16:48:26
  • 729

alsa架构分析(二)

Linux ALSA声卡驱动之六:ASoC架构中的Machine 前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责P...
  • poltroon
  • poltroon
  • 2012-09-14 11:20:49
  • 5513

ASoC Machine驱动

ASoC 体系结构分析已由DroidPhone前辈在博客中分析得很清楚,可以移步到http://blog.csdn.net/DroidPhone/article/category/1118446阅读对...
  • u013625961
  • u013625961
  • 2017-03-30 17:54:03
  • 589

ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身

设计dapm的主要目的之一,就是希望声卡上的各种部件的电源按需分配,需要的就上电,不需要的就下电,使得整个音频系统总是处于最小的耗电状态,最主要的就是,这一切对用户空间的应用程序是透明的,也就是说,用...
  • DroidPhone
  • DroidPhone
  • 2013-11-04 23:20:41
  • 17120

ALSA声卡驱动中的DAPM详解之一:kcontrol

DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态...
  • DroidPhone
  • DroidPhone
  • 2013-10-18 15:19:50
  • 31032

基于S3C2440的Linux-3.6.6移植——声卡驱动

Linux的ALSA声卡驱动较为复杂,它需要注册多个平台设备。在mach-zhaocj2440.c文件中的平台设备数组内一共有四个与ALSA相关的平台设备: &s3c_device_iis, &u...
  • zhaocj
  • zhaocj
  • 2014-03-05 11:12:35
  • 5450
收藏助手
不良信息举报
您举报文章:Linux ALSA声卡驱动之六:ASoC架构中的Machine
举报原因:
原因补充:

(最多只允许输入30个字)