設計dapm的主要目的之一,就是希望聲卡上的各種部件的電源按需分配,需要的就上電,不需要的就下電,使得整個音頻系統總是處於最小的耗電狀態,最主要的就是,這一切對用戶空間的應用程序是透明的,也就是說,用戶空間的應用程序無需關心那個部件何時需要電源,它只要按需要設定好音頻路徑,播放音頻數據,暫停或停止,dapm框架會根據音頻路徑,完美地對各種部件的電源進行控制,而且精確地按某種順序進行,防止上下電過程中產生不必要的pop-pop聲。
1 統計widget連接至端點widget的路徑個數
端點widget位於音頻路徑的起始端或者末端,所以通常它們就是指codec的輸入輸出引腳所對應的widget,或者是外部器件對應的widget,這些widget的類型如linux-alsa詳解11之DAPM詳解4驅動中widget初始化第4節所述。
dapm要給一個widget上電的其中一個前提條件是:這個widget位於一條完整的音頻路徑上,而一條完整的音頻路徑的兩頭,必須是輸入/輸出引腳,或者是一個外部音頻設備,又或者是一個處於激活狀態的音頻流widget,也就是上表中的前三項,上表中的后兩項,它們可以位於路徑的末端,但不是構成完成音頻路徑的必要條件,我們只用它來判斷掃描一條路徑的結束條件。dapm提供了兩個內部函數,用來統計一個widget連接到輸出引腳、輸入引腳、激活的音頻流widget的有效路徑個數:
is_connected_output_ep 返回連接至輸出引腳或激活狀態的輸出音頻流的路徑數量
is_connected_input_ep 返回連接至輸入引腳或激活狀態的輸入音頻流的路徑數量
兩個函數定義如下:
1 static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, 2 struct list_head *list, 3 bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, 4 enum snd_soc_dapm_direction)) 5 { 6 return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT, 7 is_connected_output_ep, custom_stop_condition); 8 } 9 10 static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, 11 struct list_head *list, 12 bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, 13 enum snd_soc_dapm_direction)) 14 { 15 return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN, 16 is_connected_input_ep, custom_stop_condition); 17 }
最終都調用函數is_connected_input_ep
函數具體過程待分析補充
1 static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, 2 struct list_head *list, enum snd_soc_dapm_direction dir, 3 int (*fn)(struct snd_soc_dapm_widget *, struct list_head *, 4 bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, 5 enum snd_soc_dapm_direction)), 6 bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, 7 enum snd_soc_dapm_direction)) 8 { 9 enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); 10 struct snd_soc_dapm_path *path; 11 int con = 0; 12 /**多個路徑可能使用了同一個widget,如果在遍歷另一個路徑時,已經統計過該widget,直接返回endpoint字段即可/ 13 if (widget->endpoints[dir] >= 0) 14 return widget->endpoints[dir]; 15 16 DAPM_UPDATE_STAT(widget, path_checks); 17 18 /* do we need to add this widget to the list ? */ 19 if (list) 20 list_add_tail(&widget->work_list, list); 21 22 if (custom_stop_condition && custom_stop_condition(widget, dir)) { 23 widget->endpoints[dir] = 1; 24 return widget->endpoints[dir]; 25 } 26 27 if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) { 28 widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget); 29 return widget->endpoints[dir]; 30 } 31 32 snd_soc_dapm_widget_for_each_path(widget, rdir, path) { 33 DAPM_UPDATE_STAT(widget, neighbour_checks); 34 35 if (path->weak || path->is_supply) 36 continue; 37 38 if (path->walking) 39 return 1; 40 41 trace_snd_soc_dapm_path(widget, dir, path); 42 43 if (path->connect) { 44 path->walking = 1; 45 con += fn(path->node[dir], list, custom_stop_condition); 46 path->walking = 0; 47 } 48 } 49 50 widget->endpoints[dir] = con; 51 52 return con; 53 }
2 dapm_dirty鏈表
在代表聲卡的snd_soc_card結構中,有一個鏈表字段:dapm_dirty,所有狀態發生了改變的widget,dapm不會立刻處理它的電源狀態,而是需要先掛在該鏈表下面,等待后續的進一步處理:或者是上電,或者是下電。dapm為我們提供了一個api函數來完成這個動作:
1 static void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason) 2 { 3 dapm_assert_locked(w->dapm); 4 5 if (!dapm_dirty_widget(w)) { 6 dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s\n", 7 w->name, reason); 8 list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty); 9 } 10 }
3 power_check回調函數
3.1 函數dapm_generic_check_power
在創建widget的時候,widget的power_check回調函數會根據widget的類型,設置不同的回調函數。當widget的狀態改變后,dapm會遍歷dapm_dirty鏈表,並通過power_check回調函數,決定該widget是否需要上電。大多數的widget的power_check回調被設置為:dapm_generic_check_power,如linux-alsa詳解11之DAPM詳解4驅動中widget初始化第三節所述。
1 /* Generic check to see if a widget should be powered. */ 2 static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) 3 { 4 int in, out; 5 6 DAPM_UPDATE_STAT(w, power_checks); 7 8 in = is_connected_input_ep(w, NULL, NULL); 9 out = is_connected_output_ep(w, NULL, NULL); 10 return out != 0 && in != 0; 11 }
分別用is_connected_output_ep和is_connected_input_ep得到該widget是否有同時連接到一個輸入端和一個輸出端,如果是,返回1來表示該widget需要上電。
3.2 dai回調
對於snd_soc_dapm_dai_out和snd_soc_dapm_dai_in類型,power_check回調是dapm_adc_check_power和dapm_dac_check_power,這里以dapm_dac_check_power為例:
1 static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) 2 { 3 int out; 4 5 DAPM_UPDATE_STAT(w, power_checks); 6 7 if (w->active) { 8 out = is_connected_output_ep(w, NULL); 9 dapm_clear_walk_output(w->dapm, &w->sinks); 10 return out != 0; 11 } else { 12 return dapm_generic_check_power(w); 13 } 14 }
處於激活狀態時,只判斷是否有連接到有效的輸出路徑即可,沒有激活時,則需要同時判斷是否有連接到輸入路徑和輸出路徑。
4 widget的上電和下電順序
在掃描dapm_dirty鏈表時,dapm使用兩個鏈表來分別保存需要上電和需要下電的widget:
1 up_list 保存需要上電的widget 2 down_list 保存需要下電的widget
4.1 函數dapm_seq_insert
dapm內部使用dapm_seq_insert函數把一個widget加入到上述兩個鏈表中的其中一個。
1 /* Insert a widget in order into a DAPM power sequence. */ 2 static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, 3 struct list_head *list, 4 bool power_up) 5 { 6 struct snd_soc_dapm_widget *w; 7 8 list_for_each_entry(w, list, power_list) 9 if (dapm_seq_compare(new_widget, w, power_up) < 0) { 10 list_add_tail(&new_widget->power_list, &w->power_list); 11 return; 12 } 13 14 list_add_tail(&new_widget->power_list, list); 15 }
上述函數會按照一定的順序把widget加入到鏈表中,從而保證正確的上下電順序:
1 /* dapm power sequences - make this per codec in the future */ 2 //上電順序 3 static int dapm_up_seq[] = { 4 [snd_soc_dapm_pre] = 0, 5 [snd_soc_dapm_regulator_supply] = 1, 6 [snd_soc_dapm_clock_supply] = 1, 7 [snd_soc_dapm_supply] = 2, 8 [snd_soc_dapm_micbias] = 3, 9 [snd_soc_dapm_dai_link] = 2, 10 [snd_soc_dapm_dai_in] = 4, 11 [snd_soc_dapm_dai_out] = 4, 12 [snd_soc_dapm_aif_in] = 4, 13 [snd_soc_dapm_aif_out] = 4, 14 [snd_soc_dapm_mic] = 5, 15 [snd_soc_dapm_mux] = 6, 16 [snd_soc_dapm_demux] = 6, 17 [snd_soc_dapm_dac] = 7, 18 [snd_soc_dapm_switch] = 8, 19 [snd_soc_dapm_mixer] = 8, 20 [snd_soc_dapm_mixer_named_ctl] = 8, 21 [snd_soc_dapm_pga] = 9, 22 [snd_soc_dapm_adc] = 10, 23 [snd_soc_dapm_out_drv] = 11, 24 [snd_soc_dapm_hp] = 11, 25 [snd_soc_dapm_spk] = 11, 26 [snd_soc_dapm_line] = 11, 27 [snd_soc_dapm_kcontrol] = 12, 28 [snd_soc_dapm_post] = 13, 29 }; 30 //下點順序 31 static int dapm_down_seq[] = { 32 [snd_soc_dapm_pre] = 0, 33 [snd_soc_dapm_kcontrol] = 1, 34 [snd_soc_dapm_adc] = 2, 35 [snd_soc_dapm_hp] = 3, 36 [snd_soc_dapm_spk] = 3, 37 [snd_soc_dapm_line] = 3, 38 [snd_soc_dapm_out_drv] = 3, 39 [snd_soc_dapm_pga] = 4, 40 [snd_soc_dapm_switch] = 5, 41 [snd_soc_dapm_mixer_named_ctl] = 5, 42 [snd_soc_dapm_mixer] = 5, 43 [snd_soc_dapm_dac] = 6, 44 [snd_soc_dapm_mic] = 7, 45 [snd_soc_dapm_micbias] = 8, 46 [snd_soc_dapm_mux] = 9, 47 [snd_soc_dapm_demux] = 9, 48 [snd_soc_dapm_aif_in] = 10, 49 [snd_soc_dapm_aif_out] = 10, 50 [snd_soc_dapm_dai_in] = 10, 51 [snd_soc_dapm_dai_out] = 10, 52 [snd_soc_dapm_dai_link] = 11, 53 [snd_soc_dapm_supply] = 12, 54 [snd_soc_dapm_clock_supply] = 13, 55 [snd_soc_dapm_regulator_supply] = 13, 56 [snd_soc_dapm_post] = 14, 57 };
5 widget的上電和下電過程
5.1 函數dapm_power_widgets
當一個widget的狀態改變后,該widget會被加入dapm_dirty鏈表,然后通過dapm_power_widgets函數來改變整個音頻路徑上的電源狀態。
1 static int dapm_power_widgets(struct snd_soc_card *card, int event) 2 { 3 struct snd_soc_dapm_widget *w; 4 struct snd_soc_dapm_context *d; 5 LIST_HEAD(up_list); 6 LIST_HEAD(down_list); 7 ASYNC_DOMAIN_EXCLUSIVE(async_domain); 8 enum snd_soc_bias_level bias; 9 10 lockdep_assert_held(&card->dapm_mutex); 11 12 trace_snd_soc_dapm_start(card); 13 14 list_for_each_entry(d, &card->dapm_list, list) { 15 if (dapm_idle_bias_off(d)) 16 d->target_bias_level = SND_SOC_BIAS_OFF; 17 else 18 d->target_bias_level = SND_SOC_BIAS_STANDBY; 19 } 20 21 dapm_reset(card); 22 23 /* Check which widgets we need to power and store them in 24 * lists indicating if they should be powered up or down. We 25 * only check widgets that have been flagged as dirty but note 26 * that new widgets may be added to the dirty list while we 27 * iterate. 28 */ 29 list_for_each_entry(w, &card->dapm_dirty, dirty) { 30 dapm_power_one_widget(w, &up_list, &down_list); 31 } 32 33 list_for_each_entry(w, &card->widgets, list) { 34 switch (w->id) { 35 case snd_soc_dapm_pre: 36 case snd_soc_dapm_post: 37 /* These widgets always need to be powered */ 38 break; 39 default: 40 list_del_init(&w->dirty); 41 break; 42 } 43 44 if (w->new_power) { 45 d = w->dapm; 46 47 /* Supplies and micbiases only bring the 48 * context up to STANDBY as unless something 49 * else is active and passing audio they 50 * generally don't require full power. Signal 51 * generators are virtual pins and have no 52 * power impact themselves. 53 */ 54 switch (w->id) { 55 case snd_soc_dapm_siggen: 56 case snd_soc_dapm_vmid: 57 break; 58 case snd_soc_dapm_supply: 59 case snd_soc_dapm_regulator_supply: 60 case snd_soc_dapm_clock_supply: 61 case snd_soc_dapm_micbias: 62 if (d->target_bias_level < SND_SOC_BIAS_STANDBY) 63 d->target_bias_level = SND_SOC_BIAS_STANDBY; 64 break; 65 default: 66 d->target_bias_level = SND_SOC_BIAS_ON; 67 break; 68 } 69 } 70 71 } 72 73 /* Force all contexts in the card to the same bias state if 74 * they're not ground referenced. 75 */ 76 bias = SND_SOC_BIAS_OFF; 77 list_for_each_entry(d, &card->dapm_list, list) 78 if (d->target_bias_level > bias) 79 bias = d->target_bias_level; 80 list_for_each_entry(d, &card->dapm_list, list) 81 if (!dapm_idle_bias_off(d)) 82 d->target_bias_level = bias; 83 84 trace_snd_soc_dapm_walk_done(card); 85 86 /* Run card bias changes at first */ 87 dapm_pre_sequence_async(&card->dapm, 0); 88 /* Run other bias changes in parallel */ 89 list_for_each_entry(d, &card->dapm_list, list) { 90 if (d != &card->dapm) 91 async_schedule_domain(dapm_pre_sequence_async, d, 92 &async_domain); 93 } 94 async_synchronize_full_domain(&async_domain); 95 96 list_for_each_entry(w, &down_list, power_list) { 97 dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD); 98 } 99 100 list_for_each_entry(w, &up_list, power_list) { 101 dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU); 102 } 103 104 /* Power down widgets first; try to avoid amplifying pops. */ 105 dapm_seq_run(card, &down_list, event, false); 106 107 dapm_widget_update(card); 108 109 /* Now power up. */ 110 dapm_seq_run(card, &up_list, event, true); 111 112 /* Run all the bias changes in parallel */ 113 list_for_each_entry(d, &card->dapm_list, list) { 114 if (d != &card->dapm) 115 async_schedule_domain(dapm_post_sequence_async, d, 116 &async_domain); 117 } 118 async_synchronize_full_domain(&async_domain); 119 /* Run card bias changes at last */ 120 dapm_post_sequence_async(&card->dapm, 0); 121 122 /* do we need to notify any clients that DAPM event is complete */ 123 list_for_each_entry(d, &card->dapm_list, list) { 124 if (d->stream_event) 125 d->stream_event(d, event); 126 } 127 128 pop_dbg(card->dev, card->pop_time, 129 "DAPM sequencing finished, waiting %dms\n", card->pop_time); 130 pop_wait(card->pop_time); 131 132 trace_snd_soc_dapm_done(card); 133 134 return 0; 135 }
分析如下:
(1)可見,該函數通過遍歷dapm_dirty鏈表,對每個鏈表中的widget調用dapm_power_one_widget,dapm_power_one_widget函數除了處理自身的狀態改變外,還把自身的變化傳遞到和它相連的鄰居widget中,結果就是,所有需要上電的widget會被放在up_list鏈表中,而所有需要下電的widget會被放在down_list鏈表中,這個函數我們稍后再討論。
(2)遍歷down_list鏈表,向其中的widget發出SND_SOC_DAPM_WILL_PMD事件,感興趣該事件的widget的event回調會被調用。
(3)遍歷up_list鏈表,向其中的widget發出SND_SOC_DAPM_WILL_PMU事件,感興趣該事件的widget的event回調會被調用。
(4)通過dapm_seq_run函數,處理down_list中的widget,使它們按定義好的順序依次下電。
(5)通過dapm_widget_update函數,切換觸發該次狀態變化的widget的kcontrol中的寄存器值,對應的結果就是:改變音頻路徑。
(6)通過dapm_seq_run函數,處理up_list中的widget,使它們按定義好的順序依次上電。
(7)對每個dapm context發出狀態改變回調。
(8)適當的延時,防止pop-pop聲。
調用過程如下:

5.2 函數dapm_power_one_widget
dapm_power_widgets的第一步,就是遍歷dapm_dirty鏈表,對每個鏈表中的widget調用dapm_power_one_widget,把需要上電和需要下電的widget分別加入到up_list和down_list鏈表中,同時,他還會把受到影響的鄰居widget再次加入到dapm_dirty鏈表的末尾,通過這個動作,聲卡中所以受到影響的widget都會被“感染”,依次被加到dapm_dirty鏈表,然后依次被執行dapm_power_one_widget函數。dapm_power_one_widget函數定義如下:
1 static void dapm_power_one_widget(struct snd_soc_dapm_widget *w, 2 struct list_head *up_list, 3 struct list_head *down_list) 4 { 5 int power; 6 7 switch (w->id) { 8 case snd_soc_dapm_pre: 9 dapm_seq_insert(w, down_list, false); 10 break; 11 case snd_soc_dapm_post: 12 dapm_seq_insert(w, up_list, true); 13 break; 14 15 default: 16 power = dapm_widget_power_check(w); 17 18 dapm_widget_set_power(w, power, up_list, down_list); 19 break; 20 } 21 }
作用如下:
(1)通過dapm_widget_power_check,調用widget的power_check回調函數,獲得該widget新的電源狀態。
(2)調用dapm_widget_set_power,“感染”與之相連的鄰居widget。
(3)遍歷source widget,通過dapm_widget_set_peer_power函數,把處於連接狀態的source widget加入dapm_dirty鏈表中。
(4)遍歷sink widget,通過dapm_widget_set_peer_power函數,把處於連接狀態的sink widget加入dapm_dirty鏈表中。
(5)根據第一步得到的新的電源狀態,把widget加入到up_list或down_list鏈表中。
可見,通過該函數,一個widget的狀態改變,鄰居widget會受到“感染”而被加入到dapm_dirty鏈表的末尾,所以掃描到鏈表的末尾時,鄰居widget也會執行同樣的操作,從而“感染”鄰居的鄰居,直到沒有新的widget被加入dapm_dirty鏈表為止,這時,所有受到影響的widget都被加入到up_list或down_li鏈表中,等待后續的上下電操作。可謂牽一發而動全身。
dapm_power_one_widget函數的調用過程如下:

5.3 函數dapm_seq_run
以上上電過程,當所有需要上電或下電的widget都被加入到dapm_dirty鏈表后,接着會通過dapm_seq_run處理down_list鏈表上的widget,把該鏈表上的widget按順序下電,然后通過dapm_widget_update更新widget中的kcontrol(這個kcontrol通常就是觸發本次狀態改變的觸發源),接着又通過apm_seq_run處理up_list鏈表上的widget,把該鏈表上的widget按順序上電。最終的上電或下電操作需要通過codec的寄存器來實現,因為定義widget時,如果這是一個帶電源控制的widget,我們必須提供reg/shift等字段的設置值,如果該widget無需寄存器控制電源狀態,則reg字段必須賦值為:
SND_SOC_NOPM (該宏定義的實際值是-1)
函數定義:
具體實現上,dapm框架使用了一點技巧:如果位於同一個上下電順序的幾個widget使用了同一個寄存器地址(一個寄存器可能使用不同的位來控制不同的widget的電源狀態),dapm_seq_run通過dapm_seq_run_coalesced函數合並這幾個widget的變更,然后只需要把合並后的值一次寫入寄存器即可。
1 static void dapm_seq_run(struct snd_soc_card *card, 2 struct list_head *list, int event, bool power_up) 3 { 4 struct snd_soc_dapm_widget *w, *n; 5 struct snd_soc_dapm_context *d; 6 LIST_HEAD(pending); 7 int cur_sort = -1; 8 int cur_subseq = -1; 9 int cur_reg = SND_SOC_NOPM; 10 struct snd_soc_dapm_context *cur_dapm = NULL; 11 int ret, i; 12 int *sort; 13 14 if (power_up) 15 sort = dapm_up_seq; 16 else 17 sort = dapm_down_seq; 18 19 list_for_each_entry_safe(w, n, list, power_list) { 20 ret = 0; 21 22 /* Do we need to apply any queued changes? */ 23 if (sort[w->id] != cur_sort || w->reg != cur_reg || 24 w->dapm != cur_dapm || w->subseq != cur_subseq) { 25 if (!list_empty(&pending)) 26 dapm_seq_run_coalesced(card, &pending);//合並這幾個widget的變更,然后只需要把合並后的值一次寫入寄存器即可 27 28 if (cur_dapm && cur_dapm->seq_notifier) { 29 for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) 30 if (sort[i] == cur_sort) 31 cur_dapm->seq_notifier(cur_dapm, 32 i, 33 cur_subseq); 34 } 35 36 if (cur_dapm && w->dapm != cur_dapm) 37 soc_dapm_async_complete(cur_dapm); 38 39 INIT_LIST_HEAD(&pending); 40 cur_sort = -1; 41 cur_subseq = INT_MIN; 42 cur_reg = SND_SOC_NOPM; 43 cur_dapm = NULL; 44 } 45 46 switch (w->id) { 47 case snd_soc_dapm_pre: 48 if (!w->event) 49 list_for_each_entry_safe_continue(w, n, list, 50 power_list); 51 52 if (event == SND_SOC_DAPM_STREAM_START) 53 ret = w->event(w, 54 NULL, SND_SOC_DAPM_PRE_PMU); 55 else if (event == SND_SOC_DAPM_STREAM_STOP) 56 ret = w->event(w, 57 NULL, SND_SOC_DAPM_PRE_PMD); 58 break; 59 60 case snd_soc_dapm_post: 61 if (!w->event) 62 list_for_each_entry_safe_continue(w, n, list, 63 power_list); 64 65 if (event == SND_SOC_DAPM_STREAM_START) 66 ret = w->event(w, 67 NULL, SND_SOC_DAPM_POST_PMU); 68 else if (event == SND_SOC_DAPM_STREAM_STOP) 69 ret = w->event(w, 70 NULL, SND_SOC_DAPM_POST_PMD); 71 break; 72 73 default: 74 /* Queue it up for application */ 75 cur_sort = sort[w->id]; 76 cur_subseq = w->subseq; 77 cur_reg = w->reg; 78 cur_dapm = w->dapm; 79 list_move(&w->power_list, &pending); 80 break; 81 } 82 83 if (ret < 0) 84 dev_err(w->dapm->dev, 85 "ASoC: Failed to apply widget power: %d\n", ret); 86 } 87 88 if (!list_empty(&pending)) 89 dapm_seq_run_coalesced(card, &pending); 90 91 if (cur_dapm && cur_dapm->seq_notifier) { 92 for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) 93 if (sort[i] == cur_sort) 94 cur_dapm->seq_notifier(cur_dapm, 95 i, cur_subseq); 96 } 97 98 list_for_each_entry(d, &card->dapm_list, list) { 99 soc_dapm_async_complete(d); 100 } 101 }
6 觸發dapm掃描操作
上面我們已經討論了如何判斷一個widget是否需要上電,以及widget的上電過程,一個widget的狀態改變如何傳遞到整個音頻路徑上的所有widget。這些過程總是需要一個起始點:是誰觸動了dapm,使得它需要執行上述的掃描和上電過程?事實上,以下幾種情況可以觸發dapm發起一次掃描操作:
(1)聲卡初始化階段,snd_soc_dapm_new_widgets函數創建widget包含的kcontrol后,會觸發一次掃描操作。
(2)用戶空間的應用程序修改了widget中包含的dapm kcontrol的配置值時,會觸發一次掃描操作。
(3)pcm的打開或關閉,會通過音頻流widget觸發一次掃描操作。
(4)驅動程序在改變了某個widget並把它加入到dapm_dirty鏈表后,主動調用snd_soc_dapm_sync函數觸發掃描操作。
這里我們主要討論一下第二種,用戶空間對kcontrol的修改,最終都會調用到kcontrol的put回調函數。對於常用的dapm kcontrol,系統已經為我們定義好了它們的put回調函數:
1 snd_soc_dapm_put_volsw mixer類型的dapm kcontrol使用的put回調 2 snd_soc_dapm_put_enum_double mux類型的dapm kcontrol使用的put回調 3 snd_soc_dapm_put_enum_virt 虛擬mux類型的dapm kcontrol使用的put回調 4 snd_soc_dapm_put_value_enum_double 控制值不連續的mux類型的dapm kcontrol使用的put回調 5 snd_soc_dapm_put_pin_switch 引腳類dapm kcontrol使用的put回調
我們以mixer類型的dapm kcontrol的put回調講解一下觸發的過程:
6.1 函數snd_soc_dapm_put_volsw
1 /** 2 * snd_soc_dapm_put_volsw - dapm mixer set callback 3 * @kcontrol: mixer control 4 * @ucontrol: control element information 5 * 6 * Callback to set the value of a dapm mixer control. 7 * 8 * Returns 0 for success. 9 */ 10 int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, 11 struct snd_ctl_elem_value *ucontrol) 12 { 13 struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); 14 struct snd_soc_card *card = dapm->card; 15 struct soc_mixer_control *mc = 16 (struct soc_mixer_control *)kcontrol->private_value; 17 int reg = mc->reg; 18 unsigned int shift = mc->shift; 19 int max = mc->max; 20 unsigned int mask = (1 << fls(max)) - 1; 21 unsigned int invert = mc->invert; 22 unsigned int val; 23 int connect, change, reg_change = 0; 24 struct snd_soc_dapm_update update; 25 int ret = 0; 26 27 if (snd_soc_volsw_is_stereo(mc)) 28 dev_warn(dapm->dev, 29 "ASoC: Control '%s' is stereo, which is not supported\n", 30 kcontrol->id.name); 31 /*從參數中取出要設置的新的設置值*/ 32 val = (ucontrol->value.integer.value[0] & mask); 33 connect = !!val; 34 35 if (invert) 36 val = max - val; 37 38 mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); 39 40 change = dapm_kcontrol_set_value(kcontrol, val);//把新的設置值緩存到kcontrol的影子widget中 41 42 if (reg != SND_SOC_NOPM) { 43 mask = mask << shift; 44 val = val << shift; 45 46 reg_change = soc_dapm_test_bits(dapm, reg, mask, val); 47 } 48 /*和實際寄存器中的值進行對比,不一樣時才會觸發寄存器的寫入;寄存器通常都會通過regmap機制進行緩存,所以這個測試不會發生實際的寄存器讀取操作;*/ 49 if (change || reg_change) {//這里只是觸發,真正的寄存器寫入操作要在掃描完dapm_dirty鏈表后的執行 50 if (reg_change) { 51 update.kcontrol = kcontrol; 52 update.reg = reg; 53 update.mask = mask; 54 update.val = val; 55 card->update = &update; 56 } 57 change |= reg_change; 58 59 ret = soc_dapm_mixer_update_power(card, kcontrol, connect);//觸發dapm的上下電掃描過程 60 61 card->update = NULL; 62 } 63 64 mutex_unlock(&card->dapm_mutex); 65 66 if (ret > 0) 67 soc_dpcm_runtime_update(card); 68 69 return change; 70 }
其中的dapm_kcontrol_set_value函數用於把設置值緩存到kcontrol對應的影子widget,影子widget是為了實現autodisable特性而創建的一個虛擬widget,影子widget的輸出連接到kcontrol的source widget,影子widget的寄存器被設置為和kcontrol一樣的寄存器地址,這樣當source widget被關閉時,會觸發影子widget被關閉,其作用就是kcontrol也被自動關閉從而在物理上斷開與source widget的連接,但是此時邏輯連接依然有效,dapm依然認為它們是連接在一起的。
6.2 函數soc_dapm_mixer_update_power
觸發dapm進行電源狀態掃描關鍵函數
1 static int soc_dapm_mixer_update_power(struct snd_soc_card *card, 2 struct snd_kcontrol *kcontrol, int connect) 3 { 4 struct snd_soc_dapm_path *path; 5 int found = 0; 6 7 /* 更新所有和該kcontrol對應輸入端相連的path的connect字段 */ 8 dapm_kcontrol_for_each_path(path, kcontrol) { 9 found = 1; 10 soc_dapm_connect_path(path, connect, "mixer update"); 11 } 12 /* 發起dapm_dirty鏈表掃描和上下電過程 */ 13 if (found) 14 dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP); 15 16 return found; 17 }
函數soc_dapm_connect_path
1 static void soc_dapm_connect_path(struct snd_soc_dapm_path *path, 2 bool connect, const char *reason) 3 { 4 if (path->connect == connect) 5 return; 6 7 path->connect = connect; 8 /*把自己和相連的source widget加入到dirty鏈表中*/ 9 dapm_mark_dirty(path->source, reason); 10 dapm_mark_dirty(path->sink, reason); 11 dapm_path_invalidate(path); 12 }
最終,還是通過dapm_power_widgets函數,觸發整個音頻路徑的掃描過程,這個函數執行后,因為kcontrol的狀態改變,被斷開連接的音頻路徑上的所有widget被按順序下電,而重新連上的音頻路徑上的所有widget被順序地上電,所以,盡管我們只改變了mixer kcontrol中的一個輸入端的連接狀態,所有相關的widget的電源狀態都會被重新設定,這一切,都是自動完成的,對用戶空間的應用程序完全透明,實現了dapm的原本設計目標。
總結下整個mixer類型的dapm kcontrol的put回調觸發的過程:

參考博文:https://blog.csdn.net/DroidPhone/java/article/details/14146319
