linux-alsa詳解14之DAPM詳解7上下電過程分析


設計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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM