哲学里有三个终极命题:你是谁?从何处来?到何处去?而对于dapm机制,我们可以这样提问:dapm是什么,起到何作用?dapm是如何建立的?dapm又是如何触发的?下面会就这三个问题进行分析。
首先是dapm是什么?这在DAPM之一:概述中提及了,就是音频电源动态管理。相信电源管理大家都不会陌生。dapm设计的目的就是只有需要时才打开必要的部件(widget),不需要时则关闭部件,达到省电的目的,这在便携设备中非常重要。这里“需要”的意思是音频播放录音等操作,“部件”的意思是ADC、DAC、PGA、、MIXER、SPK等等。除此之外,dapm还肩负一个重要角色,就是音频路径,关于这,相信在DAPM之二:audio paths与dapm kcontrol已经描述得足够详细了。因此,这篇仅对电源管理这块做一些分析。
我们先分析dapm模块(源码soc-dapm.c)中的一些重要函数:
一、dapm_update_bits
更新widget中的reg的值,一般可以认为控制widget的开关。
二、snd_soc_update_bits
更新指定寄存器reg、指定位mask的值。
三、dapm_set_pga
这个函数会在dapm触发时调用,主要是为了减少通电/断电PGA产生的POP音。如下详细分析了这个函数:
-
-
- static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power)
- {
-
-
- const struct snd_kcontrol_new *k = widget->kcontrols;
-
-
- if (widget->muted && !power)
- return 0;
- if (!widget->muted && power)
- return 0;
-
- if (widget->num_kcontrols && k) {
-
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)k->private_value;
- unsigned int reg = mc->reg;
- unsigned int shift = mc->shift;
- int max = mc->max;
- unsigned int mask = (1 << fls(max)) - 1;
- unsigned int invert = mc->invert;
-
- if (power) {
- int i;
-
-
-
- if (invert) {
- for (i = max; i > widget->saved_value; i--)
- snd_soc_update_bits(widget->codec, reg, mask, i);
- } else {
- for (i = 0; i < widget->saved_value; i++)
- snd_soc_update_bits(widget->codec, reg, mask, i);
- }
- widget->muted = 0;
- } else {
-
-
- int val = snd_soc_read(widget->codec, reg);
- int i = widget->saved_value = (val >> shift) & mask;
- if (invert) {
- for (; i < mask; i++)
- snd_soc_update_bits(widget->codec, reg, mask, i);
- } else {
- for (; i > 0; i--)
- snd_soc_update_bits(widget->codec, reg, mask, i);
- }
- widget->muted = 1;
- }
- }
- return 0;
- }
这里有个建议:当遇到PGA部件时,最好按照规范来写这个dapm widget,该widget应包含gain调整的kcontrol,这样系统会减少通电/断电时可能带来的POP音。
小结:dapm触发时会调用dapm_set_pga,控制PGA widgets的音量逐步增大/减少,目的是减少通电/断电时可能带来的POP音。
四、is_connected_output_ep
1、is_connected_output_ep和is_connected_input_ep,及其endpoint
is_connected_output_ep挺有趣的,widget一路往后走,直至达到output endpoint,统计widget到endpoints的路径数目。同理is_connected_input_ep是取得widget连接到input endpoint的路径数目,但走的方向是相反的。目的是检查路径的完整性,保证通道是贯穿input endpoint和output endpoint的。
何为output endpoint?snd_soc_dapm_adc、snd_soc_dapm_aif_out、snd_soc_dapm_output、snd_soc_dapm_hp、snd_soc_dapm_spk这些类型的widgets就是。
何为input endpoint?snd_soc_dapm_dac、snd_soc_dapm_aif_in、snd_soc_dapm_input、snd_soc_dapm_vmid、snd_soc_dapm_mic这些类型的widgets就是。
如果连接到output endpoint的路径数目为0或者连接到input endpoint的路径数目为0,那么说明该widget没有构成任意合法的完整的音频路径,因此也就应该关闭该widget;如果该widget有连接,则需要打开该widget。在dapm_power_widgets里详细分析。【实话说,代码注释是这样,但往下走到dapm_power_widgets太复杂了,我暂时未能完全分析。】
2、path->connect
连接状态是根据path->connect来判断的,改变mixer/mux部件的path->connect有两个地方:
(1)、在dapm kcontrols建立时,会检查mixer/mux的连接状态,具体见dapm_set_path_status:判定这个path对应的source是否被选中了,是则置p->connect = 1,否则置p->connect = 0。这个过程在<DAPM之二:audio paths与dapm kcontrol>有具体的描述。
(2)、上层通过alsa_amixer或其他来操作dapm kcontrols时,会更新widget路径的连接状态path->connect,简略流程如下:
- 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
pga、adc、dac、input、output等部件初始化path->connect=1,hp、spk、mic等部件初始化path->connect=0,具体见snd_soc_dapm_add_route函数。
3、path的相关链表
每一个mixer/mux widget都有数个path,path的数目与mixer/mux widget的kcontrols数目是一致的,有N个kcontrols,就可以构成N个path。每个path都会通过list_add(&path->list, &codec->dapm_paths)添加到链表codec->dapm_paths上;每个path,都会通过INIT_LIST_HEAD(&path->list_sink)创建一个path->list_sink节点,然后通过list_add(&path->list_sink, &wsink->sources)将节点添加到链表wsink->sources上;每个path,都会通过INIT_LIST_HEAD(&path->list_source)创建一个path->list_source节点,然后通过list_add(&path->list_source, &wsource->sinks)将节点添加到链表wsource->sink上。每个path都会挂到source widget和sink widget上的链表上,因此可通过widget->sources找到该widget作为sources的所有paths,通过widget->sinks找到该widget作为sinks的所有paths。
path的建立过程分析见DAPM之二:audio paths与dapm kcontrol,这里为了方便理解,画了一个示意图。其实这些应该要放到之前章节的,写得有点乱了,后面会整理一个有条理的文档出来。
4、is_connected_output_ep流程
理解以上三点,应该可以探讨is_connected_output_ep的流程了。is_connected_input_ep类似的。图解一下:
图中浅绿色为input endpoint,浅蓝色为output endpoint。如红色线路对于Input Mixer,它如何找到到达output endpoint的路径呢?
(1)、首先会找到Input Mixer->sinks,发现有两个分别是ADC和Output Mixer;
(2)、检查Input Mixer到ADC这个path是否连接,接着检查到ADC是属于snd_soc_dapm_adc类型的widget,返回1,因为这个widget属于output endpoint类型并且是连接上的;
(3)、检查Input Mixer到Output Mixer这个path是否连接的,如果不是则跳出;如果是则继续往下走,由于这个widget不属于output endpoint,继续找Output Mixer->sinks,有两个分别是HP和SPK;
(4)、再检查到HP是连接到Output Mixer,而且HP也属于output endpoint,因此Input Mixer->Output Mixer->HP这个通道也是贯穿output endpoint的。
以上是is_connected_output_ep的过程,至于is_connected_input_ep,则遍历的方向是相反的(即widget->sources),往前遍历到MIC(input endpoint)。当然已走过的path会置walked标识,不会重复检查。
只有is_connected_output_ep和is_connected_input_ep同时不为0(即往前能到input endpoint,往后能到output endpoint),才认为widget位于一条完整合法的音频通道中。
-
-
-
-
- static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
- {
- struct snd_soc_dapm_path *path;
- int con = 0;
-
- if (widget->id == snd_soc_dapm_supply)
- return 0;
-
-
- switch (widget->id) {
- case snd_soc_dapm_adc:
- case snd_soc_dapm_aif_out:
- if (widget->active)
- return 1;
- default:
- break;
- }
-
-
- if (widget->connected) {
-
- if (widget->id == snd_soc_dapm_output && !widget->ext)
- return 1;
-
-
- if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
- (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))
- return 1;
- }
-
-
-
-
-
- list_for_each_entry(path, &widget->sinks, list_source) {
-
- if (path->walked)
- continue;
-
- if (path->sink && path->connect) {
- path->walked = 1;
- con += is_connected_output_ep(path->sink);
- }
- }
-
- return con;
- }
首先要理解INIT_LIST_HEAD、list_add和list_for_each_entry等Linux kernel链表操作,这里不多提。对于list_for_each_entry(path, &widget->sinks, list_source),链表头head是widget->sinks,member是list_source,这是结构体struct snd_soc_dapm_path中的一个成员。
注意3中,path建立时链表的操作:list_add(&path->list_source, &wsource->sinks),这里将节点path->list_source添加到链表wsource->sinks中,那么如何理解wsource->sinks呢?我认为是记录连接到wsource上的每一个sink(同理wsink->sources用于记录连接到wsink上的每一个source)。因此,得到一个widget,无论角度往前遍历还是往后遍历,都可找到所有的paths。
小结:这部分写得太多了,因为细节来说的确比较复杂(我改了又改,现在还不能保证细节上是否还有错的地方),要掌握endpoint的概念、path连接状态是如何更新的、什么是完整的音频路径complete paths,难点是path的链表分析。但原理是简单的,看以上的图解就比较清晰了。这两个函数检查一个widget是否同时贯穿input endpoint和output endpoint,是则认为该widget要power up。
五、dapm_generic_check_power
-
-
- static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
- {
- int in, out;
-
- in = is_connected_input_ep(w);
- dapm_clear_walk(w->codec);
- out = is_connected_output_ep(w);
- dapm_clear_walk(w->codec);
- return out != 0 && in != 0;
- }
检查一个widget是否需要打开,调用is_connected_input_ep检查该widget是否连接到input endpoint,is_connected_output_ep检查该widget是否连接到output endpoint。一个widget只有同时连接到input endpoint和output endpoint,才认为这个widget需要打开。
这其实是一系列的函数,分别是dapm_generic_check_power、dapm_adc_check_power、dapm_dac_check_power、dapm_supply_check_power,视widget的类型使用。如mixer/mux的widget,有w->power_check = dapm_generic_check_power;adc/aif_out的widget,有w->power_check = dapm_adc_check_power。具体见snd_soc_dapm_new_widgets。
六、dapm_generic_apply_power
核心函数,开关指定的widget,dapm机制绕来绕去最终就是为了控制widget的开和关。但是原理是非常简单的,复杂的是其他部分,如检查widget是否贯穿input/out endpoint、power up/down list顺序等前期处理细节。
-
-
-
- static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w)
- {
- int ret;
-
-
- if (w->event)
- pr_debug("power %s event for %s flags %x\n",
- w->power ? "on" : "off",
- w->name, w->event_flags);
-
-
-
-
-
- if (w->power && w->event &&
- (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
- ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
- if (ret < 0)
- return ret;
- }
-
-
- if (!w->power && w->event &&
- (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
- ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
- if (ret < 0)
- return ret;
- }
-
-
-
-
- if (w->id == snd_soc_dapm_pga && !w->power)
- dapm_set_pga(w, w->power);
-
-
- dapm_update_bits(w);
-
-
-
-
- if (w->id == snd_soc_dapm_pga && w->power)
- dapm_set_pga(w, w->power);
-
-
-
-
-
- if (w->power && w->event &&
- (w->event_flags & SND_SOC_DAPM_POST_PMU)) {
- ret = w->event(w,
- NULL, SND_SOC_DAPM_POST_PMU);
- if (ret < 0)
- return ret;
- }
-
-
- if (!w->power && w->event &&
- (w->event_flags & SND_SOC_DAPM_POST_PMD)) {
- ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
- if (ret < 0)
- return ret;
- }
-
- return 0;
- }
七、dapm_seq_insert
-
- static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
- struct list_head *list,
- int sort[])
- {
- struct snd_soc_dapm_widget *w;
-
- list_for_each_entry(w, list, power_list)
- if (dapm_seq_compare(new_widget, w, sort) < 0) {
- list_add_tail(&new_widget->power_list, &w->power_list);
- return;
- }
-
- list_add_tail(&new_widget->power_list, list);
- }
这个函数的作用是:将widget按照一定的顺序插入到链表list中。在power up/down时,为了减少可能由此产生的POP音,必须按照一定的顺序来操作这些widgets,这个顺序就是由dapm_seq_insert来指定的。
power down时,顺序如下:
- static int dapm_down_seq[] = {
- [snd_soc_dapm_pre] = 0,
- [snd_soc_dapm_adc] = 1,
- [snd_soc_dapm_hp] = 2,
- [snd_soc_dapm_spk] = 2,
- [snd_soc_dapm_pga] = 4,
- [snd_soc_dapm_mixer_named_ctl] = 5,
- [snd_soc_dapm_mixer] = 5,
- [snd_soc_dapm_dac] = 6,
- [snd_soc_dapm_mic] = 7,
- [snd_soc_dapm_micbias] = 8,
- [snd_soc_dapm_mux] = 9,
- [snd_soc_dapm_value_mux] = 9,
- [snd_soc_dapm_aif_in] = 10,
- [snd_soc_dapm_aif_out] = 10,
- [snd_soc_dapm_supply] = 11,
- [snd_soc_dapm_post] = 12,
- };
power up时,顺序如下:
- static int dapm_up_seq[] = {
- [snd_soc_dapm_pre] = 0,
- [snd_soc_dapm_supply] = 1,
- [snd_soc_dapm_micbias] = 2,
- [snd_soc_dapm_aif_in] = 3,
- [snd_soc_dapm_aif_out] = 3,
- [snd_soc_dapm_mic] = 4,
- [snd_soc_dapm_mux] = 5,
- [snd_soc_dapm_value_mux] = 5,
- [snd_soc_dapm_dac] = 6,
- [snd_soc_dapm_mixer] = 7,
- [snd_soc_dapm_mixer_named_ctl] = 7,
- [snd_soc_dapm_pga] = 8,
- [snd_soc_dapm_adc] = 9,
- [snd_soc_dapm_hp] = 10,
- [snd_soc_dapm_spk] = 10,
- [snd_soc_dapm_post] = 11,
- };
可以看出,这个顺序是合理的,符合音频depop常规原理。
总结:dapm机制分析上篇就到这里吧,介绍了一些重要的子函数细节,对于dapm机制提及不多,下篇会涉及到dapm的建立和触发过程。其实就我个人而言,最难理解的就是这些细节了。为了尽量消除POP,dapm_set_pga负责pga部件的depop,dapm_seq_insert负责组织widgets power up/down操作次序;power_check系列函数用于检查指定widget是否需要power up,由此又涉及到endpoint、complete paths等概念