在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