zoukankan      html  css  js  c++  java
  • ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)

    前面的六篇文章,我们已经讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等。本章我们准备讨论dapm框架中的另一个机制:事件机制。通过dapm事件机制,widget可以对它所关心的dapm事件做出反应,这种机制对于扩充widget的能力非常有用,例如,对于那些位于codec之外的widget,好像喇叭功放、外部的前置放大器等等,由于不是使用codec内部的寄存器进行电源控制,我们就必须利用dapm的事件机制,获得相应的上下电事件,从而可以定制widget自身的电源控制功能。

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

    dapm event的种类


    dapm目前为我们定义了9种dapm event,他们分别是:

    事件类型说明
    SND_SOC_DAPM_PRE_PMU widget要上电前发出的事件
    SND_SOC_DAPM_POST_PMU widget要上电后发出的事件
    SND_SOC_DAPM_PRE_PMD widget要下电前发出的事件
    SND_SOC_DAPM_POST_PMD widget要下电后发出的事件
    SND_SOC_DAPM_PRE_REG 音频路径设置之前发出的事件
    SND_SOC_DAPM_POST_REG 音频路径设置之后发出的事件
    SND_SOC_DAPM_WILL_PMU 在处理up_list链表之前发出的事件
    SND_SOC_DAPM_WILL_PMD 在处理down_list链表之前发出的事件
    SND_SOC_DAPM_PRE_POST_PMD SND_SOC_DAPM_PRE_PMD和
    SND_SOC_DAPM_POST_PMD的合并

    前8种每种占据一个位,所以,我们可以在一个整数中表达多个我们需要关心的dapm事件,只要把它们按位或进行合并即可。

    widget的event回调函数


    ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol中,我们已经介绍过代表widget的snd_soc_widget结构,在这个结构体中,有一个event字段用于保存该widget的事件回调函数,同时,event_flags字段用于保存该widget需要关心的dapm事件种类,只有event_flags字段中相应的事件位被设置了的事件才会发到event回调函数中进行处理。

    我们知道,dapm为我们提供了常用widget的定义辅助宏,使用以下这几种辅助宏定义widget时,默认需要我们提供dapm event回调函数

    • SND_SOC_DAPM_MIC
    • SND_SOC_DAPM_HP
    • SND_SOC_DAPM_SPK
    • SND_SOC_DAPM_LINE
    这些widget都是位于codec外部的器件,它们无法使用通用的寄存器操作来控制widget的电源状态,所以需要我们提供event回调函数。以下的例子来自dapm的内核文档,外部的喇叭功放通过CORGI_GPIO_APM_ON这个gpio来控制它的电源状态:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /* turn speaker amplifier on/off depending on use */  
    2. static int corgi_amp_event(struct snd_soc_dapm_widget *w, int event)  
    3. {  
    4.     gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event));  
    5.     return 0;  
    6. }  
    7.   
    8. /* corgi machine dapm widgets */  
    9. static const struct snd_soc_dapm_widget wm8731_dapm_widgets =  
    10.     SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event);  
    另外,我们也可以通过以下这些带"_E"后缀的辅助宏版本来定义需要dapm事件的widget:
    • SND_SOC_DAPM_PGA_E
    • SND_SOC_DAPM_OUT_DRV_E
    • SND_SOC_DAPM_MIXER_E
    • SND_SOC_DAPM_MIXER_NAMED_CTL_E
    • SND_SOC_DAPM_SWITCH_E
    • SND_SOC_DAPM_MUX_E
    • SND_SOC_DAPM_VIRT_MUX_E

    触发dapm event


    我们已经定义好了带有event回调的widget,那么,在那里触发这些dapm event?答案是:在dapm_power_widgets函数的处理过程中,dapm_power_widgets函数我们已经在ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身中做了详细的分析,其中,在所有需要处理电源变化的widget被分别放入up_list和down_list链表后,会相应地发出各种dapm事件:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static int dapm_power_widgets(struct snd_soc_card *card, int event)  
    2. {  
    3.         ......  
    4.         list_for_each_entry(w, &down_list, power_list) {  
    5.                 dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD);  
    6.         }  
    7.   
    8.         list_for_each_entry(w, &up_list, power_list) {  
    9.                 dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU);  
    10.         }  
    11.   
    12.         /* Power down widgets first; try to avoid amplifying pops. */  
    13.         dapm_seq_run(card, &down_list, event, false);  
    14.   
    15.         dapm_widget_update(card);  
    16.   
    17.         /* Now power up. */  
    18.         dapm_seq_run(card, &up_list, event, true);  
    19.         ......  
    20. }  

    可见,在真正地进行上电和下电之前,dapm向down_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMD事件,而向up_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMU事件。在处理上下电的函数dapm_seq_run中,会调用dapm_seq_run_coalesced函数执行真正的寄存器操作,进行widget的电源控制,dapm_seq_run_coalesced也会发出另外几种dapm事件:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static void dapm_seq_run_coalesced(struct snd_soc_card *card,  
    2.                                    struct list_head *pending)  
    3. {  
    4.         ......  
    5.         list_for_each_entry(w, pending, power_list) {  
    6.                 ......  
    7.                 /* Check for events */  
    8.                 dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMU);  
    9.                 dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMD);  
    10.         }  
    11.   
    12.         if (reg >= 0) {  
    13.                 ......  
    14.                 pop_wait(card->pop_time);  
    15.                 soc_widget_update_bits_locked(w, reg, mask, value);  
    16.         }  
    17.   
    18.         list_for_each_entry(w, pending, power_list) {  
    19.                 dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMU);  
    20.                 dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMD);  
    21.         }  
    22. }  

    另外,负责更新音频路径的dapm_widget_update函数中也会发出dapm事件:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static void dapm_widget_update(struct snd_soc_card *card)  
    2. {  
    3.         struct snd_soc_dapm_update *update = card->update;  
    4.         struct snd_soc_dapm_widget_list *wlist;  
    5.         struct snd_soc_dapm_widget *w = NULL;  
    6.         unsigned int wi;  
    7.         int ret;  
    8.   
    9.         if (!update || !dapm_kcontrol_is_powered(update->kcontrol))  
    10.                 return;  
    11.   
    12.         wlist = dapm_kcontrol_get_wlist(update->kcontrol);  
    13.   
    14.         for (wi = 0; wi < wlist->num_widgets; wi++) {  
    15.                 w = wlist->widgets[wi];  
    16.   
    17.                 if (w->event && (w->event_flags & SND_SOC_DAPM_PRE_REG)) {  
    18.                         ret = w->event(w, update->kcontrol, SND_SOC_DAPM_PRE_REG);  
    19.                         ......  
    20.                 }  
    21.         }  
    22.   
    23.         ......  
    24.         /* 更新kcontrol的值,改变音频路径 */  
    25.         ret = soc_widget_update_bits_locked(w, update->reg, update->mask,  
    26.                                   update->val);  
    27.         ......  
    28.   
    29.         for (wi = 0; wi < wlist->num_widgets; wi++) {  
    30.                 w = wlist->widgets[wi];  
    31.   
    32.                 if (w->event && (w->event_flags & SND_SOC_DAPM_POST_REG)) {  
    33.                         ret = w->event(w, update->kcontrol, SND_SOC_DAPM_POST_REG);  
    34.                         ......  
    35.                 }  
    36.         }  
    37. }  

    可见,改变路径的前后,分别发出了SND_SOC_DAPM_PRE_REG事件和SND_SOC_DAPM_POST_REG事件。

    dai widget与stream widget


    dai widget    在ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route一文中,我们已经讨论过dai widget,dai widget又分为cpu dai widget和codec dai widget,它们在machine驱动分别匹配上相应的codec和platform后,由soc_probe_platform和soc_probe_codec这两个函数通过调用dapm的api函数:

    • snd_soc_dapm_new_dai_widgets
    来创建的,通常会为playback和capture各自创建一个dai widget,他们的类型分别是:
    • snd_soc_dapm_dai_in      对应playback dai
    • snd_soc_dapm_dai_out    对应capture dai
    另外,dai widget的名字是使用stream name来命名的,他通常来自snd_soc_dai_driver中的stream_name字段。dai widget的sname字段也使用同样的名字。
    stream widget    stream widget通常是指那些要处理音频流数据的widget,它们包含以下这几种类型:
    • snd_soc_dapm_aif_in                 用SND_SOC_DAPM_AIF_IN辅助宏定义
    • snd_soc_dapm_aif_out               用SND_SOC_DAPM_AIF_OUT辅助宏定义
    • snd_soc_dapm_dac                    用SND_SOC_DAPM_AIF_DAC辅助宏定义
    • snd_soc_dapm_adc                    用SND_SOC_DAPM_AIF_ADC辅助宏定义
    对于这几种widget,我们除了要指定widget的名字外,还要指定他对应的stream的名字,保存在widget的sname字段中。

    连接dai widget和stream widget

    默认情况下,驱动不会通过snd_soc_route来主动定义dai widget和stream widget之间的连接关系,实际上,他们之间的连接关系是由ASoc负责的,在声卡的初始化函数中,使用snd_soc_dapm_link_dai_widgets函数来建立他们之间的连接关系:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static int snd_soc_instantiate_card(struct snd_soc_card *card)  
    2. {  
    3.         ......  
    4.         /* card bind complete so register a sound card */  
    5.         ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,  
    6.                         card->owner, 0, &card->snd_card);  
    7.         ......  
    8.         if (card->dapm_widgets)  
    9.                 snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,  
    10.                                           card->num_dapm_widgets);  
    11.         /*  建立dai widget和stream widget之间的连接关系  */  
    12.         snd_soc_dapm_link_dai_widgets(card);  
    13.         ......  
    14.         if (card->controls)  
    15.                 snd_soc_add_card_controls(card, card->controls, card->num_controls);  
    16.         ......  
    17.         if (card->dapm_routes)  
    18.                 snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,  
    19.                                         card->num_dapm_routes);  
    20.         ......  
    21.         if (card->fully_routed)  
    22.                 list_for_each_entry(codec, &card->codec_dev_list, card_list)  
    23.                         snd_soc_dapm_auto_nc_codec_pins(codec);  
    24.   
    25.         snd_soc_dapm_new_widgets(card);  
    26.   
    27.         ret = snd_card_register(card->snd_card);  
    28.         ......  
    29.         return 0;  
    30. }  
    我们再来分析一下snd_soc_dapm_link_dai_widgets函数,看看它是如何连接这两种widget的,它先是遍历声卡中所有的widget,找出类型为snd_soc_dapm_dai_in和snd_soc_dapm_dai_out的widget,通过widget的priv字段,取出widget对应的snd_soc_dai结构指针:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)  
    2. {                 
    3.         struct snd_soc_dapm_widget *dai_w, *w;  
    4.         struct snd_soc_dai *dai;  
    5.           
    6.         /* For each DAI widget... */  
    7.         list_for_each_entry(dai_w, &card->widgets, list) {  
    8.                 switch (dai_w->id) {  
    9.                 case snd_soc_dapm_dai_in:  
    10.                 case snd_soc_dapm_dai_out:  
    11.                         break;  
    12.                 default:  
    13.                         continue;  
    14.                 }         
    15.                                   
    16.                 dai = dai_w->priv;  
    接着,再次从头遍历声卡中所有的widget,找出能与dai widget相连接的stream widget,第一个前提条件是这两个widget必须位于同一个dapm context中:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /* ...find all widgets with the same stream and link them */  
    2. list_for_each_entry(w, &card->widgets, list) {  
    3.         if (w->dapm != dai_w->dapm)  
    4.                 continue;  
    dai widget不会与dai widget相连,所以跳过它们:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. switch (w->id) {  
    2. case snd_soc_dapm_dai_in:  
    3. case snd_soc_dapm_dai_out:  
    4.         continue;  
    5. default:  
    6.         break;  
    7. }  
    dai widget的名字没有出现在要连接的widget的stream name中,跳过这个widget:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. if (!w->sname || !strstr(w->sname, dai_w->name))  
    2.         continue;  
    如果widget的stream name包含了dai的stream name,则匹配成功,连接这两个widget:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1.                 if (dai->driver->playback.stream_name &&  
    2.                     strstr(w->sname,  
    3.                            dai->driver->playback.stream_name)) {  
    4.                         dev_dbg(dai->dev, "%s -> %s ",  
    5.                                  dai->playback_widget->name, w->name);  
    6.   
    7.                         snd_soc_dapm_add_path(w->dapm,  
    8.                                 dai->playback_widget, w, NULL, NULL);  
    9.                 }  
    10.   
    11.                 if (dai->driver->capture.stream_name &&  
    12.                     strstr(w->sname,  
    13.                            dai->driver->capture.stream_name)) {  
    14.                         dev_dbg(dai->dev, "%s -> %s ",  
    15.                                 w->name, dai->capture_widget->name);  
    16.   
    17.                         snd_soc_dapm_add_path(w->dapm, w,  
    18.                                 dai->capture_widget, NULL, NULL);  
    19.                 }  
    20.         }  
    21. }  
    22.   
    23. return 0;  
    由此可见,dai widget和stream widget是通过stream name进行匹配的,所以,我们在定义codec的stream widget时,它们的stream name必须要包含dai的stream name,这样才能让ASoc自动把这两种widget连接在一起,只有把它们连接在一起,ASoc中的播放、录音和停止等事件,才能通过dai widget传递到codec中,使得codec中的widget能根据目前的播放状态,动态地开启或关闭音频路径上所有widget的电源。我们看看wm8993中的例子:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),  
    2. SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),  
    3.   
    4. SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),  
    5. SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),  
    分别定义了左右声道两个stream name为Capture和Playback的stream widget。对应的dai driver结构定义如下:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct snd_soc_dai_driver wm8993_dai = {  
    2.         .name = "wm8993-hifi",  
    3.         .playback = {  
    4.                 .stream_name = "Playback",  
    5.                 .channels_min = 1,  
    6.                 .channels_max = 2,  
    7.                 .rates = WM8993_RATES,  
    8.                 .formats = WM8993_FORMATS,  
    9.                 .sig_bits = 24,  
    10.         },  
    11.         .capture = {  
    12.                  .stream_name = "Capture",  
    13.                  .channels_min = 1,  
    14.                  .channels_max = 2,  
    15.                  .rates = WM8993_RATES,  
    16.                  .formats = WM8993_FORMATS,  
    17.                  .sig_bits = 24,  
    18.          },  
    19.         .ops = &wm8993_ops,  
    20.         .symmetric_rates = 1,  
    21. };  
    可见,它们的stream name是一样的,声卡初始化阶段会把它们连接在一起。需要注意的是,如果我们定义了snd_soc_dapm_aif_in和snd_soc_dapm_aif_out类型的stream widget,并指定了他们的stream name,在定义DAC或ADC对应的widget时,它们的stream name最好不要也使用相同的名字,否则,dai widget即会连接上AIF,也会连接上DAC/ADC,造成音频路径的混乱:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0),  
    2. SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0),  
    3.   
    4. SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0),  
    5. SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0),  

    stream event


    把dai widget和stream widget连接在一起,就是为了能把ASoc中的pcm处理部分和dapm进行关联,pcm的处理过程中,会通过发出stream event来通知dapm系统,重新扫描并调整音频路径上各个widget的电源状态,目前dapm提供了以下几种stream event:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /* dapm stream operations */  
    2. #define SND_SOC_DAPM_STREAM_NOP                 0x0  
    3. #define SND_SOC_DAPM_STREAM_START               0x1  
    4. #define SND_SOC_DAPM_STREAM_STOP                0x2  
    5. #define SND_SOC_DAPM_STREAM_SUSPEND             0x4  
    6. #define SND_SOC_DAPM_STREAM_RESUME              0x8  
    7. #define SND_SOC_DAPM_STREAM_PAUSE_PUSH  0x10  
    8. #define SND_SOC_DAPM_STREAM_PAUSE_RELEASE       0x20  

    比如,在soc_pcm_prepare函数中,会发出SND_SOC_DAPM_STREAM_START事件:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. snd_soc_dapm_stream_event(rtd, substream->stream,  
    2.                 SND_SOC_DAPM_STREAM_START);  

    而在soc_pcm_close函数中,会发出SND_SOC_DAPM_STREAM_STOP事件:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. snd_soc_dapm_stream_event(rtd,  
    2.                           SNDRV_PCM_STREAM_PLAYBACK,  
    3.                           SND_SOC_DAPM_STREAM_STOP);  

    snd_soc_dapm_stream_event函数最终会使用soc_dapm_stream_event函数来完成具体的工作:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,  
    2.         int event)  
    3. {  
    4.   
    5.         struct snd_soc_dapm_widget *w_cpu, *w_codec;  
    6.         struct snd_soc_dai *cpu_dai = rtd->cpu_dai;  
    7.         struct snd_soc_dai *codec_dai = rtd->codec_dai;  
    8.   
    9.         if (stream == SNDRV_PCM_STREAM_PLAYBACK) {  
    10.                 w_cpu = cpu_dai->playback_widget;  
    11.                 w_codec = codec_dai->playback_widget;  
    12.         } else {  
    13.                 w_cpu = cpu_dai->capture_widget;  
    14.                 w_codec = codec_dai->capture_widget;  
    15.         }  

    该函数首先从snd_soc_pcm_runtime结构中取出cpu dai widget和codec dai widget,接下来:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. if (w_cpu) {  
    2.   
    3.         dapm_mark_dirty(w_cpu, "stream event");  
    4.   
    5.         switch (event) {  
    6.         case SND_SOC_DAPM_STREAM_START:  
    7.                 w_cpu->active = 1;  
    8.                 break;  
    9.         case SND_SOC_DAPM_STREAM_STOP:  
    10.                 w_cpu->active = 0;  
    11.                 break;  
    12.         case SND_SOC_DAPM_STREAM_SUSPEND:  
    13.         case SND_SOC_DAPM_STREAM_RESUME:  
    14.         case SND_SOC_DAPM_STREAM_PAUSE_PUSH:  
    15.         case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:  
    16.                 break;  
    17.         }  
    18. }  

    把cpu dai widget加入到dapm_dirty链表中,根据stream event的类型,把cpu dai widget设定为激活状态或非激活状态,接下来,对codec dai widget做出同样的处理:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. if (w_codec) {  
    2.   
    3.         dapm_mark_dirty(w_codec, "stream event");  
    4.   
    5.         switch (event) {  
    6.         case SND_SOC_DAPM_STREAM_START:  
    7.                 w_codec->active = 1;  
    8.                 break;  
    9.         case SND_SOC_DAPM_STREAM_STOP:  
    10.                 w_codec->active = 0;  
    11.                 break;  
    12.         case SND_SOC_DAPM_STREAM_SUSPEND:  
    13.         case SND_SOC_DAPM_STREAM_RESUME:  
    14.         case SND_SOC_DAPM_STREAM_PAUSE_PUSH:  
    15.         case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:  
    16.                 break;  
    17.         }  
    18. }  

    最后,它调用了我们熟悉的dapm_power_widgets函数:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. dapm_power_widgets(rtd->card, event);  

    因为dai widget和codec上的stream widget是相连的,所以,dai widget的激活状态改变,会沿着音频路径传递到路径上的所有widget,等dapm_power_widgets返回后,如果发出的是SND_SOC_DAPM_STREAM_START事件,路径上的所有widget会处于上电状态,保证音频数据流的顺利播放,如果发出的是SND_SOC_DAPM_STREAM_STOP事件,路径上的所有widget会处于下电状态,保证最小的功耗水平。

  • 相关阅读:
    623. Add One Row to Tree 将一行添加到树中
    771. Jewels and Stones 珠宝和石头
    216. Combination Sum III 组合总数三
    384. Shuffle an Array 随机播放一个数组
    382. Linked List Random Node 链接列表随机节点
    向github项目push代码后,Jenkins实现其自动构建
    centos下安装Jenkins
    python提取批量文件内的指定内容
    批处理实现:批量为文件添加注释
    python抓取每期双色球中奖号码,用于分析
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6297642.html
Copyright © 2011-2022 走看看