在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