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來控制它的電源狀態:
  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事件:

  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事件:
  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事件:
  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函數來建立他們之間的連接關係:
  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結構指針:
  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中:
  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相連,所以跳過它們:
  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:
  1. if (!w->sname || !strstr(w->sname, dai_w->name))  
  2.         continue;  
如果widget的stream name包含了dai的stream name,則匹配成功,連接這兩個widget:
  1.                 if (dai->driver->playback.stream_name &&  
  2.                     strstr(w->sname,  
  3.                            dai->driver->playback.stream_name)) {  
  4.                         dev_dbg(dai->dev, "%s -> %s\n",  
  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\n",  
  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中的例子:
  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結構定義如下:
  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,造成音頻路徑的混亂:
  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:

  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事件:
  1. snd_soc_dapm_stream_event(rtd, substream->stream,  
  2.                 SND_SOC_DAPM_STREAM_START);  
而在soc_pcm_close函數中,會發出SND_SOC_DAPM_STREAM_STOP事件:
  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函數來完成具體的工作:
  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,接下來:
  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做出同樣的處理:
  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函數:
  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會處於下電狀態,保證最小的功耗水平。
發佈了3 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章