linux-alsa详解15之DAPM详解8事件机制

之前讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等。接下来讨论dapm框架中的另一个机制:事件机制。通过dapm事件机制,widget可以对它所关心的dapm事件做出反应,这种机制对于扩充widget的能力非常有用,例如,对于那些位于codec之外的widget,好像喇叭功放、外部的前置放大器等等,由于不是使用codec内部的寄存器进行电源控制,我们就必须利用dapm的事件机制,获得相应的上下电事件,从而可以定制widget自身的电源控制功能。

1 dapm事件的类型

定义位于:linux-4.9.73\include\sound\soc-dapm.h

前8种每种占据一个位,所以,我们可以在一个整数中表达多个我们需要关心的dapm事件,只要把它们按位或进行合并即可。

 1 /* dapm event types */
 2 #define SND_SOC_DAPM_PRE_PMU    0x1     /* before widget power up */widget要上电前发出的事件
 3 #define SND_SOC_DAPM_POST_PMU    0x2        /* after widget power up */widget要上电后发出的事件
 4 #define SND_SOC_DAPM_PRE_PMD    0x4     /* before widget power down */widget要下电前发出的事件
 5 #define SND_SOC_DAPM_POST_PMD    0x8        /* after widget power down */widget要下电后发出的事件
 6 #define SND_SOC_DAPM_PRE_REG    0x10    /* before audio path setup */音频路径设置之前发出的事件
 7 #define SND_SOC_DAPM_POST_REG    0x20    /* after audio path setup */音频路径设置之后发出的事件
 8 #define SND_SOC_DAPM_WILL_PMU   0x40    /* called at start of sequence */在处理up_list链表之前发出的事件
 9 #define SND_SOC_DAPM_WILL_PMD   0x80    /* called at start of sequence */在处理down_list链表之前发出的事件
10 #define SND_SOC_DAPM_PRE_POST_PMD \
11                 (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)//SND_SOC_DAPM_PRE_PMD和SND_SOC_DAPM_POST_PMD的合并

2 widget事件的回调函数

代表widget的snd_soc_widget结构,在这个结构体中,有一个event字段用于保存该widget的事件回调函数,同时,event_flags字段用于保存该widget需要关心的dapm事件种类,只有event_flags字段中相应的事件位被设置了的事件才会发到event回调函数中进行处理。
我们知道,dapm为我们提供了常用widget的定义辅助宏,使用以下这几种辅助宏定义widget时,默认需要我们提供dapm event回调函数:

 1 #define SND_SOC_DAPM_MIC(wname, wevent) \
 2 {    .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \
 3     .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
 4     .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
 5 #define SND_SOC_DAPM_HP(wname, wevent) \
 6 {    .id = snd_soc_dapm_hp, .name = wname, .kcontrol_news = NULL, \
 7     .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
 8     .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
 9 #define SND_SOC_DAPM_SPK(wname, wevent) \
10 {    .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \
11     .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
12     .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
13 #define SND_SOC_DAPM_LINE(wname, wevent) \
14 {    .id = snd_soc_dapm_line, .name = wname, .kcontrol_news = NULL, \
15     .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
16     .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}

这些widget都是位于codec外部的器件,它们无法使用通用的寄存器操作来控制widget的电源状态,所以需要我们提供event回调函数。

另外,我们也可以通过以下这些带"_E"后缀的辅助宏版本来定义需要dapm事件的widget:

 1 /* path domain with event - event handler must return 0 for success */
 2 #define SND_SOC_DAPM_PGA_E(wname, wreg, wshift, winvert, wcontrols, \
 3     wncontrols, wevent, wflags) \
 4 {    .id = snd_soc_dapm_pga, .name = wname, \
 5     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
 6     .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \
 7     .event = wevent, .event_flags = wflags}
 8 #define SND_SOC_DAPM_OUT_DRV_E(wname, wreg, wshift, winvert, wcontrols, \
 9     wncontrols, wevent, wflags) \
10 {    .id = snd_soc_dapm_out_drv, .name = wname, \
11     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
12     .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \
13     .event = wevent, .event_flags = wflags}
14 #define SND_SOC_DAPM_MIXER_E(wname, wreg, wshift, winvert, wcontrols, \
15     wncontrols, wevent, wflags) \
16 {    .id = snd_soc_dapm_mixer, .name = wname, \
17     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
18     .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \
19     .event = wevent, .event_flags = wflags}
20 #define SND_SOC_DAPM_MIXER_NAMED_CTL_E(wname, wreg, wshift, winvert, \
21     wcontrols, wncontrols, wevent, wflags) \
22 {       .id = snd_soc_dapm_mixer, .name = wname, \
23     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
24     .kcontrol_news = wcontrols, \
25     .num_kcontrols = wncontrols, .event = wevent, .event_flags = wflags}
26 #define SND_SOC_DAPM_SWITCH_E(wname, wreg, wshift, winvert, wcontrols, \
27     wevent, wflags) \
28 {    .id = snd_soc_dapm_switch, .name = wname, \
29     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
30     .kcontrol_news = wcontrols, .num_kcontrols = 1, \
31     .event = wevent, .event_flags = wflags}
32 #define SND_SOC_DAPM_MUX_E(wname, wreg, wshift, winvert, wcontrols, \
33     wevent, wflags) \
34 {    .id = snd_soc_dapm_mux, .name = wname, \
35     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
36     .kcontrol_news = wcontrols, .num_kcontrols = 1, \
37     .event = wevent, .event_flags = wflags}

3 触发dapm event

经定义好了带有event回调的widget,那么,在那里触发这些dapm event?答案是:在dapm_power_widgets函数的处理过程中,dapm_power_widgets函数详解见linux-alsa详解14之DAPM详解7上下电过程分析 ,其中,在所有需要处理电源变化的widget被分别放入up_list和down_list链表后,会相应地发出各种dapm事件。

3.1  dapm_power_widgets函数

 1 static int dapm_power_widgets(struct snd_soc_card *card, int event)
 2 {
 3         ......
 4         list_for_each_entry(w, &down_list, power_list) {
 5                 dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD);
 6         }
 7  
 8         list_for_each_entry(w, &up_list, power_list) {
 9                 dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU);
10         }
11  
12         /* Power down widgets first; try to avoid amplifying pops. */
13         dapm_seq_run(card, &down_list, event, false);
14  
15         dapm_widget_update(card);
16  
17         /* Now power up. */
18         dapm_seq_run(card, &up_list, event, true);
19         ......
20 }

可见,在真正地进行上电和下电之前,dapm向down_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMD事件,而向up_list链表中的每个widget发出SND_SOC_DAPM_WILL_PMU事件。

3.2 函数dapm_seq_run

在处理上下电的函数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     ...
 5 
 6     list_for_each_entry_safe(w, n, list, power_list) {
 7         ret = 0;
 8 
 9         /* Do we need to apply any queued changes? */
10         if (sort[w->id] != cur_sort || w->reg != cur_reg ||
11             w->dapm != cur_dapm || w->subseq != cur_subseq) {
12             if (!list_empty(&pending))
13                 dapm_seq_run_coalesced(card, &pending);
14 
15             if (cur_dapm && cur_dapm->seq_notifier) {
16                 for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
17                     if (sort[i] == cur_sort)
18                         cur_dapm->seq_notifier(cur_dapm,
19                                        i,
20                                        cur_subseq);
21             }
22 
23   ...
24 }

.3.3 函数dapm_seq_run_coalesced

dapm_seq_run_coalesced也会发出另外几种dapm事件:

 1 /* Apply the coalesced changes from a DAPM sequence */
 2 static void dapm_seq_run_coalesced(struct snd_soc_card *card,
 3                    struct list_head *pending)
 4 {
 5      ...
 6 
 7         /* Check for events */
 8         dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMU);
 9         dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMD);
10     }
11 
12     if (reg >= 0) {
13         /* Any widget will do, they should all be updating the
14          * same register.
15          */
16 
17         pop_dbg(dapm->dev, card->pop_time,
18             "pop test : Applying 0x%x/0x%x to %x in %dms\n",
19             value, mask, reg, card->pop_time);
20         pop_wait(card->pop_time);
21         soc_dapm_update_bits(dapm, reg, mask, value);
22     }
23 
24     list_for_each_entry(w, pending, power_list) {
25         dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMU);
26         dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMD);
27     }
28 }

3.4 dapm_widget_update函数

负责更新音频路径的dapm_widget_update函数中也会发出dapm事件:

改变路径的前后,分别发出了SND_SOC_DAPM_PRE_REG事件和SND_SOC_DAPM_POST_REG事件。

 1 static void dapm_widget_update(struct snd_soc_card *card)
 2 {
 3        ...
 4  
 5         if (!update || !dapm_kcontrol_is_powered(update->kcontrol))
 6                 return;
 7  
 8         wlist = dapm_kcontrol_get_wlist(update->kcontrol);
 9  
10         for (wi = 0; wi < wlist->num_widgets; wi++) {
11                 w = wlist->widgets[wi];
12  
13                 if (w->event && (w->event_flags & SND_SOC_DAPM_PRE_REG)) {
14                         ret = w->event(w, update->kcontrol, SND_SOC_DAPM_PRE_REG);
15                         ......
16                 }
17         }
18  
19         ......
20         /* 更新kcontrol的值,改变音频路径 */
21         ret = soc_widget_update_bits_locked(w, update->reg, update->mask,
22                                   update->val);
23         ......
24  
25         for (wi = 0; wi < wlist->num_widgets; wi++) {
26                 w = wlist->widgets[wi];
27  
28                 if (w->event && (w->event_flags & SND_SOC_DAPM_POST_REG)) {
29                         ret = w->event(w, update->kcontrol, SND_SOC_DAPM_POST_REG);
30                         ......
31                 }
32         }
33 }

4 dai widget和stream widget

4.1 dai widget

详解见linux-alsa详解11之DAPM详解4驱动中widget初始化 ,dai widget又分为cpu dai widget和codec dai widget,它们在machine驱动分别匹配上相应的codec和platform后,由soc_probe_platform和soc_probe_codec这两个函数通过调用dapm的api函数:

snd_soc_dapm_new_dai_widgets

来创建的,通常会为playback和capture各自创建一个dai widget,他们的类型分别是:

1 snd_soc_dapm_dai_in      对应playback dai
2 snd_soc_dapm_dai_out    对应capture dai

另外,dai widget的名字是使用stream name来命名的,他通常来自snd_soc_dai_driver中的stream_name字段。

4.2 stream widget

stream widget通常是指那些要处理音频流数据的widget,它们包含以下这几种类型:

1 snd_soc_dapm_aif_in                 用SND_SOC_DAPM_AIF_IN辅助宏定义
2 snd_soc_dapm_aif_out               用SND_SOC_DAPM_AIF_OUT辅助宏定义
3 snd_soc_dapm_dac                    用SND_SOC_DAPM_AIF_DAC辅助宏定义
4 snd_soc_dapm_adc                    用SND_SOC_DAPM_AIF_ADC辅助宏定义

对于这几种widget,我们除了要指定widget的名字外,还要指定他对应的stream的名字,保存在widget的sname字段中。

4.2 dai widget和stream widget的连接

默认情况下,驱动不会通过snd_soc_route来主动定义dai widget和stream widget之间的连接关系,实际上,他们之间的连接关系是由ASoc负责的,在声卡的初始化函数中,使用snd_soc_dapm_link_dai_widgets函数来建立他们之间的连接关系:

 1 static int snd_soc_instantiate_card(struct snd_soc_card *card)
 2 {
 3     ...
 4 
 5     if (card->dapm_widgets)
 6         snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
 7                       card->num_dapm_widgets);
 8 
 9     if (card->of_dapm_widgets)
10         snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
11                       card->num_of_dapm_widgets);
12 
13     ...
14         /* 建立dai widget和stream widget之间的连接关系  */
15     snd_soc_dapm_link_dai_widgets(card);
16     ...
17 }

分析一下snd_soc_dapm_link_dai_widgets函数,看看它是如何连接这两种widget的,它先是遍历声卡中所有的widget,找出类型为snd_soc_dapm_dai_in和snd_soc_dapm_dai_out的widget,通过widget的priv字段,取出widget对应的snd_soc_dai结构指针:

 1 int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
 2 {
 3     struct snd_soc_dapm_widget *dai_w, *w;
 4     struct snd_soc_dapm_widget *src, *sink;
 5     struct snd_soc_dai *dai;
 6 
 7     /* For each DAI widget... */
 8     list_for_each_entry(dai_w, &card->widgets, list) {
 9         switch (dai_w->id) {//遍历声卡中所有的widget,找出类型为snd_soc_dapm_dai_in和snd_soc_dapm_dai_out的widget
10         case snd_soc_dapm_dai_in:
11         case snd_soc_dapm_dai_out:
12             break;
13         default:
14             continue;
15         }
16 
17         dai = dai_w->priv;//通过widget的priv字段,取出widget对应的snd_soc_dai结构指针
18         /*再次从头遍历声卡中所有的widget,找出能与dai widget相连接的stream widget,第一个前提条件是这两个widget必须位于同一个dapm context中*/
19         /* ...find all widgets with the same stream and link them */
20         list_for_each_entry(w, &card->widgets, list) {
21             if (w->dapm != dai_w->dapm)//两个widget必须位于同一个dapm context中
22                 continue;
23 
24             switch (w->id) {//dai widget不会与dai widget相连,所以跳过它们
25             case snd_soc_dapm_dai_in:
26             case snd_soc_dapm_dai_out:
27                 continue;
28             default:
29                 break;
30             }
31 
32             if (!w->sname || !strstr(w->sname, dai_w->sname))//dai widget的名字没有出现在要连接的widget的stream name中,跳过这个widget
33                 continue;
34 
35             if (dai_w->id == snd_soc_dapm_dai_in) {//如果widget的stream name包含了dai的stream name,则匹配成功,连接这两个widget
36                 src = dai_w;
37                 sink = w;
38             } else {
39                 src = w;
40                 sink = dai_w;
41             }
42             dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
43             snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
44         }
45     }
46 
47     return 0;
48 }

由此可见,dai widget和stream widget是通过stream name进行匹配的,所以,我们在定义codec的stream widget时,它们的stream name必须要包含dai的stream name,这样才能让ASoc自动把这两种widget连接在一起,只有把它们连接在一起,ASoc中的播放、录音和停止等事件,才能通过dai widget传递到codec中,使得codec中的widget能根据目前的播放状态,动态地开启或关闭音频路径上所有widget的电源。
以linux-4.9.73\sound\soc\codecs\wm8993.c为例:

分别定义了左右声道两个stream name为Capture和Playback的stream widget:

1 SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
2 SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
3 
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),

对应的dai driver结构定义如下:

 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 }

可见,它们的stream name是一样的,声卡初始化阶段会把它们连接在一起。需要注意的是,如果我们定义了snd_soc_dapm_aif_in和snd_soc_dapm_aif_out类型的stream widget,并指定了他们的stream name。

在定义DAC或ADC对应的widget时,它们的stream name最好不要也使用相同的名字,否则,dai widget即会连接上AIF,也会连接上DAC/ADC,造成音频路径的混乱:

1 SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0),
2 SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0),
3 
4 SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0),
5 SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0),

5 stream event

把dai widget和stream widget连接在一起,就是为了能把ASoc中的pcm处理部分和dapm进行关联,pcm的处理过程中,会通过发出stream event来通知dapm系统,重新扫描并调整音频路径上各个widget的电源状态,目前dapm提供了以下几种stream event:

1 /* dapm stream operations */
2 #define SND_SOC_DAPM_STREAM_NOP            0x0
3 #define SND_SOC_DAPM_STREAM_START        0x1
4 #define SND_SOC_DAPM_STREAM_STOP        0x2
5 #define SND_SOC_DAPM_STREAM_SUSPEND        0x4
6 #define SND_SOC_DAPM_STREAM_RESUME        0x8
7 #define SND_SOC_DAPM_STREAM_PAUSE_PUSH    0x10
8 #define SND_SOC_DAPM_STREAM_PAUSE_RELEASE    0x20

比如,在soc_pcm_prepare函数中,会发出SND_SOC_DAPM_STREAM_START事件:

linux-4.9.73\sound\soc\soc-pcm.c

 1 static int soc_pcm_prepare(struct snd_pcm_substream *substream)
 2 {
 3     ...
 4 
 5     /* cancel any delayed stream shutdown that is pending */
 6     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
 7         rtd->pop_wait) {
 8         rtd->pop_wait = 0;
 9         cancel_delayed_work(&rtd->delayed_work);
10     }
11 
12     snd_soc_dapm_stream_event(rtd, substream->stream,
13             SND_SOC_DAPM_STREAM_START);
14 
15     ...
16 }

而在soc_pcm_close函数中,会发出SND_SOC_DAPM_STREAM_STOP事件:

 1 static int soc_pcm_close(struct snd_pcm_substream *substream)
 2 {
 3         ...
 4 
 5     if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 6         if (snd_soc_runtime_ignore_pmdown_time(rtd)) {
 7             /* powered down playback stream now */
 8             snd_soc_dapm_stream_event(rtd,
 9                           SNDRV_PCM_STREAM_PLAYBACK,
10                           SND_SOC_DAPM_STREAM_STOP);
11         } else {
12             /* start delayed pop wq here for playback streams */
13             rtd->pop_wait = 1;
14             queue_delayed_work(system_power_efficient_wq,
15                        &rtd->delayed_work,
16                        msecs_to_jiffies(rtd->pmdown_time));
17         }
18     } else {
19         /* capture streams can be powered down now */
20         snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE,
21                       SND_SOC_DAPM_STREAM_STOP);
22     }
23 
24     ...
25 }

snd_soc_dapm_stream_event函数最终会使用soc_dapm_stream_event函数来完成具体的工作:

1 void snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
2                   int event)
3 {
4     struct snd_soc_card *card = rtd->card;
5 
6     mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
7     soc_dapm_stream_event(rtd, stream, event);
8     mutex_unlock(&card->dapm_mutex);
9 }

5.1 函数soc_dapm_stream_event

 1 static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
 2     int event)
 3 {
 4     int i;
 5 
 6     soc_dapm_dai_stream_event(rtd->cpu_dai, stream, event);//从snd_soc_pcm_runtime结构中取出cpu dai widget
 7     for (i = 0; i < rtd->num_codecs; i++)//从snd_soc_pcm_runtime结构中取出codec dai widget
 8         soc_dapm_dai_stream_event(rtd->codec_dais[i], stream, event);
 9 
10     dapm_power_widgets(rtd->card, event);//熟悉的dapm_power_widgets函数,详见linux-alsa详解14之DAPM详解7上下电过程分析 5.1
11 }

继续调用函数soc_dapm_dai_stream_event

 1 static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream,
 2     int event)
 3 {
 4     struct snd_soc_dapm_widget *w;
 5     unsigned int ep;
 6 
 7     if (stream == SNDRV_PCM_STREAM_PLAYBACK)
 8         w = dai->playback_widget;
 9     else
10         w = dai->capture_widget;
11 
12     if (w) {
13         dapm_mark_dirty(w, "stream event");//把cpu/codec dai widget加入到dapm_dirty链表中,根据stream event的类型,把cpu dai widget设定为激活状态或非激活状态
14 
15         if (w->id == snd_soc_dapm_dai_in) {
16             ep = SND_SOC_DAPM_EP_SOURCE;
17             dapm_widget_invalidate_input_paths(w);
18         } else {
19             ep = SND_SOC_DAPM_EP_SINK;
20             dapm_widget_invalidate_output_paths(w);
21         }
22 
23         switch (event) {
24         case SND_SOC_DAPM_STREAM_START:
25             w->active = 1;
26             w->is_ep = ep;
27             break;
28         case SND_SOC_DAPM_STREAM_STOP:
29             w->active = 0;
30             w->is_ep = 0;
31             break;
32         case SND_SOC_DAPM_STREAM_SUSPEND:
33         case SND_SOC_DAPM_STREAM_RESUME:
34         case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
35         case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
36             break;
37         }
38     }
39 }

因为dai widget和codec上的stream widget是相连的,所以,dai widget的激活状态改变,会沿着音频路径传递到路径上的所有widget,等dapm_power_widgets返回后,如果发出的是SND_SOC_DAPM_STREAM_START事件,路径上的所有widget会处于上电状态,保证音频数据流的顺利播放,如果发出的是SND_SOC_DAPM_STREAM_STOP事件,路径上的所有widget会处于下电状态,保证最小的功耗水平。

参考博文:https://blog.csdn.net/DroidPhone/java/article/details/14548631

posted @ 2020-06-25 16:45  Action_er  阅读(1219)  评论(0)    收藏  举报