原文: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。
- /* powers down audio subsystem for suspend */
- int snd_soc_suspend(struct device *dev)
- {
- struct snd_soc_card *card = dev_get_drvdata(dev);
- struct snd_soc_codec *codec;
- int i;
- //...
- /* suspend all CODECs */
- list_for_each_entry(codec, &card->codec_dev_list, card_list) {
- /* If there are paths active then the CODEC will be held with
- * bias _ON and should not be suspended. */
- if (!codec->suspended && codec->driver->suspend) {
- switch (codec->dapm.bias_level) {
- case SND_SOC_BIAS_STANDBY:
- /*
- * If the CODEC is capable of idle
- * bias off then being in STANDBY
- * means it's doing something,
- * otherwise fall through.
- */
- if (codec->dapm.idle_bias_off) {
- dev_dbg(codec->dev,
- "idle_bias_off CODEC on over suspend\n");
- break;
- }
- case SND_SOC_BIAS_OFF:
- codec->driver->suspend(codec);
- codec->suspended = 1;
- codec->cache_sync = 1;
- break;
- default:
- dev_dbg(codec->dev, "CODEC is on over suspend\n");
- break;
- }
- }
- }
- //...
- }
从这里我们可以知道:肯定是codec->dapm.bias_level标志错了,从而导致我们这个问题。加了些打印,果然发现出问题时bias状态是ON的。
那么bias状态又在哪里被改变呢?我们在soc-dapm.c中找到这两个函数:
- /* Async callback run prior to DAPM sequences - brings to _PREPARE if
- * they're changing state.
- */
- static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
- {
- struct snd_soc_dapm_context *d = data;
- int ret;
- /* If we're off and we're not supposed to be go into STANDBY */
- if (d->bias_level == SND_SOC_BIAS_OFF &&
- d->target_bias_level != SND_SOC_BIAS_OFF) {
- if (d->dev)
- pm_runtime_get_sync(d->dev);
- ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to turn on bias: %d\n", ret);
- }
- /* Prepare for a STADDBY->ON or ON->STANDBY transition */
- if (d->bias_level != d->target_bias_level) {
- ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to prepare bias: %d\n", ret);
- }
- }
- /* Async callback run prior to DAPM sequences - brings to their final
- * state.
- */
- static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
- {
- struct snd_soc_dapm_context *d = data;
- int ret;
- /* If we just powered the last thing off drop to standby bias */
- if (d->bias_level == SND_SOC_BIAS_PREPARE &&
- (d->target_bias_level == SND_SOC_BIAS_STANDBY ||
- d->target_bias_level == SND_SOC_BIAS_OFF)) {
- ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
- if (ret != 0)
- dev_err(d->dev, "Failed to apply standby bias: %d\n",
- ret);
- }
- /* If we're in standby and can support bias off then do that */
- if (d->bias_level == SND_SOC_BIAS_STANDBY &&
- d->target_bias_level == SND_SOC_BIAS_OFF) {
- ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF);
- if (ret != 0)
- dev_err(d->dev, "Failed to turn off bias: %d\n", ret);
- if (d->dev)
- pm_runtime_put(d->dev);
- }
- /* If we just powered up then move to active bias */
- if (d->bias_level == SND_SOC_BIAS_PREPARE &&
- d->target_bias_level == SND_SOC_BIAS_ON) {
- ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);
- if (ret != 0)
- dev_err(d->dev, "Failed to apply active bias: %d\n",
- ret);
- }
- }
从代码注释来看,我们更应该关注dapm_post_sequence_async,因为它决定了bias的最终状态。
然后我们发现这两个函数都是给dapm_power_widgets()调用的,之前强调过这个函数是dapm中最核心的一个函数。到了这章,还得继续围绕它来分析。
我们暂时先把目光放回到dapm_post_sequence_async函数,看看设置bias状态为ON时需要什么条件:
- /* If we just powered up then move to active bias */
- if (d->bias_level == SND_SOC_BIAS_PREPARE &&
- d->target_bias_level == SND_SOC_BIAS_ON) {
- ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);
- if (ret != 0)
- dev_err(d->dev, "Failed to apply active bias: %d\n",
- ret);
- }
它要求当前bias状态为PREPARE,并且目的bias状态为ON,才会把codec->dapm.bias_level置为ON。
接着我们详细分析下dapm_power_widgets,看它在什么情况下满足这个条件的。相关的代码如下:
- static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
- {
- struct snd_soc_card *card = dapm->card;
- struct snd_soc_dapm_widget *w;
- struct snd_soc_dapm_context *d;
- LIST_HEAD(up_list);
- LIST_HEAD(down_list);
- LIST_HEAD(async_domain);
- enum snd_soc_bias_level bias;
- trace_snd_soc_dapm_start(card);
- list_for_each_entry(d, &card->dapm_list, list) {
- if (d->n_widgets || d->codec == NULL) {
- if (d->idle_bias_off)
- d->target_bias_level = SND_SOC_BIAS_OFF;
- else
- d->target_bias_level = SND_SOC_BIAS_STANDBY;
- }
- }
- dapm_reset(card);
- /* Check which widgets we need to power and store them in
- * lists indicating if they should be powered up or down. We
- * only check widgets that have been flagged as dirty but note
- * that new widgets may be added to the dirty list while we
- * iterate.
- */
- list_for_each_entry(w, &card->dapm_dirty, dirty) {
- dapm_power_one_widget(w, &up_list, &down_list);
- }
- list_for_each_entry(w, &card->widgets, list) {
- list_del_init(&w->dirty);
- if (w->power) {
- d = w->dapm;
- /* Supplies and micbiases only bring the
- * context up to STANDBY as unless something
- * else is active and passing audio they
- * generally don't require full power. Signal
- * generators are virtual pins and have no
- * power impact themselves.
- */
- switch (w->id) {
- case snd_soc_dapm_siggen:
- break;
- case snd_soc_dapm_supply:
- case snd_soc_dapm_regulator_supply:
- case snd_soc_dapm_micbias:
- if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
- d->target_bias_level = SND_SOC_BIAS_STANDBY;
- break;
- default:
- d->target_bias_level = SND_SOC_BIAS_ON;
- break;
- }
- }
- }
我们先看这段:
- /* Check which widgets we need to power and store them in
- * lists indicating if they should be powered up or down. We
- * only check widgets that have been flagged as dirty but note
- * that new widgets may be added to the dirty list while we
- * iterate.
- */
- list_for_each_entry(w, &card->dapm_dirty, dirty) {
- dapm_power_one_widget(w, &up_list, &down_list);
- }
这在《DAPM之五:dapm机制深入分析(上)》第4节)有非常详细的解释:它主要遍历每个widget,寻找complete path;如果找到,则将complete path上的所有widgets插入到up_list链表上,这是要通电的;然后将其余的widgets插入到down_list链表上,这是要断电的。
经过这个步骤,codec上所有widgets的目的状态都是确定的,保存在w->power标志中。
然后看下面的代码片段:
- list_for_each_entry(w, &card->widgets, list) {
- switch (w->id) {
- case snd_soc_dapm_pre:
- case snd_soc_dapm_post:
- /* These widgets always need to be powered */
- break;
- default:
- list_del_init(&w->dirty);
- break;
- }
- if (w->power) {
- d = w->dapm;
- /* Supplies and micbiases only bring the
- * context up to STANDBY as unless something
- * else is active and passing audio they
- * generally don't require full power. Signal
- * generators are virtual pins and have no
- * power impact themselves.
- */
- switch (w->id) {
- case snd_soc_dapm_siggen:
- break;
- case snd_soc_dapm_supply:
- case snd_soc_dapm_regulator_supply:
- case snd_soc_dapm_micbias:
- if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
- d->target_bias_level = SND_SOC_BIAS_STANDBY;
- break;
- default:
- d->target_bias_level = SND_SOC_BIAS_ON;
- break;
- }
- }
- }
由这里得知:当检查到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
浙公网安备 33010602011771号