DAPM之五:dapm机制深入分析(上)

原作者:sepnic      博客:http://blog.csdn.net/sepnic/

原文:http://blog.csdn.net/sepnic/article/details/6428885

请移步原文地址参与讨论。


哲学里有三个终极命题:你是谁?从何处来?到何处去?而对于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音。如下详细分析了这个函数:

[cpp]  view plain copy
  1. /* ramps the volume up or down to minimise pops before or after a 
  2.  * DAPM power event */  
  3. static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power)  
  4. {  
  5.     //取得该widget的kcontrol,这个kcontrol应该是用于调节PGA的音量的,请留意这点。  
  6.     //对于PGA widget,一般是单路输入,单路输出,带gain调整。  
  7.     const struct snd_kcontrol_new *k = widget->kcontrols;   
  8.   
  9.     //检查该widget是否为muted,从下面看来,当widget power up时,置muted=0,反之置1  
  10.     if (widget->muted && !power)  
  11.         return 0;  
  12.     if (!widget->muted && power)  
  13.         return 0;  
  14.   
  15.     if (widget->num_kcontrols && k) {  
  16.         //以下操作取得kcontrol的reg、shift和mask等,用于调整该PGA widget的增益gain。  
  17.         struct soc_mixer_control *mc =  
  18.             (struct soc_mixer_control *)k->private_value;  
  19.         unsigned int reg = mc->reg;  
  20.         unsigned int shift = mc->shift;  
  21.         int max = mc->max;  
  22.         unsigned int mask = (1 << fls(max)) - 1;  
  23.         unsigned int invert = mc->invert;  
  24.   
  25.         if (power) {  
  26.             int i;  
  27.             /* power up has happended, increase volume to last level */  
  28.             //power up widget,则将保存值恢复到widget。注意:这是一个逐步的过程,  
  29.             //从0至保存值saved_value逐步恢复,我想是这是为了减少POP。  
  30.             if (invert) {  
  31.                 for (i = max; i > widget->saved_value; i--)  
  32.                     snd_soc_update_bits(widget->codec, reg, mask, i);  
  33.             } else {  
  34.                 for (i = 0; i < widget->saved_value; i++)  
  35.                     snd_soc_update_bits(widget->codec, reg, mask, i);  
  36.             }  
  37.             widget->muted = 0;   
  38.         } else {  
  39.             /* power down is about to occur, decrease volume to mute */  
  40.             //power down widget,保存widget当前的gain值,并逐步减少该widget的音量,直至0。  
  41.             int val = snd_soc_read(widget->codec, reg);  
  42.             int i = widget->saved_value = (val >> shift) & mask;  
  43.             if (invert) {  
  44.                 for (; i < mask; i++)  
  45.                     snd_soc_update_bits(widget->codec, reg, mask, i);  
  46.             } else {  
  47.                 for (; i > 0; i--)  
  48.                     snd_soc_update_bits(widget->codec, reg, mask, i);  
  49.             }  
  50.             widget->muted = 1;  
  51.         }  
  52.     }  
  53.     return 0;  
  54. }  
这里有个建议:当遇到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,简略流程如下:

[cpp]  view plain copy
  1. amixer-应用层[alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1]    
  2.   |->snd_ctl_ioctl-系统调用    
  3.        |->snd_ctl_elem_write_user-内核钩子函数    
  4.             |->snd_ctl_elem_wirte-    
  5.                  |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl    
  6.                  |->kctl->put()-调用kctl的成员函数put()    
  7.                       |->snd_soc_dapm_put_volsw  
  8.                            |->dapm_mixer_update_power  
  9.                               更新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,这里为了方便理解,画了一个示意图。其实这些应该要放到之前章节的,写得有点乱了,后面会整理一个有条理的文档出来。

DAPM之五:dapm机制深入分析(上)_第1张图片

4、is_connected_output_ep流程

理解以上三点,应该可以探讨is_connected_output_ep的流程了。is_connected_input_ep类似的。图解一下:

DAPM之五:dapm机制深入分析(上)_第2张图片

图中浅绿色为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位于一条完整合法的音频通道中。

[cpp]  view plain copy
  1. /* 
  2.  * Recursively check for a completed path to an active or physically connected 
  3.  * output widget. Returns number of complete paths. 
  4.  */  
  5. static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)  
  6. {  
  7.     struct snd_soc_dapm_path *path;  
  8.     int con = 0;  
  9.   
  10.     if (widget->id == snd_soc_dapm_supply)  
  11.         return 0;  
  12.   
  13.     //如果widget类型是adc或aif_out,则到达endpoint,置widget->endpoint路径合法。  
  14.     switch (widget->id) {  
  15.     case snd_soc_dapm_adc:  
  16.     case snd_soc_dapm_aif_out:  
  17.         if (widget->active)  
  18.             return 1;  
  19.     default:  
  20.         break;  
  21.     }  
  22.   
  23.     //如果widget类型是output、hp、spk等,则到达endpoint,置widget->endpoint路径合法。  
  24.     if (widget->connected) {  
  25.         /* connected pin ? */  
  26.         if (widget->id == snd_soc_dapm_output && !widget->ext)  
  27.             return 1;  
  28.   
  29.         /* connected jack or spk ? */  
  30.         if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||  
  31.             (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))  
  32.             return 1;  
  33.     }  
  34.   
  35.     //这里比较难以理解,dapm的链表很复杂,后面具体提一下,但不保证能表达清楚。  
  36.     //这里递归遍历widget->sink,直至到达一个endpoint或者遍历完毕,过程中检查path连接状态。  
  37.     //is_connected_output_ep是将widget作为source往前递归找到output endpoint;  
  38.     //is_connected_input_ep是将widget作为sink往后递归找到input endpoint。  
  39.     list_for_each_entry(path, &widget->sinks, list_source) {  
  40.         //这条路径已走过,找下一个path  
  41.         if (path->walked)  
  42.             continue;  
  43.   
  44.         if (path->sink && path->connect) {  
  45.             path->walked = 1;  
  46.             con += is_connected_output_ep(path->sink);  
  47.         }  
  48.     }  
  49.   
  50.     return con;  
  51. }  
首先要理解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

[cpp]  view plain copy
  1. /* Generic check to see if a widget should be powered. 
  2.  */  
  3. static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)  
  4. {  
  5.     int in, out;  
  6.   
  7.     in = is_connected_input_ep(w);  
  8.     dapm_clear_walk(w->codec);  
  9.     out = is_connected_output_ep(w);  
  10.     dapm_clear_walk(w->codec);  
  11.     return out != 0 && in != 0;  
  12. }  
检查一个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顺序等前期处理细节。

[cpp]  view plain copy
  1. /* Standard power change method, used to apply power changes to most 
  2.  * widgets. 
  3.  */  
  4. static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w)  
  5. {  
  6.     int ret;  
  7.   
  8.     /* call any power change event handlers */  
  9.     if (w->event)  
  10.         pr_debug("power %s event for %s flags %x\n",  
  11.              w->power ? "on" : "off",  
  12.              w->name, w->event_flags);  
  13.   
  14.     //在开关widget前,先执行标识为SND_SOC_DAPM_PRE_PMU/SND_SOC_DAPM_PRE_PMD的event  
  15.     //回调函数分支。event flag定义见dapm.h。  
  16.   
  17.     /* power up pre event */  
  18.     if (w->power && w->event &&  
  19.         (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {  
  20.         ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);  
  21.         if (ret < 0)  
  22.             return ret;  
  23.     }  
  24.   
  25.     /* power down pre event */  
  26.     if (!w->power && w->event &&  
  27.         (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {  
  28.         ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);  
  29.         if (ret < 0)  
  30.             return ret;  
  31.     }  
  32.   
  33.     //关闭widget前,如果该widget属于PGA部件,调用dapm_set_pga控制pga部件音量逐步减少,  
  34.     //减少部件断电时产生的POP音。  
  35.     /* Lower PGA volume to reduce pops */  
  36.     if (w->id == snd_soc_dapm_pga && !w->power)  
  37.         dapm_set_pga(w, w->power);  
  38.   
  39.     //操作widget开启或关闭。  
  40.     dapm_update_bits(w);  
  41.   
  42.     //开启widget后,如果该widget属于PGA部件,调用dapm_set_pga控制pga部件音量逐步增大,  
  43.     //减少部件断电时产生的POP音。  
  44.     /* Raise PGA volume to reduce pops */  
  45.     if (w->id == snd_soc_dapm_pga && w->power)  
  46.         dapm_set_pga(w, w->power);  
  47.   
  48.     //在开关widget后,执行标识为SND_SOC_DAPM_POST_PMU/SND_SOC_DAPM_POST_PMD的event  
  49.     //回调函数分支。event flag定义见dapm.h。  
  50.       
  51.     /* power up post event */  
  52.     if (w->power && w->event &&  
  53.         (w->event_flags & SND_SOC_DAPM_POST_PMU)) {  
  54.         ret = w->event(w,  
  55.                    NULL, SND_SOC_DAPM_POST_PMU);  
  56.         if (ret < 0)  
  57.             return ret;  
  58.     }  
  59.   
  60.     /* power down post event */  
  61.     if (!w->power && w->event &&  
  62.         (w->event_flags & SND_SOC_DAPM_POST_PMD)) {  
  63.         ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);  
  64.         if (ret < 0)  
  65.             return ret;  
  66.     }  
  67.   
  68.     return 0;  
  69. }  

七、dapm_seq_insert

[cpp]  view plain copy
  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.                 int sort[])  
  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, sort) < 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按照一定的顺序插入到链表list中。在power up/down时,为了减少可能由此产生的POP音,必须按照一定的顺序来操作这些widgets,这个顺序就是由dapm_seq_insert来指定的。

power down时,顺序如下:

[cpp]  view plain copy
  1. static int dapm_down_seq[] = {  
  2.     [snd_soc_dapm_pre] = 0,  
  3.     [snd_soc_dapm_adc] = 1,  
  4.     [snd_soc_dapm_hp] = 2,  
  5.     [snd_soc_dapm_spk] = 2,  
  6.     [snd_soc_dapm_pga] = 4,  
  7.     [snd_soc_dapm_mixer_named_ctl] = 5,  
  8.     [snd_soc_dapm_mixer] = 5,  
  9.     [snd_soc_dapm_dac] = 6,  
  10.     [snd_soc_dapm_mic] = 7,  
  11.     [snd_soc_dapm_micbias] = 8,  
  12.     [snd_soc_dapm_mux] = 9,  
  13.     [snd_soc_dapm_value_mux] = 9,  
  14.     [snd_soc_dapm_aif_in] = 10,  
  15.     [snd_soc_dapm_aif_out] = 10,  
  16.     [snd_soc_dapm_supply] = 11,  
  17.     [snd_soc_dapm_post] = 12,  
  18. };  
power up时,顺序如下:
[cpp]  view plain copy
  1. static int dapm_up_seq[] = {  
  2.     [snd_soc_dapm_pre] = 0,  
  3.     [snd_soc_dapm_supply] = 1,  
  4.     [snd_soc_dapm_micbias] = 2,  
  5.     [snd_soc_dapm_aif_in] = 3,  
  6.     [snd_soc_dapm_aif_out] = 3,  
  7.     [snd_soc_dapm_mic] = 4,  
  8.     [snd_soc_dapm_mux] = 5,  
  9.     [snd_soc_dapm_value_mux] = 5,  
  10.     [snd_soc_dapm_dac] = 6,  
  11.     [snd_soc_dapm_mixer] = 7,  
  12.     [snd_soc_dapm_mixer_named_ctl] = 7,  
  13.     [snd_soc_dapm_pga] = 8,  
  14.     [snd_soc_dapm_adc] = 9,  
  15.     [snd_soc_dapm_hp] = 10,  
  16.     [snd_soc_dapm_spk] = 10,  
  17.     [snd_soc_dapm_post] = 11,  
  18. };  

可以看出,这个顺序是合理的,符合音频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等概念。

你可能感兴趣的:(DAPM之五:dapm机制深入分析(上))