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



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

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


    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//module_init 函数;
    2. {  
    3.     int ret;  
    4.   
    5.     smdk_snd_device = platform_device_alloc("soc-audio", -1); //create a platform device
    6.     if (!smdk_snd_device)  
    7.         return -ENOMEM;  
    8.   
    9.     platform_set_drvdata(smdk_snd_device, &smdk);  //结果 platform_device->dev->p->driver_data = data
    10.   
    11.     ret = platform_device_add(smdk_snd_device);  //platform_device_register 就是 调用 这个函数, 注册 platform_device 设备
    12.     if (ret)  
    13.         platform_device_put(smdk_snd_device);  //出错 执行这里;
    14.   
    15.     return ret;  
    16. }  


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


    static struct snd_soc_card smdk = {
        .name = "SMDK-I2S",
        .dai_link = smdk_dai,
        .num_links = ARRAY_SIZE(smdk_dai),
    };

    这里调用了 snd_soc_dai_link 结构;


    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.   

    通过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//module_nint
    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 ",  
    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 ",  
    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 ");  
    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 ",  
    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 ", 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序列图





  • 相关阅读:
    Office2007界面风格的绿色软件针式个人知识库管理系统[V3.5]
    Mentor工具简介
    Xilinx网站资源导读
    FPGA时钟问题的探讨汇总
    FPGA中竞争冒险问题的研究
    一些IC前端设计工具
    SPI协议简介
    USB接口定义
    TTL与CMOS电平的区别
    Synopsys工具简介
  • 原文地址:https://www.cnblogs.com/liulaolaiu/p/11744608.html
Copyright © 2020-2023  润新知