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
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号