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

在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

你可能感兴趣的:(DAPM之九:complete path对codec suspend的影响)