导航

DAPM之九:complete path对codec suspend的影响

Posted on 2016-03-04 12:56  思禽  阅读(567)  评论(0)    收藏  举报

原文:http://blog.csdn.net/azloong/article/details/9910361

在stream domain触发过程分析里面提及过:

Linux-3.4.5时代,只要dapm模块发现codec内部还打开一条complete path(不知道complete path是什么东东的,请补习《DAPM之五:dapm机制深入分析(上)》第4节),那么系统休眠/唤醒时,codec驱动不会跑其suspend/resume流程。

当时由于有其他的任务未详细分析,这几天恰好再次遇上这样的一个bug。虽然调整音频路径解决了这个问题,但还是抽空把这个问题的缘由大致跟踪了下,记录如下。

 

当系统进入休眠时,会调用snd_soc_suspend()函数,继而调用cpu_dai/pcm_dma/codec这三部分的suspend回调函数。

而codec的suspend调用有点特殊,它会检查bias的状态,当发现codec->dapm.bias_level为ON时,则跳出不跑suspend;只有codec->dapm.bias_level为STANDBY或者OFF时,才进入suspend处理。

为什么要这样做呢?因为系统休眠时,codec可能还是通电状态甚至在使用过程中的。试想下这个情景:语音通话,modem是直接连接到codec的,音频数据不经过cpu,因此这种情形下cpu可以进入休眠,只保持codec正常工作就行了。所以说,codec->dapm.bias_level就是用于判断codec是否还在工作,是则不进入codec的suspend处理了。“是否还在工作”的衡量标准就是上面所说的codec内部是否还存在complete path。

 

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /* powers down audio subsystem for suspend */  
  2. int snd_soc_suspend(struct device *dev)  
  3. {  
  4.     struct snd_soc_card *card = dev_get_drvdata(dev);  
  5.     struct snd_soc_codec *codec;  
  6.     int i;  
  7.   
  8.     //...  
  9.   
  10.     /* suspend all CODECs */  
  11.     list_for_each_entry(codec, &card->codec_dev_list, card_list) {  
  12.         /* If there are paths active then the CODEC will be held with 
  13.          * bias _ON and should not be suspended. */  
  14.         if (!codec->suspended && codec->driver->suspend) {  
  15.             switch (codec->dapm.bias_level) {  
  16.             case SND_SOC_BIAS_STANDBY:  
  17.                 /* 
  18.                  * If the CODEC is capable of idle 
  19.                  * bias off then being in STANDBY 
  20.                  * means it's doing something, 
  21.                  * otherwise fall through. 
  22.                  */  
  23.                 if (codec->dapm.idle_bias_off) {  
  24.                     dev_dbg(codec->dev,  
  25.                         "idle_bias_off CODEC on over suspend\n");  
  26.                     break;  
  27.                 }  
  28.             case SND_SOC_BIAS_OFF:  
  29.                 codec->driver->suspend(codec);  
  30.                 codec->suspended = 1;  
  31.                 codec->cache_sync = 1;  
  32.                 break;  
  33.             default:  
  34.                 dev_dbg(codec->dev, "CODEC is on over suspend\n");  
  35.                 break;  
  36.             }  
  37.         }  
  38.     }  
  39.       
  40.     //...  
  41. }  

从这里我们可以知道:肯定是codec->dapm.bias_level标志错了,从而导致我们这个问题。加了些打印,果然发现出问题时bias状态是ON的。

 

 

那么bias状态又在哪里被改变呢?我们在soc-dapm.c中找到这两个函数:

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /* Async callback run prior to DAPM sequences - brings to _PREPARE if 
  2.  * they're changing state. 
  3.  */  
  4. static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)  
  5. {  
  6.     struct snd_soc_dapm_context *d = data;  
  7.     int ret;  
  8.   
  9.     /* If we're off and we're not supposed to be go into STANDBY */  
  10.     if (d->bias_level == SND_SOC_BIAS_OFF &&  
  11.         d->target_bias_level != SND_SOC_BIAS_OFF) {  
  12.         if (d->dev)  
  13.             pm_runtime_get_sync(d->dev);  
  14.   
  15.         ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);  
  16.         if (ret != 0)  
  17.             dev_err(d->dev,  
  18.                 "Failed to turn on bias: %d\n", ret);  
  19.     }  
  20.   
  21.     /* Prepare for a STADDBY->ON or ON->STANDBY transition */  
  22.     if (d->bias_level != d->target_bias_level) {  
  23.         ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);  
  24.         if (ret != 0)  
  25.             dev_err(d->dev,  
  26.                 "Failed to prepare bias: %d\n", ret);  
  27.     }  
  28. }  
  29.   
  30. /* Async callback run prior to DAPM sequences - brings to their final 
  31.  * state. 
  32.  */  
  33. static void dapm_post_sequence_async(void *data, async_cookie_t cookie)  
  34. {  
  35.     struct snd_soc_dapm_context *d = data;  
  36.     int ret;  
  37.   
  38.     /* If we just powered the last thing off drop to standby bias */  
  39.     if (d->bias_level == SND_SOC_BIAS_PREPARE &&  
  40.         (d->target_bias_level == SND_SOC_BIAS_STANDBY ||  
  41.          d->target_bias_level == SND_SOC_BIAS_OFF)) {  
  42.         ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);  
  43.         if (ret != 0)  
  44.             dev_err(d->dev, "Failed to apply standby bias: %d\n",  
  45.                 ret);  
  46.     }  
  47.   
  48.     /* If we're in standby and can support bias off then do that */  
  49.     if (d->bias_level == SND_SOC_BIAS_STANDBY &&  
  50.         d->target_bias_level == SND_SOC_BIAS_OFF) {  
  51.         ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF);  
  52.         if (ret != 0)  
  53.             dev_err(d->dev, "Failed to turn off bias: %d\n", ret);  
  54.   
  55.         if (d->dev)  
  56.             pm_runtime_put(d->dev);  
  57.     }  
  58.   
  59.     /* If we just powered up then move to active bias */  
  60.     if (d->bias_level == SND_SOC_BIAS_PREPARE &&  
  61.         d->target_bias_level == SND_SOC_BIAS_ON) {  
  62.         ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);  
  63.         if (ret != 0)  
  64.             dev_err(d->dev, "Failed to apply active bias: %d\n",  
  65.                 ret);  
  66.     }  
  67. }  

从代码注释来看,我们更应该关注dapm_post_sequence_async,因为它决定了bias的最终状态。

 

然后我们发现这两个函数都是给dapm_power_widgets()调用的,之前强调过这个函数是dapm中最核心的一个函数。到了这章,还得继续围绕它来分析。

我们暂时先把目光放回到dapm_post_sequence_async函数,看看设置bias状态为ON时需要什么条件:

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /* If we just powered up then move to active bias */  
  2. if (d->bias_level == SND_SOC_BIAS_PREPARE &&  
  3.     d->target_bias_level == SND_SOC_BIAS_ON) {  
  4.     ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);  
  5.     if (ret != 0)  
  6.         dev_err(d->dev, "Failed to apply active bias: %d\n",  
  7.             ret);  
  8. }  

它要求当前bias状态为PREPARE,并且目的bias状态为ON,才会把codec->dapm.bias_level置为ON。

 

 

接着我们详细分析下dapm_power_widgets,看它在什么情况下满足这个条件的。相关的代码如下:

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)  
  2. {  
  3.     struct snd_soc_card *card = dapm->card;  
  4.     struct snd_soc_dapm_widget *w;  
  5.     struct snd_soc_dapm_context *d;  
  6.     LIST_HEAD(up_list);  
  7.     LIST_HEAD(down_list);  
  8.     LIST_HEAD(async_domain);  
  9.     enum snd_soc_bias_level bias;  
  10.   
  11.     trace_snd_soc_dapm_start(card);  
  12.   
  13.     list_for_each_entry(d, &card->dapm_list, list) {  
  14.         if (d->n_widgets || d->codec == NULL) {  
  15.             if (d->idle_bias_off)  
  16.                 d->target_bias_level = SND_SOC_BIAS_OFF;  
  17.             else  
  18.                 d->target_bias_level = SND_SOC_BIAS_STANDBY;  
  19.         }  
  20.     }  
  21.   
  22.     dapm_reset(card);  
  23.   
  24.     /* Check which widgets we need to power and store them in 
  25.      * lists indicating if they should be powered up or down.  We 
  26.      * only check widgets that have been flagged as dirty but note 
  27.      * that new widgets may be added to the dirty list while we 
  28.      * iterate. 
  29.      */  
  30.     list_for_each_entry(w, &card->dapm_dirty, dirty) {  
  31.         dapm_power_one_widget(w, &up_list, &down_list);  
  32.     }  
  33.   
  34.     list_for_each_entry(w, &card->widgets, list) {  
  35.         list_del_init(&w->dirty);  
  36.   
  37.         if (w->power) {  
  38.             d = w->dapm;  
  39.   
  40.             /* Supplies and micbiases only bring the 
  41.              * context up to STANDBY as unless something 
  42.              * else is active and passing audio they 
  43.              * generally don't require full power.  Signal 
  44.              * generators are virtual pins and have no 
  45.              * power impact themselves. 
  46.              */  
  47.             switch (w->id) {  
  48.             case snd_soc_dapm_siggen:  
  49.                 break;  
  50.             case snd_soc_dapm_supply:  
  51.             case snd_soc_dapm_regulator_supply:  
  52.             case snd_soc_dapm_micbias:  
  53.                 if (d->target_bias_level < SND_SOC_BIAS_STANDBY)  
  54.                     d->target_bias_level = SND_SOC_BIAS_STANDBY;  
  55.                 break;  
  56.             default:  
  57.                 d->target_bias_level = SND_SOC_BIAS_ON;  
  58.                 break;  
  59.             }  
  60.         }  
  61.   
  62.     }  

我们先看这段:

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /* Check which widgets we need to power and store them in 
  2.  * lists indicating if they should be powered up or down.  We 
  3.  * only check widgets that have been flagged as dirty but note 
  4.  * that new widgets may be added to the dirty list while we 
  5.  * iterate. 
  6.  */  
  7. list_for_each_entry(w, &card->dapm_dirty, dirty) {  
  8.     dapm_power_one_widget(w, &up_list, &down_list);  
  9. }  

这在《DAPM之五:dapm机制深入分析(上)》第4节)有非常详细的解释:它主要遍历每个widget,寻找complete path;如果找到,则将complete path上的所有widgets插入到up_list链表上,这是要通电的;然后将其余的widgets插入到down_list链表上,这是要断电的。

 

经过这个步骤,codec上所有widgets的目的状态都是确定的,保存在w->power标志中。
然后看下面的代码片段:

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. list_for_each_entry(w, &card->widgets, list) {  
  2.     switch (w->id) {  
  3.     case snd_soc_dapm_pre:  
  4.     case snd_soc_dapm_post:  
  5.         /* These widgets always need to be powered */  
  6.         break;  
  7.     default:  
  8.         list_del_init(&w->dirty);  
  9.         break;  
  10.     }  
  11.   
  12.     if (w->power) {  
  13.         d = w->dapm;  
  14.   
  15.         /* Supplies and micbiases only bring the 
  16.          * context up to STANDBY as unless something 
  17.          * else is active and passing audio they 
  18.          * generally don't require full power.  Signal 
  19.          * generators are virtual pins and have no 
  20.          * power impact themselves. 
  21.          */  
  22.         switch (w->id) {  
  23.         case snd_soc_dapm_siggen:  
  24.             break;  
  25.         case snd_soc_dapm_supply:  
  26.         case snd_soc_dapm_regulator_supply:  
  27.         case snd_soc_dapm_micbias:  
  28.             if (d->target_bias_level < SND_SOC_BIAS_STANDBY)  
  29.                 d->target_bias_level = SND_SOC_BIAS_STANDBY;  
  30.             break;  
  31.         default:  
  32.             d->target_bias_level = SND_SOC_BIAS_ON;  
  33.             break;  
  34.         }  
  35.     }  
  36.   
  37. }  

 

由这里得知:当检查到codec上最少有一个widget需要通电时,则置target_bias_level的状态为ON。而任意一个widget通电的前置条件是它必须处在一条complete path里面。

 

结合以上的代码分析,得出总结论:当系统检查到codec还保有complete path,则complete path上所有的widgets均需要通电,并且置bias状态为ON,标记codec还在正常工作中,不能让suspend/resume流程打扰它。

日后如果遇到codec suspend/resume未被调用,则需检查休眠前,codec里面是否还存在一条complete path。

OVER