ALSA聲卡驅動中的DAPM詳解之四:在驅動程序中初始化並註冊widget和route

前幾篇文章我們從dapm的數據結構入手,瞭解了代表音頻控件的widget,代表連接路徑的route以及用於連接兩個widget的path。之前都是一些概念的講解以及對數據結構中各個字段的說明,從本章開始,我們要從代碼入手,分析dapm的詳細工作原理:

  • 如何註冊widget
  • 如何連接兩個widget
  • 一個widget的狀態裱畫如何傳遞到整個音頻路徑中
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

dapm context


在討論widget的註冊之前,我們先了解另一個概念:dapm context,直譯過來的意思是dapm上下文,這個好像不好理解,其實我們可以這麼理解:dapm把整個音頻系統,按照功能和偏置電壓級別,劃分爲若干個電源域,每個域包含各自的widget,每個域中的所有widget通常都處於同一個偏置電壓級別上,而一個電源域就是一個dapm context,通常會有以下幾種dapm context:
  • 屬於codec中的widget位於一個dapm context中
  • 屬於platform的widget位於一個dapm context中
  • 屬於整個聲卡的widget位於一個dapm context中
對於音頻系統的硬件來說,通常要提供合適的偏置電壓才能正常地工作,有了dapm context這種組織方式,我們可以方便地對同一組widget進行統一的偏置電壓管理,ASoc用snd_soc_dapm_context結構來表示一個dapm context:
[cpp] view plain copy
  1. struct snd_soc_dapm_context {  
  2.         enum snd_soc_bias_level bias_level;  
  3.         enum snd_soc_bias_level suspend_bias_level;  
  4.         struct delayed_work delayed_work;  
  5.         unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */  
  6.   
  7.         struct snd_soc_dapm_update *update;  
  8.   
  9.         void (*seq_notifier)(struct snd_soc_dapm_context *,  
  10.                              enum snd_soc_dapm_type, int);  
  11.   
  12.         struct device *dev; /* from parent - for debug */  
  13.         struct snd_soc_codec *codec; /* parent codec */  
  14.         struct snd_soc_platform *platform; /* parent platform */  
  15.         struct snd_soc_card *card; /* parent card */  
  16.   
  17.         /* used during DAPM updates */  
  18.         enum snd_soc_bias_level target_bias_level;  
  19.         struct list_head list;  
  20.   
  21.         int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);  
  22.   
  23. #ifdef CONFIG_DEBUG_FS  
  24.         struct dentry *debugfs_dapm;  
  25. #endif  
  26. };  
snd_soc_bias_level的取值範圍是以下幾種:
  • SND_SOC_BIAS_OFF
  • SND_SOC_BIAS_STANDBY
  • SND_SOC_BIAS_PREPARE
  • SND_SOC_BIAS_ON
snd_soc_dapm_context被內嵌到代表codec、platform、card、dai的結構體中:
[cpp] view plain copy
  1. struct snd_soc_codec {  
  2.         ......  
  3.         /* dapm */  
  4.         struct snd_soc_dapm_context dapm;  
  5.         ......  
  6. };  
  7.   
  8. struct snd_soc_platform {  
  9.         ......  
  10.         /* dapm */  
  11.         struct snd_soc_dapm_context dapm;  
  12.         ......  
  13. };  
  14.   
  15. struct snd_soc_card {  
  16.         ......  
  17.         /* dapm */  
  18.         struct snd_soc_dapm_context dapm;  
  19.         ......  
  20. };  
  21. :  
  22. struct snd_soc_dai {  
  23.         ......  
  24.         /* dapm */  
  25.         struct snd_soc_dapm_widget *playback_widget;  
  26.         struct snd_soc_dapm_widget *capture_widget;  
  27.         struct snd_soc_dapm_context dapm;  
  28.         ......  
  29. };  
代表widget結構snd_soc_dapm_widget中,有一個snd_soc_dapm_context結構指針,指向所屬的codec、platform、card、或dai的dapm結構。同時,所有的dapm結構,通過它的list字段,鏈接到代表聲卡的snd_soc_card結構的dapm_list鏈表頭字段。

創建和註冊widget


我們已經知道,一個widget用snd_soc_dapm_widget結構體來描述,通常,我們會根據音頻硬件的組成,分別在聲卡的codec驅動、platform驅動和machine驅動中定義一組widget,這些widget用數組進行組織,我們一般會使用dapm框架提供的大量的輔助宏來定義這些widget數組,輔助宏的說明請參考前一偏文章:ALSA聲卡驅動中的DAPM詳解之三:如何定義各種widget

codec驅動中註冊    我們知道,我們會通過ASoc提供的api函數snd_soc_register_codec來註冊一個codec驅動,該函數的第二個參數是一個snd_soc_codec_driver結構指針,這個snd_soc_codec_driver結構需要我們在codec驅動中顯式地進行定義,其中有幾個與dapm框架有關的字段:

[cpp] view plain copy
  1. struct snd_soc_codec_driver {  
  2.         ......          
  3.         /* Default control and setup, added after probe() is run */  
  4.         const struct snd_kcontrol_new *controls;  
  5.         int num_controls;  
  6.         const struct snd_soc_dapm_widget *dapm_widgets;  
  7.         int num_dapm_widgets;  
  8.         const struct snd_soc_dapm_route *dapm_routes;  
  9.         int num_dapm_routes;  
  10.         ......  
  11. }  
我們只要把我們定義好的snd_soc_dapm_widget結構數組的地址和widget的數量賦值到dapm_widgets和num_dapm_widgets字段即可,這樣,經過snd_soc_register_codec註冊codec後,在machine驅動匹配上該codec時,系統會判斷這兩個字段是否被賦值,如果有,它會調傭dapm框架提供的api來創建和註冊widget,注意這裏我說還要創建這個詞,你可能比較奇怪,既然代表widget的snd_soc_dapm_widget結構數組已經在codec驅動中定義好了,爲什麼還要在創建?事實上,我們在codec驅動中定義的widget數組只是作爲一個模板,dapm框架會根據該模板重新申請內存並初始化各個widget。我們看看實際的例子可能是這樣的:

[cpp] view plain copy
  1. static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {  
  2.         ......  
  3.         SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0),  
  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),  
  6.         ......  
  7. };  
  8.   
  9. static struct snd_soc_codec_driver soc_codec_dev_wm8993 = {  
  10.         .probe =        codec_xxx_probe,  
  11.         ......  
  12.         .dapm_widgets =       &wm8993_dapm_widgets[0],  
  13.         .num_dapm_widgets =      ARRAY_SIZE(wm8993_dapm_widgets),  
  14.         ......  
  15. };   
  16.   
  17. static int codec_wm8993_i2c_probe(struct i2c_client *i2c,  
  18.                             const struct i2c_device_id *id)  
  19. {  
  20.         ......  
  21.         ret = snd_soc_register_codec(&i2c->dev,  
  22.                         &soc_codec_dev_wm8993, &wm8993_dai, 1);  
  23.         ......  
  24. }  
上面這種註冊方法有個缺點,有時候我們爲了代碼的清晰,可能會根據功能把不同的widget定義成多個數組,但是snd_soc_codec_driver中只有一個dapm_widgets字段,無法設定多個widget數組,這時候,我們需要主動在codec的probe回調中調用dapm框架提供的api來創建這些widget:
[cpp] view plain copy
  1. static int wm8993_probe(struct snd_soc_codec *codec)  
  2. {  
  3.         ......  
  4.         snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,  
  5.                                   ARRAY_SIZE(wm8993_dapm_widgets));  
  6.         ......  
  7. }  
實際上,對於第一種方法,snd_soc_register_codec內部其實也是調用snd_soc_dapm_new_controls來完成的。後面會有關於這個函數的詳細分析。

platform驅動中註冊    和codec驅動一樣,我們會通過ASoc提供的api函數snd_soc_register_platform來註冊一個platform驅動,該函數的第二個參數是一個snd_soc_platform_driver結構指針,snd_soc_platform_driver結構中同樣也包含了與dapm相關的字段:

[cpp] view plain copy
  1. struct snd_soc_platform_driver {  
  2.         ......          
  3.         /* Default control and setup, added after probe() is run */  
  4.         const struct snd_kcontrol_new *controls;  
  5.         int num_controls;  
  6.         const struct snd_soc_dapm_widget *dapm_widgets;  
  7.         int num_dapm_widgets;  
  8.         const struct snd_soc_dapm_route *dapm_routes;  
  9.         int num_dapm_routes;  
  10.         ......  
  11. }  
要註冊platform級別的widget,和codec驅動一樣,只要把定義好的widget數組賦值給dapm_widgets和num_dapm_widgets字段即可,snd_soc_register_platform函數註冊paltform後,當machine驅動匹配上該platform時,系統會自動完成創建和註冊的工作。同理,我們也可以在platform驅動的probe回調函數中主動使用snd_soc_dapm_new_controls來完成widget的創建工作。具體的代碼和codec驅動是類似的,這裏就不貼了。

machine驅動中註冊    有些widget可能不是位於codec中,例如一個獨立的耳機放大器,或者是喇叭功放等,這種widget通常需要在machine驅動中註冊,通常他們的dapm context也從屬於聲卡(snd_soc_card)域。做法依然和codec驅動類似,通過代表聲卡的snd_soc_card結構中的幾個dapm字段完成:

[cpp] view plain copy
  1. struct snd_soc_card {  
  2.         ......  
  3.         /* 
  4.          * Card-specific routes and widgets. 
  5.          */  
  6.         const struct snd_soc_dapm_widget *dapm_widgets;  
  7.         int num_dapm_widgets;  
  8.         const struct snd_soc_dapm_route *dapm_routes;  
  9.         int num_dapm_routes;  
  10.         bool fully_routed;  
  11.         ......  
  12. }  
只要把定義好的widget數組和數量賦值給dapm_widgets指針和num_dapm_widgets即可,註冊聲卡使用的api:snd_soc_register_card(),也會通過snd_soc_dapm_new_controls來完成widget的創建工作。

註冊音頻路徑


系統中註冊的各種widget需要互相連接在一起才能協調工作,連接關係通過snd_soc_dapm_route結構來定義,關於如何用snd_soc_dapm_route結構來定義路徑信息,請參考:ALSA聲卡驅動中的DAPM詳解之三:如何定義各種widget中的"建立widget和route"一節的內容。通常,所有的路徑信息會用一個snd_soc_dapm_route結構數組來定義。和widget一樣,路徑信息也分別存在與codec驅動,machine驅動和platform驅動中,我們一樣有兩種方式來註冊音頻路徑信息:
  • 通過snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card結構中的dapm_routes和num_dapm_routes字段;
  • 在codec、platform的的probe回調中主動註冊音頻路徑,machine驅動中則通過snd_soc_dai_link結構的init回調函數來註冊音頻路徑;
兩種方法最終都是通過調用snd_soc_dapm_add_routes函數來完成音頻路徑的註冊工作的。以下的代碼片段是omap的pandora板子的machine驅動,使用第二種方法註冊路徑信息:
[cpp] view plain copy
  1. static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {  
  2.         SND_SOC_DAPM_MIC("Mic (internal)", NULL),  
  3.         SND_SOC_DAPM_MIC("Mic (external)", NULL),  
  4.         SND_SOC_DAPM_LINE("Line In", NULL),  
  5. };  
  6.   
  7. static const struct snd_soc_dapm_route omap3pandora_out_map[] = {  
  8.         {"PCM DAC", NULL, "APLL Enable"},  
  9.         {"Headphone Amplifier", NULL, "PCM DAC"},  
  10.         {"Line Out", NULL, "PCM DAC"},  
  11.         {"Headphone Jack", NULL, "Headphone Amplifier"},  
  12. };  
  13.   
  14. static const struct snd_soc_dapm_route omap3pandora_in_map[] = {  
  15.         {"AUXL", NULL, "Line In"},  
  16.         {"AUXR", NULL, "Line In"},  
  17.   
  18.         {"MAINMIC", NULL, "Mic (internal)"},  
  19.         {"Mic (internal)", NULL, "Mic Bias 1"},  
  20.   
  21.         {"SUBMIC", NULL, "Mic (external)"},  
  22.         {"Mic (external)", NULL, "Mic Bias 2"},  
  23. };  
  24. static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)  
  25. {  
  26.         struct snd_soc_codec *codec = rtd->codec;  
  27.         struct snd_soc_dapm_context *dapm = &codec->dapm;  
  28.         int ret;  
  29.   
  30.         /* All TWL4030 output pins are floating */  
  31.         snd_soc_dapm_nc_pin(dapm, "EARPIECE");  
  32.         ......  
  33.         //註冊kcontrol控件  
  34.         ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,  
  35.                                 ARRAY_SIZE(omap3pandora_out_dapm_widgets));  
  36.         if (ret < 0)  
  37.                 return ret;  
  38.         //註冊machine的音頻路徑  
  39.         return snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,  
  40.                 ARRAY_SIZE(omap3pandora_out_map));  
  41. }  
  42.   
  43. static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)  
  44. {  
  45.         struct snd_soc_codec *codec = rtd->codec;  
  46.         struct snd_soc_dapm_context *dapm = &codec->dapm;  
  47.         int ret;  
  48.   
  49.         /* Not comnnected */  
  50.         snd_soc_dapm_nc_pin(dapm, "HSMIC");  
  51.         ......  
  52.         //註冊kcontrol控件  
  53.         ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,  
  54.                                 ARRAY_SIZE(omap3pandora_in_dapm_widgets));  
  55.         if (ret < 0)  
  56.                 return ret;  
  57.         //註冊machine音頻路徑  
  58.         return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,  
  59.                 ARRAY_SIZE(omap3pandora_in_map));  
  60. }  
  61.   
  62. /* Digital audio interface glue - connects codec <--> CPU */  
  63. static struct snd_soc_dai_link omap3pandora_dai[] = {  
  64.         {  
  65.                 .name = "PCM1773",  
  66.                 ......  
  67.                 .init = omap3pandora_out_init,  
  68.         }, {  
  69.                 .name = "TWL4030",  
  70.                 .stream_name = "Line/Mic In",  
  71.                 ......  
  72.                 .init = omap3pandora_in_init,  
  73.         }  
  74. };  



dai widget


上面幾節的內容介紹了codec、platform以及machine級別的widget和route的註冊方法,在dapm框架中,還有另外一種widget,它代表了一個dai(數字音頻接口),關於dai的描述,請參考:Linux ALSA聲卡驅動之七:ASoC架構中的Codec。dai按所在的位置,又分爲cpu dai和codec dai,在硬件上,通常一個cpu dai會連接一個codec dai,而在machine驅動中,我們要在snd_soc_card結構中指定一個叫做snd_soc_dai_link的結構,該結構定義了聲卡使用哪一個cpu dai和codec dai進行連接。在Asoc中,一個dai用snd_soc_dai結構來表述,其中有幾個字段和dapm框架有關:

[cpp] view plain copy
  1. struct snd_soc_dai {  
  2.         ......  
  3.         struct snd_soc_dapm_widget *playback_widget;  
  4.         struct snd_soc_dapm_widget *capture_widget;  
  5.         struct snd_soc_dapm_context dapm;  
  6.         ......  
  7. }  
dai由codec驅動和平臺代碼中的iis或pcm接口驅動註冊,machine驅動負責找到snd_soc_dai_link中指定的一對cpu/codec dai,並把它們進行綁定。不管是cpu dai還是codec dai,通常會同時傳輸播放和錄音的音頻流的能力,所以我們可以看到,snd_soc_dai中有兩個widget指針,分別代表播放流和錄音流。這兩個dai widget是何時創建的呢?下面我們逐一進行分析。

codec dai widget    

首先,codec驅動在註冊codec時,會傳入該codec所支持的dai個數和記錄dai信息的snd_soc_dai_driver結構指針:

[cpp] view plain copy
  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. };  
  22.   
  23. static int wm8993_i2c_probe(struct i2c_client *i2c,  
  24.                             const struct i2c_device_id *id)  
  25. {  
  26.         ......  
  27.         ret = snd_soc_register_codec(&i2c->dev,  
  28.                         &soc_codec_dev_wm8993, &wm8993_dai, 1);  
  29.         ......  
  30. }  
這回使得ASoc把codec的dai註冊到系統中,並把這些dai都掛在全局鏈表變量dai_list中,然後,在codec被machine驅動匹配後,soc_probe_codec函數會被調用,他會通過全局鏈表變量dai_list查找所有屬於該codec的dai,調用snd_soc_dapm_new_dai_widgets函數來生成該dai的播放流widget和錄音流widget:

[cpp] view plain copy
  1. static int soc_probe_codec(struct snd_soc_card *card,  
  2.                            struct snd_soc_codec *codec)  
  3. {  
  4.         ......  
  5.         /* Create DAPM widgets for each DAI stream */  
  6.         list_for_each_entry(dai, &dai_list, list) {  
  7.                 if (dai->dev != codec->dev)  
  8.                         continue;  
  9.   
  10.                 snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);  
  11.         }  
  12.         ......  
  13. }  
我們看看snd_soc_dapm_new_dai_widgets的代碼:

[cpp] view plain copy
  1. int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,  
  2.                                  struct snd_soc_dai *dai)  
  3. {                 
  4.         struct snd_soc_dapm_widget template;  
  5.         struct snd_soc_dapm_widget *w;  
  6.           
  7.         WARN_ON(dapm->dev != dai->dev);  
  8.           
  9.         memset(&template, 0, sizeof(template));  
  10.         template.reg = SND_SOC_NOPM;  
  11.         // 創建播放 dai widget                  
  12.         if (dai->driver->playback.stream_name) {  
  13.                 template.id = snd_soc_dapm_dai_in;  
  14.                 template.name = dai->driver->playback.stream_name;  
  15.                 template.sname = dai->driver->playback.stream_name;  
  16.                           
  17.                 w = snd_soc_dapm_new_control(dapm, &template);  
  18.           
  19.                 w->priv = dai;  
  20.                 dai->playback_widget = w;  
  21.         }  
  22.         // 創建錄音 dai widget  
  23.         if (dai->driver->capture.stream_name) {  
  24.                 template.id = snd_soc_dapm_dai_out;  
  25.                 template.name = dai->driver->capture.stream_name;  
  26.                 template.sname = dai->driver->capture.stream_name;  
  27.   
  28.                 w = snd_soc_dapm_new_control(dapm, &template);  
  29.    
  30.                 w->priv = dai;  
  31.                 dai->capture_widget = w;  
  32.         }  
  33.   
  34.         return 0;  
  35. }  
分別爲Playback和Capture創建了一個widget,widget的priv字段指向了該dai,這樣通過widget就可以找到相應的dai,並且widget的名字就是snd_soc_dai_driver結構的stream_name。

cpu dai widget    
這裏順便說一個小意外,昨天晚上手賤,執行了一下git pull,版本升級到了3.12 rc7,結果發現ASoc的代碼有所變化,於是稍稍糾結了一下,用新的代碼繼續還是恢復之前的3.10 rc5?經過查看了一些變化後,發現還是新的版本改進得更合理,現在決定,後面的內容都是基於3.12 rc7了。如果大家發現後面貼的代碼和之前貼的有差異的地方,自己比較一下這兩個版本的代碼吧!
回到cpu dai,以前的內核版本由驅動通過snd_soc_register_dais註冊,新的版本中,這個函數變爲了soc-core的內部函數,驅動改爲使用snd_soc_register_component註冊,snd_soc_register_component函數再通過調用snd_soc_register_dai/snd_soc_register_dais來完成實際的註冊工作。和codec dai widget一樣,cpu dai widget也發生在machine驅動匹配上相應的platform驅動之後,soc_probe_platform會被調用,在soc_probe_platform函數中,通過比較dai->dev和platform->dev,挑選出屬於該platform的dai,然後通過snd_soc_dapm_new_dai_widgets爲cpu dai創建相應的widget:
[cpp] view plain copy
  1. static int soc_probe_platform(struct snd_soc_card *card,  
  2.                            struct snd_soc_platform *platform)  
  3. {  
  4.         int ret = 0;  
  5.         const struct snd_soc_platform_driver *driver = platform->driver;  
  6.         struct snd_soc_dai *dai;  
  7.   
  8.         ......  
  9.   
  10.         if (driver->dapm_widgets)  
  11.                 snd_soc_dapm_new_controls(&platform->dapm,  
  12.                         driver->dapm_widgets, driver->num_dapm_widgets);  
  13.   
  14.         /* Create DAPM widgets for each DAI stream */  
  15.         list_for_each_entry(dai, &dai_list, list) {  
  16.                 if (dai->dev != platform->dev)  
  17.                         continue;  
  18.   
  19.                 snd_soc_dapm_new_dai_widgets(&platform->dapm, dai);  
  20.         }  
  21.   
  22.         platform->dapm.idle_bias_off = 1;  
  23.   
  24.         ......  
  25.   
  26.         if (driver->controls)  
  27.                 snd_soc_add_platform_controls(platform, driver->controls,  
  28.                                      driver->num_controls);  
  29.         if (driver->dapm_routes)  
  30.                 snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,  
  31.                                         driver->num_dapm_routes);  
  32.         ......  
  33.   
  34.         return 0;  
  35. }  
從上面的代碼我們也可以看出,在上面的”創建和註冊widget“一節提到的第一種方法,即通過給snd_soc_platform_driver結構的dapm_widgets和num_dapm_widgets字段賦值,ASoc會自動爲我們創建所需的widget,真正執行創建工作就在上面所列的soc_probe_platform函數中完成的,普通的kcontrol和音頻路徑也是一樣的原理。反推回來,codec的widget也是一樣的,在soc_probe_codec中會做同樣的事情,只是我上面貼出來soc_probe_codec的代碼裏沒有貼出來,有興趣的讀者自己查看一下它的代碼即可。
花了這麼多篇幅來講解dai widget,好像現在看來它還沒有什麼用處。嗯,不要着急,實際上dai widget是一條完整dapm音頻路徑的重要元素,沒有她,我們無法完成dapm的動態電源管理工作,因爲它是音頻流和其他widget的紐帶,細節我們要留到下一篇文章中來闡述了。

端點widget


一條完整的dapm音頻路徑,必然有起點和終點,我們把位於這些起點和終點的widget稱之爲端點widget。以下這些類型的widget可以成爲端點widget:

codec的輸入輸出引腳

  • snd_soc_dapm_output
  • snd_soc_dapm_input
外接的音頻設備
  • snd_soc_dapm_hp
  • snd_soc_dapm_spk
  • snd_soc_dapm_line
音頻流(stream domain)
  • snd_soc_dapm_adc
  • snd_soc_dapm_dac
  • snd_soc_dapm_aif_out
  • snd_soc_dapm_aif_in
  • snd_soc_dapm_dai_out
  • snd_soc_dapm_dai_in
電源、時鐘和其它
  • snd_soc_dapm_supply
  • snd_soc_dapm_regulator_supply
  • snd_soc_dapm_clock_supply
  • snd_soc_dapm_kcontrol

當聲卡上的其中一個widget的狀態發生改變時,從這個widget開始,dapm框架會向前和向後遍歷路徑上的所有widget,判斷每個widget的狀態是否需要跟着變更,到達這些端點widget就會認爲它是一條完整音頻路徑的開始和結束,從而結束一次掃描動作。至於代碼的分析,先讓我歇一會......,我會在後面的文章中討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章