Suspend and Resume speed

Mark Brown broonie at opensource.wolfsonmicro.com
Thu Jun 12 17:05:59 CEST 2008


On Wed, Jun 11, 2008 at 07:54:06PM +0100, Andy Green wrote:

> I took your advice on the nits and attach a new version of the patch.
> Thanks a lot for your hints.

Thanks for the patch - it looks good and seems to work well, I just want
to test it some more.  Unfortunately there's some unrelated breakage in
suspend support on one of my test systems which is hampering that.

This is the version I'm currently using, adding the fix for rapid
resume/suspend cycles:

>From 945beb3dde0bd8aaf6291d5752ba9dd339e4e21c Mon Sep 17 00:00:00 2001
From: Andy Green <andy at openmoko.com>
Date: Wed, 11 Jun 2008 19:54:06 +0100
Subject: [PATCH] ASoC: Don't block system resume

On OpenMoko soc-audio resume is taking 700ms of the whole resume time of
1.3s, dominated by writes to the codec over I2C.  This patch shunts the
resume guts into a workqueue which then is done asynchronously.

The "card" is locked using the ALSA power state APIs as suggested by
Mark Brown.

[Added fix for race with resume to suspend and fixed a couple of nits
from checkpatch -- broonie.]

Signed-off-by: Andy Green <andy at openmoko.com>
Signed-off-by: Mark Brown <broonie at opensource.wolfsonmicro.com>
---
 include/sound/soc.h  |    1 +
 sound/soc/soc-core.c |   56 ++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index 97615b7..de39f5b 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -528,6 +528,7 @@ struct snd_soc_device {
 	struct snd_soc_codec *codec;
 	struct snd_soc_codec_device *codec_dev;
 	struct delayed_work delayed_work;
+	struct work_struct deferred_resume_work;
 	void *codec_data;
 };
 
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 0a144cd..12386c4 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -635,6 +635,16 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state)
 	struct snd_soc_codec *codec = socdev->codec;
 	int i;
 
+	/* Due to the resume being scheduled into a workqueue we could
+	 * suspend before that's finished - wait for it to complete.
+	 */
+	snd_power_lock(codec->card);
+	snd_power_wait(codec->card, SNDRV_CTL_POWER_D0);
+	snd_power_unlock(codec->card);
+
+	/* we're going to block userspace touching us until resume completes */
+	snd_power_change_state(codec->card, SNDRV_CTL_POWER_D3hot);
+
 	/* mute any active DAC's */
 	for(i = 0; i < machine->num_links; i++) {
 		struct snd_soc_codec_dai *dai = machine->dai_link[i].codec_dai;
@@ -687,20 +697,31 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state)
 	return 0;
 }
 
-/* powers up audio subsystem after a suspend */
-static int soc_resume(struct platform_device *pdev)
+/* deferred resume work, so resume can complete before we finished
+ * setting our codec back up, which can be very slow on I2C
+ */
+static void soc_resume_deferred(struct work_struct *work)
 {
- 	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_device *socdev = container_of(work,
+						     struct snd_soc_device,
+						     deferred_resume_work);
  	struct snd_soc_machine *machine = socdev->machine;
  	struct snd_soc_platform *platform = socdev->platform;
  	struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
 	struct snd_soc_codec *codec = socdev->codec;
+	struct platform_device *pdev = to_platform_device(socdev->dev);
 	int i;
 
+	/* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
+	 * so userspace apps are blocked from touching us
+	 */
+
+	dev_info(socdev->dev, "starting resume work\n");
+
 	if (machine->resume_pre)
 		machine->resume_pre(pdev);
 
-	for(i = 0; i < machine->num_links; i++) {
+	for (i = 0; i < machine->num_links; i++) {
 		struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai;
 		if (cpu_dai->resume && cpu_dai->type == SND_SOC_DAI_AC97)
 			cpu_dai->resume(pdev, cpu_dai);
@@ -709,8 +730,8 @@ static int soc_resume(struct platform_device *pdev)
 	if (codec_dev->resume)
 		codec_dev->resume(pdev);
 
-	for(i = 0; i < codec->num_dai; i++) {
-		char* stream = codec->dai[i].playback.stream_name;
+	for (i = 0; i < codec->num_dai; i++) {
+		char *stream = codec->dai[i].playback.stream_name;
 		if (stream != NULL)
 			snd_soc_dapm_stream_event(codec, stream,
 				SND_SOC_DAPM_STREAM_RESUME);
@@ -721,13 +742,13 @@ static int soc_resume(struct platform_device *pdev)
 	}
 
 	/* unmute any active DAC's */
-	for(i = 0; i < machine->num_links; i++) {
+	for (i = 0; i < machine->num_links; i++) {
 		struct snd_soc_codec_dai *dai = machine->dai_link[i].codec_dai;
 		if (dai->dai_ops.digital_mute && dai->playback.active)
 			dai->dai_ops.digital_mute(dai, 0);
 	}
 
-	for(i = 0; i < machine->num_links; i++) {
+	for (i = 0; i < machine->num_links; i++) {
 		struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai;
 		if (cpu_dai->resume && cpu_dai->type != SND_SOC_DAI_AC97)
 			cpu_dai->resume(pdev, cpu_dai);
@@ -738,6 +759,22 @@ static int soc_resume(struct platform_device *pdev)
 	if (machine->resume_post)
 		machine->resume_post(pdev);
 
+	dev_info(socdev->dev, "resume work completed\n");
+
+	/* userspace can access us now we are back as we were before */
+	snd_power_change_state(codec->card, SNDRV_CTL_POWER_D0);
+}
+
+/* powers up audio subsystem after a suspend */
+static int soc_resume(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+	dev_info(socdev->dev, "scheduling resume work\n");
+
+	if (!schedule_work(&socdev->deferred_resume_work))
+		dev_err(socdev->dev, "work item may be lost\n");
+
 	return 0;
 }
 
@@ -784,6 +821,9 @@ static int soc_probe(struct platform_device *pdev)
 
 	/* DAPM stream work */
 	INIT_DELAYED_WORK(&socdev->delayed_work, close_delayed_work);
+	/* deferred resume work */
+	INIT_WORK(&socdev->deferred_resume_work, soc_resume_deferred);
+
 	return 0;
 
 platform_err:
-- 
1.5.5.4





More information about the openmoko-kernel mailing list