在上一篇文章中,我們重點介紹了widget、path、route之間的關係及其widget的註冊; http://www.cnblogs.com/linhaostudy/p/8509899.html 在最後一章中,我們已經簡單介紹了snd_soc_dapm_new_controls函數用來創建wid ...
在上一篇文章中,我們重點介紹了widget、path、route之間的關係及其widget的註冊;
http://www.cnblogs.com/linhaostudy/p/8509899.html
在最後一章中,我們已經簡單介紹了snd_soc_dapm_new_controls函數用來創建widget。
實際上,這個函數只是創建widget的第一步,它為每一個widget分配記憶體,初始化;
要使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 /** 2 * snd_soc_dapm_new_controls - create new dapm controls 3 * @dapm: DAPM context 4 * @widget: widget array 5 * @num: number of widgets 6 * 7 * Creates new DAPM controls based upon the templates. 8 * 9 * Returns 0 for success else error. 10 */ 11 int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, 12 const struct snd_soc_dapm_widget *widget, 13 int num) 14 { 15 struct snd_soc_dapm_widget *w; 16 int i; 17 int ret = 0; 18 19 mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); 20 for (i = 0; i < num; i++) { 21 w = snd_soc_dapm_new_control(dapm, widget); 22 if (!w) { 23 dev_err(dapm->dev, 24 "ASoC: Failed to create DAPM control %s\n", 25 widget->name); 26 ret = -ENOMEM; 27 break; 28 } 29 widget++; 30 } 31 mutex_unlock(&dapm->card->dapm_mutex); 32 return ret; 33 }
該函數只是簡單的一個迴圈,為傳入的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的名稱前加入必要的首碼:
if (dapm->codec && dapm->codec->name_prefix) w->name = kasprintf(GFP_KERNEL, "%s %s", dapm->codec->name_prefix, widget->name); else w->name = kasprintf(GFP_KERNEL, "%s", widget->name);
然後,為不同類型的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 |
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鏈表
1 /* machine layer set ups unconnected pins and insertions */ 2 w->connected = 1; 3 return w; 4 }
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
- snd_soc_dapm_enable_pin
- snd_soc_dapm_force_enable_pin
- snd_soc_dapm_disable_pin
- snd_soc_dapm_nc_pin
- 為widget分配記憶體,並拷貝參數中傳入的在驅動中定義好的模板
- 設置power_check回調函數
- 把widget掛在音效卡的widgets鏈表中
二、為widget建立dapm kcontrol
定義一個widget,我們需要指定兩個很重要的內容:一個是用於控制widget的電源狀態的reg/shift等寄存器信息,另一個是用於控制音頻路徑切換的dapm kcontrol信息,這些dapm kcontrol有它們自己的reg/shift寄存器信息用於切換widget的路徑連接方式。
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函數的工作過程。
2.1 snd_soc_dapm_new_widgets函數:
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;
接著,設置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); 5 }
最後,通過dapm_power_widgets函數,統一處理所有位於dapm_dirty鏈表上的widget的狀態改變:
1 dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP); 2 ...... 3 return 0;
三、為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);
最後,使用來增加一條連接信息:
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函數是整個調用鏈條中的關鍵,我們來分析一下:(註意linux3.10.28代碼沒有相應的snd_soc_dapm_add_path函數,在linux3.12才有設計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之間通過path進行連接之後,他們之間的關係就如下圖所示:
到這裡為止,我們為音效卡創建並初始化好了所需的widget,各個widget也通過path連接在了一起,接下來,dapm等待用戶的指令,一旦某個dapm kcontrol被用戶空間改變,利用這些連接關係,dapm會重新創建音頻路徑,脫離音頻路徑的widget會被下電,加入音頻路徑的widget會被上電,所有的上下電動作都會自動完成,用戶空間的應用程式無需關註這些變化,它只管按需要改變某個dapm kcontrol即可。