ALSA聲卡驅動中的DAPM詳解之五:建立widget之間的連接關係

前面我們主要着重於codec、platform、machine驅動程序中如何使用和建立dapm所需要的widget,route,這些是音頻驅動開發人員必須要了解的內容,經過前幾章的介紹,我們應該知道如何在alsa音頻驅動的3大部分(codec、platform、machine)中,按照所使用的音頻硬件結構,定義出相應的widget,kcontrol,以及必要的音頻路徑,而在本章中,我們將會深入dapm的核心部分,看看各個widget之間是如何建立連接關係,形成一條完整的音頻路徑。

/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

前面我們已經簡單地介紹過,驅動程序需要使用以下api函數創建widget:

  • snd_soc_dapm_new_controls
實際上,這個函數只是創建widget的第一步,它爲每個widget分配內存,初始化必要的字段,然後把這些widget掛在代表聲卡的snd_soc_card的widgets鏈表字段中。要使widget之間具備連接能力,我們還需要第二個函數:
  • snd_soc_dapm_new_widgets
這個函數會根據widget的信息,創建widget所需要的dapm kcontrol,這些dapm kcontol的狀態變化,代表着音頻路徑的變化,從而影響着各個widget的電源狀態。看到函數的名稱可能會迷惑一下,實際上,snd_soc_dapm_new_controls的作用更多地是創建widget,而snd_soc_dapm_new_widget的作用則更多地是創建widget所包含的kcontrol,所以在我看來,這兩個函數名稱應該換過來叫更好!下面我們分別介紹一下這兩個函數是如何工作的。

創建widget:snd_soc_dapm_new_controls


snd_soc_dapm_new_controls函數完成widget的創建工作,並把這些創建好的widget註冊在聲卡的widgets鏈表中,我們看看他的定義:
  1. int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,  
  2.         const struct snd_soc_dapm_widget *widget,  
  3.         int num)  
  4. {         
  5.         ......   
  6.         for (i = 0; i < num; i++) {  
  7.                 w = snd_soc_dapm_new_control(dapm, widget);  
  8.                 if (!w) {  
  9.                         dev_err(dapm->dev,  
  10.                                 "ASoC: Failed to create DAPM control %s\n",  
  11.                                 widget->name);  
  12.                         ret = -ENOMEM;  
  13.                         break;  
  14.                 }                 
  15.                 widget++;  
  16.         }  
  17.         ......  
  18.         return ret;  
  19. }  
該函數只是簡單的一個循環,爲傳入的widget模板數組依次調用snd_soc_dapm_new_control函數,實際的工作由snd_soc_dapm_new_control完成,繼續進入該函數,看看它做了那些工作。
我們之前已經說過,驅動中定義的snd_soc_dapm_widget數組,只是作爲一個模板,所以,snd_soc_dapm_new_control所做的第一件事,就是爲該widget重新分配內存,並把模板的內容拷貝過來:

  1. static struct snd_soc_dapm_widget *  
  2. snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,  
  3.                          const struct snd_soc_dapm_widget *widget)  
  4. {  
  5.         struct snd_soc_dapm_widget *w;  
  6.         int ret;  
  7.   
  8.         if ((w = dapm_cnew_widget(widget)) == NULL)  
  9.                 return NULL;  

由dapm_cnew_widget完成內存申請和拷貝模板的動作。接下來,根據widget的類型做不同的處理:
  1.         switch (w->id) {  
  2.         case snd_soc_dapm_regulator_supply:  
  3.                 w->regulator = devm_regulator_get(dapm->dev, w->name);  
  4.                 ......  
  5.   
  6.                 if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {  
  7.                         ret = regulator_allow_bypass(w->regulator, true);  
  8.                         ......  
  9.                 }  
  10.                 break;  
  11.         case snd_soc_dapm_clock_supply:  
  12. #ifdef CONFIG_CLKDEV_LOOKUP  
  13.                 w->clk = devm_clk_get(dapm->dev, w->name);  
  14.                 ......  
  15. #else  
  16.                 return NULL;  
  17. #endif  
  18.                 break;  
  19.         default:  
  20.                 break;  
  21.         }  
對於snd_soc_dapm_regulator_supply類型的widget,根據widget的名稱獲取對應的regulator結構,對於snd_soc_dapm_clock_supply類型的widget,根據widget的名稱,獲取對應的clock結構。接下來,根據需要,在widget的名稱前加入必要的前綴:
  1. if (dapm->codec && dapm->codec->name_prefix)  
  2.         w->name = kasprintf(GFP_KERNEL, "%s %s",  
  3.                 dapm->codec->name_prefix, widget->name);  
  4. else  
  5.         w->name = kasprintf(GFP_KERNEL, "%s", widget->name);  
然後,爲不同類型的widget設置合適的power_check電源狀態回調函數,widget類型和對應的power_check回調函數設置如下表所示:
widget的power_check回調函數
widget類型 power_check回調函數
mixer類:
snd_soc_dapm_switch
snd_soc_dapm_mixer
snd_soc_dapm_mixer_named_ctl
dapm_generic_check_power
mux類:
snd_soc_dapm_mux
snd_soc_dapm_mux
snd_soc_dapm_mux
dapm_generic_check_power
snd_soc_dapm_dai_out dapm_adc_check_power
snd_soc_dapm_dai_in dapm_dac_check_power
端點類:
snd_soc_dapm_adc
snd_soc_dapm_aif_out
snd_soc_dapm_dac
snd_soc_dapm_aif_in
snd_soc_dapm_pga
snd_soc_dapm_out_drv
snd_soc_dapm_input
snd_soc_dapm_output
snd_soc_dapm_micbias
snd_soc_dapm_spk
snd_soc_dapm_hp
snd_soc_dapm_mic
snd_soc_dapm_line
snd_soc_dapm_dai_link
dapm_generic_check_power
電源/時鐘/影子widget:
snd_soc_dapm_supply
snd_soc_dapm_regulator_supply
snd_soc_dapm_clock_supply
snd_soc_dapm_kcontrol
dapm_supply_check_power
其它類型 dapm_always_on_check_power
當音頻路徑發生變化時,power_check回調會被調用,用於檢查該widget的電源狀態是否需要更新。power_check設置完成後,需要設置widget所屬的codec、platform和dapm context,幾個用於音頻路徑的鏈表也需要初始化,然後,把該widget加入到聲卡的widgets鏈表中:
  1. w->dapm = dapm;  
  2. w->codec = dapm->codec;  
  3. w->platform = dapm->platform;  
  4. INIT_LIST_HEAD(&w->sources);  
  5. INIT_LIST_HEAD(&w->sinks);  
  6. INIT_LIST_HEAD(&w->list);  
  7. INIT_LIST_HEAD(&w->dirty);  
  8. list_add(&w->list, &dapm->card->widgets);  
幾個鏈表的作用如下:
  • sources    用於鏈接所有連接到該widget輸入端的snd_soc_path結構
  • sinks    用於鏈接所有連接到該widget輸出端的snd_soc_path結構
  • list    用於鏈接到聲卡的widgets鏈表
  • dirty    用於鏈接到聲卡的dapm_dirty鏈表
最後,把widget設置爲connect狀態:
  1. /* machine layer set ups unconnected pins and insertions */  
  2. w->connected = 1;  
  3. return w;  
connected字段代表着引腳的連接狀態,目前,只有以下這些widget使用connected字段:
  • snd_soc_dapm_output
  • snd_soc_dapm_input
  • snd_soc_dapm_hp
  • snd_soc_dapm_spk
  • snd_soc_dapm_line
  • snd_soc_dapm_vmid
  • snd_soc_dapm_mic
  • snd_soc_dapm_siggen
驅動程序可以使用以下這些api來設置引腳的連接狀態:
  • snd_soc_dapm_enable_pin
  • snd_soc_dapm_force_enable_pin
  • snd_soc_dapm_disable_pin
  • snd_soc_dapm_nc_pin
到此,widget已經被正確地創建並初始化,而且被掛在聲卡的widgets鏈表中,以後我們就可以通過聲卡的widgets鏈表來遍歷所有的widget,再次強調一下snd_soc_dapm_new_controls函數所完成的主要功能:
  • 爲widget分配內存,並拷貝參數中傳入的在驅動中定義好的模板
  • 設置power_check回調函數
  • 把widget掛在聲卡的widgets鏈表中

爲widget建立dapm kcontrol


定義一個widget,我們需要指定兩個很重要的內容:一個是用於控制widget的電源狀態的reg/shift等寄存器信息,另一個是用於控制音頻路徑切換的dapm kcontrol信息,這些dapm kcontrol有它們自己的reg/shift寄存器信息用於切換widget的路徑連接方式。前一節的內容中,我們只是創建了widget的實例,並把它們註冊到聲卡的widgts鏈表中,但是到目前爲止,包含在widget中的dapm kcontrol並沒有建立起來,dapm框架在聲卡的初始化階段,等所有的widget(包括machine、platform、codec)都創建好之後,通過snd_soc_dapm_new_widgets函數,創建widget內包含的dapm kcontrol,並初始化widget的初始電源狀態和音頻路徑的初始連接狀態。我們看看聲卡的初始化函數,都有那些初始化與dapm有關:
  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.    
  9.         card->dapm.bias_level = SND_SOC_BIAS_OFF;  
  10.         card->dapm.dev = card->dev;  
  11.         card->dapm.card = card;  
  12.         list_add(&card->dapm.list, &card->dapm_list);  
  13.   
  14. #ifdef CONFIG_DEBUG_FS  
  15.         snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);  
  16. #endif  
  17.         ......  
  18.         if (card->dapm_widgets)    /* 創建machine級別的widget  */  
  19.                 snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,  
  20.                                           card->num_dapm_widgets);  
  21.         ......  
  22.         snd_soc_dapm_link_dai_widgets(card);  /*  連接dai widget  */  
  23.   
  24.         if (card->controls)    /*  建立machine級別的普通kcontrol控件  */  
  25.                 snd_soc_add_card_controls(card, card->controls, card->num_controls);  
  26.   
  27.         if (card->dapm_routes)    /*  註冊machine級別的路徑連接信息  */  
  28.                 snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,  
  29.                                         card->num_dapm_routes);  
  30.         ......  
  31.   
  32.         if (card->fully_routed)    /*  如果該標誌被置位,自動把codec中沒有路徑連接信息的引腳設置爲無用widget  */  
  33.                 list_for_each_entry(codec, &card->codec_dev_list, card_list)  
  34.                         snd_soc_dapm_auto_nc_codec_pins(codec);  
  35.   
  36.         snd_soc_dapm_new_widgets(card);    /*初始化widget包含的dapm kcontrol、電源狀態和連接狀態*/  
  37.   
  38.         ret = snd_card_register(card->snd_card);  
  39.         ......  
  40.         card->instantiated = 1;  
  41.         snd_soc_dapm_sync(&card->dapm);  
  42.         ......  
  43.         return 0;  
  44. }   
正如我添加的註釋中所示,在完成machine級別的widget和route處理之後,調用的snd_soc_dapm_new_widgets函數,來爲所有已經註冊的widget初始化他們所包含的dapm kcontrol,並初始化widget的電源狀態和路徑連接狀態。下面我們看看snd_soc_dapm_new_widgets函數的工作過程。

snd_soc_dapm_new_widgets函數    

該函數通過聲卡的widgets鏈表,遍歷所有已經註冊了的widget,其中的new字段用於判斷該widget是否已經執行過snd_soc_dapm_new_widgets函數,如果num_kcontrols字段有數值,表明該widget包含有若干個dapm kcontrol,那麼就需要爲這些kcontrol分配一個指針數組,並把數組的首地址賦值給widget的kcontrols字段,該數組存放着指向這些kcontrol的指針,當然現在這些都是空指針,因爲實際的kcontrol現在還沒有被創建:
  1. int snd_soc_dapm_new_widgets(struct snd_soc_card *card)  
  2. {  
  3.         ......  
  4.         list_for_each_entry(w, &card->widgets, list)  
  5.         {                 
  6.                 if (w->new)       
  7.                         continue;  
  8.                                   
  9.                 if (w->num_kcontrols) {  
  10.                         w->kcontrols = kzalloc(w->num_kcontrols *  
  11.                                                 sizeof(struct snd_kcontrol *),  
  12.                                                 GFP_KERNEL);  
  13.                         ......  
  14.                 }  
接着,對幾種能影響音頻路徑的widget,創建並初始化它們所包含的dapm kcontrol:
  1. switch(w->id) {  
  2. case snd_soc_dapm_switch:  
  3. case snd_soc_dapm_mixer:  
  4. case snd_soc_dapm_mixer_named_ctl:  
  5.         dapm_new_mixer(w);  
  6.         break;  
  7. case snd_soc_dapm_mux:  
  8. case snd_soc_dapm_virt_mux:  
  9. case snd_soc_dapm_value_mux:  
  10.         dapm_new_mux(w);  
  11.         break;  
  12. case snd_soc_dapm_pga:  
  13. case snd_soc_dapm_out_drv:  
  14.         dapm_new_pga(w);  
  15.         break;  
  16. default:  
  17.         break;  
  18. }  
需要用到的創建函數分別是:
  • dapm_new_mixer()    對於mixer類型,用該函數創建dapm kcontrol;
  • dapm_new_mux()   對於mux類型,用該函數創建dapm kcontrol;
  • dapm_new_pga()   對於pga類型,用該函數創建dapm kcontrol;
然後,根據widget寄存器的當前值,初始化widget的電源狀態,並設置到power字段中:
  1. /* Read the initial power state from the device */  
  2. if (w->reg >= 0) {  
  3.         val = soc_widget_read(w, w->reg) >> w->shift;  
  4.         val &= w->mask;  
  5.         if (val == w->on_val)  
  6.                 w->power = 1;  
  7. }  
接着,設置new字段,表明該widget已經初始化完成,我們還要吧該widget加入到聲卡的dapm_dirty鏈表中,表明該widget的狀態發生了變化,稍後在合適的時刻,dapm框架會掃描dapm_dirty鏈表,統一處理所有已經變化的widget。爲什麼要統一處理?因爲dapm要控制各種widget的上下電順序,同時也是爲了減少寄存器的讀寫次數(多個widget可能使用同一個寄存器):
  1. w->new = 1;  
  2.   
  3. dapm_mark_dirty(w, "new widget");  
  4. dapm_debugfs_add_widget(w);  

最後,通過dapm_power_widgets函數,統一處理所有位於dapm_dirty鏈表上的widget的狀態改變:
  1. dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);  
  2. ......  
  3. return 0;  

dapm mixer kcontrol

上一節中,我們提到,對於mixer類型的dapm kcontrol,我們會使用dapm_new_mixer來完成具體的創建工作,先看代碼後分析:
  1. static int dapm_new_mixer(struct snd_soc_dapm_widget *w)  
  2. {  
  3.         int i, ret;  
  4.         struct snd_soc_dapm_path *path;  
  5.   
  6.         /* add kcontrol */  
  7. <span style="font-family:Arial,Helvetica,sans-serif">(1)</span>        for (i = 0; i < w->num_kcontrols; i++) {                                  
  8.                 /* match name */  
  9. (2)                list_for_each_entry(path, &w->sources, list_sink) {               
  10.                         /* mixer/mux paths name must match control name */  
  11. (3)                        if (path->name != (char *)w->kcontrol_news[i].name)       
  12.                                 continue;  
  13.   
  14. (4)                        if (w->kcontrols[i]) {                                   
  15.                                 dapm_kcontrol_add_path(w->kcontrols[i], path);  
  16.                                 continue;  
  17.                         }  
  18.   
  19. (5)                        ret = dapm_create_or_share_mixmux_kcontrol(w, i);        
  20.                         if (ret < 0)  
  21.                                 return ret;  
  22.   
  23. (6)                        dapm_kcontrol_add_path(w->kcontrols[i], path);           
  24.                 }  
  25.         }  
  26.   
  27.         return 0;  
  28. }  
(1)  因爲一個mixer是由多個kcontrol組成的,每個kcontrol控制着mixer的一個輸入端的開啓和關閉,所以,該函數會根據kcontrol的數量做循環,逐個建立對應的kcontrol。
(2)(3)  之前多次提到,widget之間使用snd_soc_path進行連接,widget的sources鏈表保存着所有和輸入端連接的snd_soc_path結構,所以我們可以用kcontrol模板中指定的名字來匹配對應的snd_soc_path結構。
(4)  因爲一個輸入腳可能會連接多個輸入源,所以可能在上一個輸入源的path關聯時已經創建了這個kcontrol,所以這裏判斷kcontrols指針數組中對應索引中的指針值,如果已經賦值,說明kcontrol已經在之前創建好了,所以我們只要簡單地把連接該輸入端的path加入到kcontrol的path_list鏈表中,並且增加一個虛擬的影子widget,該影子widget連接和輸入端對應的源widget,因爲使用了kcontrol本身的reg/shift等寄存器信息,所以實際上控制的是該kcontrol的開和關,這個影子widget只有在kcontrol的autodisable字段被設置的情況下才會被創建,該特性使得source的關閉時,與之連接的mixer的輸入端也可以自動關閉,這個特性通過dapm_kcontrol_add_path來實現這一點:
  1. static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,  
  2.         struct snd_soc_dapm_path *path)  
  3. {  
  4.         struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);  
  5.         /*  把kcontrol連接的path加入到paths鏈表中  */  
  6.         /*  paths鏈表所在的dapm_kcontrol_data結構會保存在kcontrol的private_data字段中  */  
  7.         list_add_tail(&path->list_kcontrol, &data->paths);  
  8.   
  9.         if (data->widget) {  
  10.                 snd_soc_dapm_add_path(data->widget->dapm, data->widget,  
  11.                     path->source, NULL, NULL);  
  12.         }  
  13. }  

(5)  如果kcontrol之前沒有被創建,則通過dapm_create_or_share_mixmux_kcontrol創建這個輸入端的kcontrol,同理,kcontrol對應的影子widget也會通過dapm_kcontrol_add_path判斷是否需要創建。

dapm mux kcontrol

因爲一個widget最多隻會包含一個mux類型的damp kcontrol,所以他的創建方法稍有不同,dapm框架使用dapm_new_mux函數來創建mux類型的dapm kcontrol:
  1. static int dapm_new_mux(struct snd_soc_dapm_widget *w)  
  2. {         
  3.         struct snd_soc_dapm_context *dapm = w->dapm;  
  4.         struct snd_soc_dapm_path *path;  
  5.         int ret;  
  6.           
  7. (1)     if (w->num_kcontrols != 1) {  
  8.                 dev_err(dapm->dev,  
  9.                         "ASoC: mux %s has incorrect number of controls\n",  
  10.                         w->name);  
  11.                 return -EINVAL;  
  12.         }  
  13.   
  14.         if (list_empty(&w->sources)) {  
  15.                 dev_err(dapm->dev, "ASoC: mux %s has no paths\n", w->name);  
  16.                 return -EINVAL;  
  17.         }  
  18.   
  19. (2)     ret = dapm_create_or_share_mixmux_kcontrol(w, 0);  
  20.         if (ret < 0)  
  21.                 return ret;  
  22. (3)       list_for_each_entry(path, &w->sources, list_sink)  
  23.                 dapm_kcontrol_add_path(w->kcontrols[0], path);  
  24.         return 0;  
  25. }  


(1)  對於mux類型的widget,因爲只會有一個kcontrol,所以在這裏做一下判斷。
(2)  同樣地,和mixer類型一樣,也使用dapm_create_or_share_mixmux_kcontrol來創建這個kcontrol。
(3)  對每個輸入端所連接的path都加入dapm_kcontrol_data結構的paths鏈表中,並且創建一個影子widget,用於支持autodisable特性。

dapm pga kcontrol

目前對於pga類型的widget,kcontrol的創建函數是個空函數,所以我們不用太關注它:
  1. static int dapm_new_pga(struct snd_soc_dapm_widget *w)  
  2. {  
  3.         if (w->num_kcontrols)  
  4.                 dev_err(w->dapm->dev,  
  5.                         "ASoC: PGA controls not supported: '%s'\n", w->name);  
  6.   
  7.         return 0;  
  8. }  

dapm_create_or_share_mixmux_kcontrol函數

上面所說的mixer類型和mux類型的widget,在創建他們所包含的dapm kcontrol時,最後其實都是使用了dapm_create_or_share_mixmux_kcontrol函數來完成創建工作的,所以在這裏我們有必要分析一下這個函數的工作原理。這個函數中有很大一部分代碼實在處理kcontrol的名字是否要加入codec的前綴,我們會忽略這部分的代碼,感興趣的讀者可以自己查看內核的代碼,路徑在:sound/soc/soc-dapm.c中,簡化後的代碼如下:
  1. static int dapm_create_or_share_mixmux_kcontrol(struct snd_soc_dapm_widget *w,  
  2.         int kci)  
  3. {  
  4.           ......  
  5. (1)       shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],  
  6.                                          &kcontrol);  
  7.      
  8. (2)       if (!kcontrol) {  
  9. (3)            kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,prefix);  
  10.                ......  
  11.                kcontrol->private_free = dapm_kcontrol_free;  
  12. (4)            ret = dapm_kcontrol_data_alloc(w, kcontrol);  
  13.                 ......  
  14. (5)            ret = snd_ctl_add(card, kcontrol);  
  15.                 ......  
  16.         }  
  17. (6)     ret = dapm_kcontrol_add_widget(kcontrol, w);  
  18.         ......  
  19. (7)     w->kcontrols[kci] = kcontrol;  
  20.         return 0;  
  21. }  

(1)  爲了節省內存,通過kcontrol名字的匹配查找,如果這個kcontrol已經在其他widget中已經創建好了,那我們不再創建,dapm_is_shared_kcontrol的參數kcontrol會返回已經創建好的kcontrol的指針。
(2)  如果kcontrol指針被賦值,說明在(1)中查找到了其他widget中同名的kcontrol,我們不用再次創建,只要共享該kcontrol即可。
(3)  標準的kcontrol創建函數,請參看:Linux ALSA聲卡驅動之四:Control設備的創建中的“創建control“一節的內容。
(4)  如果widget支持autodisable特性,創建與該kcontrol所對應的影子widget,該影子widget的類型是:snd_soc_dapm_kcontrol。
(5)  標準的kcontrol創建函數,請參看:Linux ALSA聲卡驅動之四:Control設備的創建中的“創建control“一節的內容。
(6)  把所有共享該kcontrol的影子widget(snd_soc_dapm_kcontrol),加入到kcontrol的private_data字段所指向的dapm_kcontrol_data結構中。
(7)  把創建好的kcontrol指針賦值到widget的kcontrols數組中。
需要注意的是,如果kcontol支持autodisable特性,一旦kcontrol由於source的關閉而被自動關閉,則用戶空間只能操作該kcontrol的cache值,只有該kcontrol再次打開時,該cache值纔會被真正地更新到寄存器中。
現在。我們總結一下,創建一個widget所包含的kcontrol所做的工作:
  • 循環每一個輸入端,爲每個輸入端依次執行下面的一系列操作
  • 爲每個輸入端創建一個kcontrol,能共享的則直接使用創建好的kcontrol
  • kcontrol的private_data字段保存着這些共享widget的信息
  • 如果支持autodisable特性,每個輸入端還要額外地創建一個虛擬的snd_soc_dapm_kcontrol類型的影子widget,該影子widget也記錄在private_data字段中
  • 創建好的kcontrol會依次存放在widget的kcontrols數組中,供路徑的控制和匹配之用。

爲widget建立連接關係


如果widget之間沒有連接關係,dapm就無法實現動態的電源管理工作,正是widget之間有了連結關係,這些連接關係形成了一條所謂的完成的音頻路徑,dapm可以順着這條路徑,統一控制路徑上所有widget的電源狀態,前面我們已經知道,widget之間是使用snd_soc_path結構進行連接的,驅動要做的是定義一個snd_soc_route結構數組,該數組的每個條目描述了目的widget的和源widget的名稱,以及控制這個連接的kcontrol的名稱,最終,驅動程序使用api函數snd_soc_dapm_add_routes來註冊這些連接信息,接下來我們就是要分析該函數的具體實現方式:
  1. int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,  
  2.                             const struct snd_soc_dapm_route *route, int num)  
  3. {  
  4.         int i, r, ret = 0;  
  5.   
  6.         mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);  
  7.         for (i = 0; i < num; i++) {  
  8.                 r = snd_soc_dapm_add_route(dapm, route);  
  9.                 ......  
  10.                 route++;  
  11.         }  
  12.         mutex_unlock(&dapm->card->dapm_mutex);  
  13.   
  14.         return ret;  
  15. }  
該函數只是一個循環,依次對參數傳入的數組調用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我們進入snd_soc_dapm_add_route函數看看:
  1. static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,  
  2.                                   const struct snd_soc_dapm_route *route)  
  3. {  
  4.         struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;  
  5.         struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;  
  6.         const char *sink;  
  7.         const char *source;  
  8.         ......  
  9.         list_for_each_entry(w, &dapm->card->widgets, list) {  
  10.                 if (!wsink && !(strcmp(w->name, sink))) {  
  11.                         wtsink = w;  
  12.                         if (w->dapm == dapm)  
  13.                                 wsink = w;  
  14.                         continue;  
  15.                 }  
  16.                 if (!wsource && !(strcmp(w->name, source))) {  
  17.                         wtsource = w;  
  18.                         if (w->dapm == dapm)  
  19.                                 wsource = w;  
  20.                 }  
  21.         }  
上面的代碼我再次省略了關於名稱前綴的處理部分。我們可以看到,用widget的名字來比較,遍歷聲卡的widgets鏈表,找出源widget和目的widget的指針,這段代碼雖然正確,但我總感覺少了一個判斷退出循環的條件,如果鏈表的開頭就找到了兩個widget,還是要遍歷整個鏈表才結束循環,好浪費時間。
下面,如果在本dapm context中沒有找到,則使用別的dapm context中找到的widget:
  1. if (!wsink)  
  2.         wsink = wtsink;  
  3. if (!wsource)  
  4.         wsource = wtsource;  
最後,使用來增加一條連接信息:
  1.         ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,  
  2.                 route->connected);  
  3.         ......  
  4.   
  5.         return 0;  
  6. }  
snd_soc_dapm_add_path函數是整個調用鏈條中的關鍵,我們來分析一下:
  1. static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,  
  2.         struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,  
  3.         const char *control,  
  4.         int (*connected)(struct snd_soc_dapm_widget *source,  
  5.                          struct snd_soc_dapm_widget *sink))  
  6. {  
  7.         struct snd_soc_dapm_path *path;  
  8.         int ret;  
  9.   
  10.         path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);  
  11.         if (!path)  
  12.                 return -ENOMEM;  
  13.   
  14.         path->source = wsource;  
  15.         path->sink = wsink;  
  16.         path->connected = connected;  
  17.         INIT_LIST_HEAD(&path->list);  
  18.         INIT_LIST_HEAD(&path->list_kcontrol);  
  19.         INIT_LIST_HEAD(&path->list_source);  
  20.         INIT_LIST_HEAD(&path->list_sink);  
函數的一開始,首先爲這個連接分配了一個snd_soc_path結構,path的source和sink字段分別指向源widget和目的widget,connected字段保存connected回調函數,初始化幾個snd_soc_path結構中的幾個鏈表。
  1. /* check for external widgets */  
  2.         if (wsink->id == snd_soc_dapm_input) {  
  3.                 if (wsource->id == snd_soc_dapm_micbias ||  
  4.                         wsource->id == snd_soc_dapm_mic ||  
  5.                         wsource->id == snd_soc_dapm_line ||  
  6.                         wsource->id == snd_soc_dapm_output)  
  7.                         wsink->ext = 1;  
  8.         }  
  9.         if (wsource->id == snd_soc_dapm_output) {  
  10.                 if (wsink->id == snd_soc_dapm_spk ||  
  11.                         wsink->id == snd_soc_dapm_hp ||  
  12.                         wsink->id == snd_soc_dapm_line ||  
  13.                         wsink->id == snd_soc_dapm_input)  
  14.                         wsource->ext = 1;  
  15.         }  
這段代碼用於判斷是否有外部連接關係,如果有,置位widget的ext字段。判斷方法從代碼中可以方便地看出:
  • 目的widget是一個輸入腳,如果源widget是mic、line、micbias或output,則認爲目的widget具有外部連接關係。
  • 源widget是一個輸出腳,如果目的widget是spk、hp、line或input,則認爲源widget具有外部連接關係。
  1. dapm_mark_dirty(wsource, "Route added");  
  2. dapm_mark_dirty(wsink, "Route added");  
  3.   
  4. /* connect static paths */  
  5. if (control == NULL) {  
  6.         list_add(&path->list, &dapm->card->paths);  
  7.         list_add(&path->list_sink, &wsink->sources);  
  8.         list_add(&path->list_source, &wsource->sinks);  
  9.         path->connect = 1;  
  10.         return 0;  
  11. }  
因爲增加了連結關係,所以把源widget和目的widget加入到dapm_dirty鏈表中。如果沒有kcontrol來控制該連接關係,則這是一個靜態連接,直接用path把它們連接在一起。在接着往下看:
  1. /* connect dynamic paths */  
  2. switch (wsink->id) {  
  3. case snd_soc_dapm_adc:  
  4. case snd_soc_dapm_dac:  
  5. case snd_soc_dapm_pga:  
  6. case snd_soc_dapm_out_drv:  
  7. case snd_soc_dapm_input:  
  8. case snd_soc_dapm_output:  
  9. case snd_soc_dapm_siggen:  
  10. case snd_soc_dapm_micbias:  
  11. case snd_soc_dapm_vmid:  
  12. case snd_soc_dapm_pre:  
  13. case snd_soc_dapm_post:  
  14. case snd_soc_dapm_supply:  
  15. case snd_soc_dapm_regulator_supply:  
  16. case snd_soc_dapm_clock_supply:  
  17. case snd_soc_dapm_aif_in:  
  18. case snd_soc_dapm_aif_out:  
  19. case snd_soc_dapm_dai_in:  
  20. case snd_soc_dapm_dai_out:  
  21. case snd_soc_dapm_dai_link:  
  22. case snd_soc_dapm_kcontrol:  
  23.         list_add(&path->list, &dapm->card->paths);  
  24.         list_add(&path->list_sink, &wsink->sources);  
  25.         list_add(&path->list_source, &wsource->sinks);  
  26.         path->connect = 1;  
  27.         return 0;  
按照目的widget來判斷,如果屬於以上這些類型,直接把它們連接在一起即可,這段感覺有點多餘,因爲通常以上這些類型的widget本來也沒有kcontrol,直接用上一段代碼就可以了,也許是dapm的作者們想着以後可能會有所擴展吧。
  1. case snd_soc_dapm_mux:  
  2. case snd_soc_dapm_virt_mux:  
  3. case snd_soc_dapm_value_mux:  
  4.         ret = dapm_connect_mux(dapm, wsource, wsink, path, control,  
  5.                 &wsink->kcontrol_news[0]);  
  6.         if (ret != 0)  
  7.                 goto err;  
  8.         break;  
  9. case snd_soc_dapm_switch:  
  10. case snd_soc_dapm_mixer:  
  11. case snd_soc_dapm_mixer_named_ctl:  
  12.         ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);  
  13.         if (ret != 0)  
  14.                 goto err;  
  15.         break;  
目的widget如果是mixer和mux類型,分別用dapm_connect_mixer和dapm_connect_mux函數完成連接工作,這兩個函數我們後面再講。
  1.         case snd_soc_dapm_hp:  
  2.         case snd_soc_dapm_mic:  
  3.         case snd_soc_dapm_line:  
  4.         case snd_soc_dapm_spk:  
  5.                 list_add(&path->list, &dapm->card->paths);  
  6.                 list_add(&path->list_sink, &wsink->sources);  
  7.                 list_add(&path->list_source, &wsource->sinks);  
  8.                 path->connect = 0;  
  9.                 return 0;  
  10.         }  
  11.   
  12.         return 0;  
  13. err:  
  14.         kfree(path);  
  15.         return ret;  
  16. }  
hp、mic、line和spk這幾種widget屬於外部器件,也只是簡單地連接在一起,不過connect字段默認爲是未連接狀態。
現在,我們回過頭來看看目的widget是mixer和mux這兩種類型時的連接方式:
dapm_connect_mixer  用該函數連接一個目的widget爲mixer類型的所有輸入端:
  1. static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,  
  2.         struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,  
  3.         struct snd_soc_dapm_path *path, const char *control_name)  
  4. {  
  5.         int i;  
  6.   
  7.         /* search for mixer kcontrol */  
  8.         for (i = 0; i < dest->num_kcontrols; i++) {  
  9.                 if (!strcmp(control_name, dest->kcontrol_news[i].name)) {  
  10.                         list_add(&path->list, &dapm->card->paths);  
  11.                         list_add(&path->list_sink, &dest->sources);  
  12.                         list_add(&path->list_source, &src->sinks);  
  13.                         path->name = dest->kcontrol_news[i].name;  
  14.                         dapm_set_path_status(dest, path, i);  
  15.                         return 0;  
  16.                 }  
  17.         }  
  18.         return -ENODEV;  
  19. }  
用需要用來連接的kcontrol的名字,和目的widget中的kcontrol模板數組中的名字相比較,找出該kcontrol在widget中的編號,path的名字設置爲該kcontrol的名字,然後用dapm_set_path_status函數來初始化該輸入端的連接狀態。連接兩個widget的鏈表操作和其他widget是一樣的。

dapm_connect_mux 用該函數連接一個目的widget是mux類型的所有輸入端:
  1. static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,  
  2.         struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,  
  3.         struct snd_soc_dapm_path *path, const char *control_name,  
  4.         const struct snd_kcontrol_new *kcontrol)  
  5. {  
  6.         struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;  
  7.         int i;  
  8.   
  9.         for (i = 0; i < e->max; i++) {  
  10.                 if (!(strcmp(control_name, e->texts[i]))) {  
  11.                         list_add(&path->list, &dapm->card->paths);  
  12.                         list_add(&path->list_sink, &dest->sources);  
  13.                         list_add(&path->list_source, &src->sinks);  
  14.                         path->name = (char*)e->texts[i];  
  15.                         dapm_set_path_status(dest, path, 0);  
  16.                         return 0;  
  17.                 }  
  18.         }  
  19.   
  20.         return -ENODEV;  
  21. }  
和mixer類型一樣用名字進行匹配,只不過mux類型的kcontrol只需一個,所以要通過private_value字段所指向的soc_enum結構找出匹配的輸入腳編號,最後也是通過dapm_set_path_status函數來初始化該輸入端的連接狀態,因爲只有一個kcontrol,所以第三個參數是0。連接兩個widget的鏈表操作和其他widget也是一樣的。
dapm_set_path_status    該函數根據傳入widget中的kcontrol編號,讀取實際寄存器的值,根據寄存器的值來初始化這個path是否處於連接狀態,詳細的代碼這裏就不貼了。
當widget之間通過path進行連接之後,他們之間的關係就如下圖所示:
widget通過path連接



到這裏爲止,我們爲聲卡創建並初始化好了所需的widget,各個widget也通過path連接在了一起,接下來,dapm等待用戶的指令,一旦某個dapm kcontrol被用戶空間改變,利用這些連接關係,dapm會重新創建音頻路徑,脫離音頻路徑的widget會被下電,加入音頻路徑的widget會被上電,所有的上下電動作都會自動完成,用戶空間的應用程序無需關注這些變化,它只管按需要改變某個dapm kcontrol即可
發佈了9 篇原創文章 · 獲贊 10 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章