ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身

2019-07-14 02:07发布

设计dapm的主要目的之一,就是希望声卡上的各种部件的电源按需分配,需要的就上电,不需要的就下电,使得整个音频系统总是处于最小的耗电状态,最主要的就是,这一切对用户空间的应用程序是透明的,也就是说,用户空间的应用程序无需关心那个部件何时需要电源,它只要按需要设定好音频路径,播放音频数据,暂停或停止,dapm框架会根据音频路径,完美地对各种部件的电源进行控制,而且精确地按某种顺序进行,防止上下电过程中产生不必要的pop-pop声。这就是本章我们需要讨论的内容。 /*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

统计widget连接至端点widget的路径个数

ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route这篇文章中的最后一节,我们曾经提出了端点widget这一概念,端点widget位于音频路径的起始端或者末端,所以通常它们就是指codec的输入输出引脚所对应的widget,或者是外部器件对应的widget,这些widget的类型有以下这些:
端点widget的种类 分类 widget类型 codec的输入输出引脚 snd_soc_dapm_output
snd_soc_dapm_input 外接的音频设备 snd_soc_dapm_hp
snd_soc_dapm_spk
snd_soc_dapm_line
snd_soc_dapm_mic 音频流(stream domain) snd_soc_dapm_adc
snd_soc_dapm_dac
snd_soc_dapm_aif_out
snd_soc_dapm_aif_in
snd_soc_dapm_dai_out
snd_soc_dapm_dai_in 电源、时钟 snd_soc_dapm_supply
snd_soc_dapm_regulator_supply
snd_soc_dapm_clock_supply 影子widget snd_soc_dapm_kcontrol dapm要给一个widget上电的其中一个前提条件是:这个widget位于一条完整的音频路径上,而一条完整的音频路径的两头,必须是输入/输出引脚,或者是一个外部音频设备,又或者是一个处于激活状态的音频流widget,也就是上表中的前三项,上表中的后两项,它们可以位于路径的末端,但不是构成完成音频路径的必要条件,我们只用它来判断扫描一条路径的结束条件。dapm提供了两个内部函数,用来统计一个widget连接到输出引脚、输入引脚、激活的音频流widget的有效路径个数:
  • is_connected_output_ep    返回连接至输出引脚或激活状态的输出音频流的路径数量
  • is_connected_input_ep    返回连接至输入引脚或激活状态的输入音频流的路径数量
下面我贴出is_connected_output_ep函数和必要的注释: [html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int is_connected_output_ep(struct snd_soc_dapm_widget *widget,  
  2.         struct snd_soc_dapm_widget_list **list)  
  3. {  
  4.         struct snd_soc_dapm_path *path;  
  5.         int con = 0;  
  6.         /*  多个路径可能使用了同一个widget,如果在遍历另一个路径时,*/  
  7.         /*  已经统计过该widget,直接返回output字段即可。            */  
  8.         if (widget->outputs >= 0)  
  9.                 return widget->outputs;  
  10.   
  11.         /*  以下这几种widget是端点widget,但不是输出,所以直接返回0,结束该路径的扫描  */  
  12.         switch (widget->id) {  
  13.         case snd_soc_dapm_supply:  
  14.         case snd_soc_dapm_regulator_supply:  
  15.         case snd_soc_dapm_clock_supply:  
  16.         case snd_soc_dapm_kcontrol:  
  17.                 return 0;  
  18.         default:  
  19.                 break;  
  20.         }  
  21.         /*  对于音频流widget,如果处于激活状态,如果没有休眠,返回1,否则,返回0  */  
  22.         /*  而且对于激活的音频流widget是端点widget,所以也会结束该路径的扫描  */  
  23.         /*  如果没有处于激活状态,按普通的widget继续往下执行  */  
  24.         switch (widget->id) {  
  25.         case snd_soc_dapm_adc:  
  26.         case snd_soc_dapm_aif_out:  
  27.         case snd_soc_dapm_dai_out:  
  28.                 if (widget->active) {  
  29.                         widget->outputs = snd_soc_dapm_suspend_check(widget);  
  30.                         return widget->outputs;  
  31.                 }  
  32.         default:  
  33.                 break;  
  34.         }  
  35.   
  36.         if (widget->connected) {  
  37.                 /* 处于连接状态的输出引脚,也根据休眠状态返回1或0 */  
  38.                 if (widget->id == snd_soc_dapm_output && !widget->ext) {  
  39.                         widget->outputs = snd_soc_dapm_suspend_check(widget);  
  40.                         return widget->outputs;  
  41.                 }  
  42.   
  43.                 /* 处于连接状态的输出设备,也根据休眠状态返回1或0 */  
  44.                 if (widget->id == snd_soc_dapm_hp ||  
  45.                     widget->id == snd_soc_dapm_spk ||  
  46.                     (widget->id == snd_soc_dapm_line &&  
  47.                      !list_empty(&widget->sources))) {  
  48.                         widget->outputs = snd_soc_dapm_suspend_check(widget);  
  49.                         return widget->outputs;  
  50.                 }  
  51.         }  
  52.         /*  不是端点widget,循环查询它的输出端  */  
  53.         list_for_each_entry(path, &widget->sinks, list_source) {  
  54.                 DAPM_UPDATE_STAT(widget, neighbour_checks);  
  55.   
  56.                 if (path->weak)  
  57.                         continue;  
  58.   
  59.                 if (path->walking)   /* 比较奇怪,防止无限循环的路径? */  
  60.                         return 1;  
  61.   
  62.                 if (path->walked)  
  63.                         continue;  
  64.   
  65.                 if (path->sink && path->connect) {  
  66.                         path->walked = 1;  
  67.                         path->walking = 1;  
  68.                         ......  
  69.                         /*  递归调用,统计每一个输出端  */  
  70.                         con += is_connected_output_ep(path->sink, list);  
  71.   
  72.                         path->walking = 0;  
  73.                 }  
  74.         }  
  75.   
  76.         widget->outputs = con;  
  77.   
  78.         return con;  
  79. }  
该函数使用了递归算法,直到遇到端点widget为止才停止扫描,把统计到的输出路径个数保存在output字段中并返回。is_connected_intput_ep函数的原理差不多,有兴趣的苏浙可以自己查看内核的原码。

dapm_dirty链表

在代表声卡的snd_soc_card结构中,有一个链表字段:dapm_dirty,所有状态发生了改变的widget,dapm不会立刻处理它的电源状态,而是需要先挂在该链表下面,等待后续的进一步处理:或者是上电,或者是下电。dapm为我们提供了一个api函数来完成这个动作: [html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason)  
  2. {  
  3.         if (!dapm_dirty_widget(w)) {  
  4.                 dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s ",  
  5.                          w->name, reason);  
  6.                 list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);  
  7.         }  
  8. }  

power_check回调函数

在文章 ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系中,我们知道,在创建widget的时候,widget的power_check回调函数会根据widget的类型,设置不同的回调函数。当widget的状态改变后,dapm会遍历dapm_dirty链表,并通过power_check回调函数,决定该widget是否需要上电。大多数的widget的power_check回调被设置为:dapm_generic_check_power: [html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)  
  2. {  
  3.         int in, out;