• 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会处于下电状态,保证最小的功耗水平。

  • 相关阅读:
    sqlserver监控体系
    使SQL用户只能看到自己拥有权限的库
    存储过程版本控制-DDL触发器
    查看剩余执行时间
    迁移数据库文件位置
    sublime使用Package Control不能正常使用的解决办法
    未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序的处理方式
    1770Special Experiment
    1848Tree
    1322Chocolate
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6297642.html
Copyright © 2020-2023  润新知