dapm触发时的入口函数是dapm_power_widgets,稍后详细分析这个函数,这里仅说其作用:检查每个dapm widget,如果该widget处在一条complete paths中,则power up这个widget,否则power down。
1、dapm widgets建立时,详见snd_soc_dapm_new_widgets;
2、上层通过alsa_amixer等工具改变codec音频路径时,此时与此相关的widgets状态要重置,详见dapm_mixer_update_power和dapm_mux_update_power;
amixer-应用层[alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1] |->snd_ctl_ioctl-系统调用 |->snd_ctl_elem_write_user-内核钩子函数 |->snd_ctl_elem_wirte- |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl |->kctl->put()-调用kctl的成员函数put() |->snd_soc_dapm_put_volsw |->dapm_mixer_update_power |->更新path->connect状态 |->dapm_power_widgets 触发dapm,重置相关的widgets
3、发生stream事件时,会触发snd_soc_dapm_stream_even。什么叫stream事件?准备或关闭一个pcm stream通道(snd_pcm_prepare/snd_pcm_close)这些都属于stream事件。另外suspend或resume时,也会触发snd_soc_dapm_stream_event处理。
snd_pcm_prepare |->soc_pcm_prepare |->处理platform、codec-dai、cpu-dai的prepare回调函数 |->snd_soc_dapm_stream_event |->遍历codec每个dapm widget,如果该widget的stream name与传递进来的stream参数相匹配,如果匹配则置widget->active为真 |->dapm_power_widgets 触发dapm,重置相关的widgets
1、初始化两个链表up_list和down_list,如字面意思,up_list指向要power up的widgets,down_list指向要power down的widgets;
2、遍历所有widgets,检查是否需要对其进行power操作;要power up的则插入到up_list,要power down的则插入到down_list;
3、先power down down_list上widgets,再power up up_list上的widgets;
4、设置codec的偏置(bias)电压。
/* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- * * o DAC to output pin. * o Input Pin to ADC. * o Input pin to Output pin (bypass, sidetone) * o DAC to ADC (loopback). */ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; //初始化两个链表up_list和down_list,up_list指向要power up的widgets,down_list指向要power down的widgets LIST_HEAD(up_list); LIST_HEAD(down_list); int ret = 0; int power; int sys_power = 0; /* Check which widgets we need to power and store them in * lists indicating if they should be powered up or down. */ //遍历所有的dapm widgets,检查是否需要对widget开关;'开'则把该widget插入到up_list,'关'则插入到down_list list_for_each_entry(w, &codec->dapm_widgets, list) { switch (w->id) { case snd_soc_dapm_pre: //属machine specific pre widget,插入到down_list最前方 dapm_seq_insert(w, &down_list, dapm_down_seq); break; case snd_soc_dapm_post: //属machine specific post widget,插入到up_list最后方 dapm_seq_insert(w, &up_list, dapm_up_seq); break; default: //其他类型的widgets,则调用自身的power_check函数进行检查需要开关。 //关于power_check,具体见《DAPM之五:dapm机制深入分析(上)》第四、第五小节。非常重要的一个函数。 if (!w->power_check) continue; /* If we're suspending then pull down all the * power. */ switch (event) { case SND_SOC_DAPM_STREAM_SUSPEND: //上面注释很清楚了,如果是suspend事件,则pull down所有widgets。 power = 0; break; default: power = w->power_check(w); if (power) sys_power = 1; break; } //w->power保存widget当前的power状态,如果当前状态和设置状态一致,那么显然不用重复设置widget if (w->power == power) continue; //将widget插入到up_list或down_list中 if (power) dapm_seq_insert(w, &up_list, dapm_up_seq); else dapm_seq_insert(w, &down_list, dapm_down_seq); //更新w->power的状态 w->power = power; break; } } /* If there are no DAPM widgets then try to figure out power from the * event type. */ if (list_empty(&codec->dapm_widgets)) { switch (event) { case SND_SOC_DAPM_STREAM_START: case SND_SOC_DAPM_STREAM_RESUME: sys_power = 1; break; case SND_SOC_DAPM_STREAM_SUSPEND: sys_power = 0; break; case SND_SOC_DAPM_STREAM_NOP: sys_power = codec->bias_level != SND_SOC_BIAS_STANDBY; break; default: break; } } /* If we're changing to all on or all off then prepare */ if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) || (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) { ret = snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_PREPARE); if (ret != 0) pr_err("Failed to prepare bias: %d\n", ret); } //先power down链表down_list上的widgets,接着power up链表up_list上的widgets //按照这样的次序,目的是避免产生pop音 //dapm_seq_run核心函数,见其详细分析 /* Power down widgets first; try to avoid amplifying pops. */ dapm_seq_run(codec, &down_list, event, dapm_down_seq); /* Now power up. */ dapm_seq_run(codec, &up_list, event, dapm_up_seq); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { ret = snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_STANDBY); if (ret != 0) pr_err("Failed to apply standby bias: %d\n", ret); } /* If we just powered up then move to active bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) { ret = snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON); if (ret != 0) pr_err("Failed to apply active bias: %d\n", ret); } pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n", codec->pop_time); return 0; }
/* Apply a DAPM power sequence. * * We walk over a pre-sorted list of widgets to apply power to. In * order to minimise the number of writes to the device required * multiple widgets will be updated in a single write where possible. * Currently anything that requires more than a single write is not * handled. */ static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, int event, int sort[]) { struct snd_soc_dapm_widget *w, *n; //创建pending链表 LIST_HEAD(pending); int cur_sort = -1; int cur_reg = SND_SOC_NOPM; int ret; //遍历list(即up_list或down_list),根据成员power_list找到挂到list上的每一个widget list_for_each_entry_safe(w, n, list, power_list) { ret = 0; /* Do we need to apply any queued changes? */ if (sort[w->id] != cur_sort || w->reg != cur_reg) { if (!list_empty(&pending)) dapm_seq_run_coalesced(codec, &pending); INIT_LIST_HEAD(&pending); cur_sort = -1; cur_reg = SND_SOC_NOPM; } switch (w->id) { //为什么类型为pre/post的widget只执行event回调函数?看看它们的原型就明白了。 //#define SND_SOC_DAPM_PRE(wname, wevent),显然这些widget只含有stream name和event回调函数。 case snd_soc_dapm_pre: if (!w->event) list_for_each_entry_safe_continue(w, n, list, power_list); if (event == SND_SOC_DAPM_STREAM_START) ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); else if (event == SND_SOC_DAPM_STREAM_STOP) ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); break; case snd_soc_dapm_post: if (!w->event) list_for_each_entry_safe_continue(w, n, list, power_list); if (event == SND_SOC_DAPM_STREAM_START) ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMU); else if (event == SND_SOC_DAPM_STREAM_STOP) ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); break; case snd_soc_dapm_input: case snd_soc_dapm_output: case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_line: case snd_soc_dapm_spk: /* No register support currently */ //这里就比较奇怪了,input/output/hp/mic/line/spk这些widgets也只有stream name和event, //但是仍然调用dapm_generic_apply_power试图控制widget的开关?根据这里的注释,应该是预留的。 ret = dapm_generic_apply_power(w); break; default: /* Queue it up for application */ //遇到非以上类型的widget,则插入到pending链表,进一步调用dapm_seq_run_coalesced处理。 //这里设计很巧妙!下面详细解析这点。 cur_sort = sort[w->id]; cur_reg = w->reg; list_move(&w->power_list, &pending); break; } if (ret < 0) pr_err("Failed to apply widget power: %d\n", ret); } if (!list_empty(&pending)) dapm_seq_run_coalesced(codec, &pending); }从dapm_seq_run的分析,我们可以看出,mixer/mux类型的widgets处理是不同的。
/* Do we need to apply any queued changes? */ if (sort[w->id] != cur_sort || w->reg != cur_reg) { if (!list_empty(&pending)) dapm_seq_run_coalesced(codec, &pending); INIT_LIST_HEAD(&pending); cur_sort = -1; cur_reg = SND_SOC_NOPM; }
/* Queue it up for application */ cur_sort = sort[w->id]; cur_reg = w->reg; list_move(&w->power_list, &pending);
结合这两段代码理解:遇到操作对象是同一个reg的widgets,则把他们放入pending链表中,随后调用dapm_seq_run_coalesced进行处理。这样做的意义何在?见dapm_seq_run注释:In order to minimise the number of writes to the device required multiple widgets will be updated in a single write where possible.保证了同reg但不同widgets的一次性读写。这设计是相当巧妙高效的。
dapm_seq_run_coalesced就不累述了,比较简单,对同reg但不同widgets进行一次读写。