[alsa-devel] [PATCH 0/5] Enabling ASoC paths over suspend

The following patch series implements support for keeping some paths in the audio codec enabled while the system is suspended. This is primarily intended for mobile phones, where the GSM modem is normally capable of supporting a voice call with no intervention from the CPU so power can be saved by suspending the Linux system. This is done by allowing DAPM nodes to be marked as ignoring suspend. Any audio paths between two widgets set up like this at suspend time will be kept active and the CODEC device will not be suspended.
The existing testing is very light (especially for digital paths), the intention is to do more of that get this merged into Takashi's tree early next week.
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git suspend-enable
Mark Brown (5): ASoC: Remove unneeded suspend bias managment from CODEC drivers ASoC: Remove unused DAPM suspend flag ASoC: Refactor DAPM suspend handling ASoC: Support leaving paths enabled over system suspend ASoC: Allow active paths from the GSM modem while the GTA02 is suspended
include/sound/soc-dapm.h | 3 +- sound/soc/codecs/ak4535.c | 1 - sound/soc/codecs/cq93vc.c | 2 +- sound/soc/codecs/ssm2602.c | 1 - sound/soc/codecs/stac9766.c | 3 - sound/soc/codecs/tlv320aic23.c | 1 - sound/soc/codecs/tlv320aic3x.c | 2 +- sound/soc/codecs/tlv320dac33.c | 3 - sound/soc/codecs/twl4030.c | 1 - sound/soc/codecs/twl6040.c | 1 - sound/soc/codecs/uda1380.c | 1 - sound/soc/codecs/wm8350.c | 3 - sound/soc/codecs/wm8510.c | 2 +- sound/soc/codecs/wm8711.c | 2 +- sound/soc/codecs/wm8728.c | 2 +- sound/soc/codecs/wm8731.c | 1 - sound/soc/codecs/wm8903.c | 1 - sound/soc/codecs/wm8940.c | 1 - sound/soc/codecs/wm8960.c | 2 +- sound/soc/codecs/wm8974.c | 2 +- sound/soc/codecs/wm9712.c | 3 - sound/soc/codecs/wm9713.c | 3 - sound/soc/s3c24xx/neo1973_gta02_wm8753.c | 8 +++ sound/soc/soc-core.c | 35 ++++++++++- sound/soc/soc-dapm.c | 92 +++++++++++++++++++----------- 25 files changed, 107 insertions(+), 69 deletions(-)

The core will ensure that the device is in either STANDBY or OFF bias before suspending, restoring the bias in the driver is unneeded. Some drivers doing slightly more roundabout things have been left alone for now.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/ak4535.c | 1 - sound/soc/codecs/cq93vc.c | 2 +- sound/soc/codecs/ssm2602.c | 1 - sound/soc/codecs/stac9766.c | 3 --- sound/soc/codecs/tlv320aic23.c | 1 - sound/soc/codecs/tlv320aic3x.c | 2 +- sound/soc/codecs/tlv320dac33.c | 3 --- sound/soc/codecs/twl4030.c | 1 - sound/soc/codecs/twl6040.c | 1 - sound/soc/codecs/uda1380.c | 1 - sound/soc/codecs/wm8350.c | 3 --- sound/soc/codecs/wm8510.c | 2 +- sound/soc/codecs/wm8711.c | 2 +- sound/soc/codecs/wm8728.c | 2 +- sound/soc/codecs/wm8731.c | 1 - sound/soc/codecs/wm8903.c | 1 - sound/soc/codecs/wm8940.c | 1 - sound/soc/codecs/wm8960.c | 2 +- sound/soc/codecs/wm8974.c | 2 +- sound/soc/codecs/wm9712.c | 3 --- sound/soc/codecs/wm9713.c | 3 --- 21 files changed, 7 insertions(+), 31 deletions(-)
diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 2f6221b..b5917df 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -445,7 +445,6 @@ static int ak4535_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; ak4535_sync(codec); ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ak4535_set_bias_level(codec, codec->suspend_bias_level); return 0; }
diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c index 8f19b93..a320fb5 100644 --- a/sound/soc/codecs/cq93vc.c +++ b/sound/soc/codecs/cq93vc.c @@ -153,7 +153,7 @@ static int cq93vc_resume(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
- cq93vc_set_bias_level(codec, codec->suspend_bias_level); + cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0; } diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 99ddec7..f18c8b2 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -559,7 +559,6 @@ static int ssm2602_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ssm2602_set_bias_level(codec, codec->suspend_bias_level); return 0; }
diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index a1c7ca8..e90fe29 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -288,9 +288,6 @@ reset: } stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - stac9766_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; }
diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index da589d8..40bcbba 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -633,7 +633,6 @@ static int tlv320aic23_resume(struct platform_device *pdev) }
tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - tlv320aic23_set_bias_level(codec, codec->suspend_bias_level);
return 0; } diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index d57372b..eafa164 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1143,7 +1143,7 @@ static int aic3x_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); }
- aic3x_set_bias_level(codec, codec->suspend_bias_level); + aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0; } diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index ad5e263..007fe83 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1386,9 +1386,6 @@ static int dac33_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec;
dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - dac33_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - dac33_set_bias_level(codec, codec->suspend_bias_level);
return 0; } diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index b717a03..0fe74d1 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -2181,7 +2181,6 @@ static int twl4030_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec;
twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - twl4030_set_bias_level(codec, codec->suspend_bias_level); return 0; }
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index ead6d4b..2ae442e 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -996,7 +996,6 @@ static int twl6040_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec;
twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - twl6040_set_bias_level(codec, codec->suspend_bias_level);
return 0; } diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index ecba686..2f925a2 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -670,7 +670,6 @@ static int uda1380_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - uda1380_set_bias_level(codec, codec->suspend_bias_level); return 0; }
diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 145467d..e5a48da 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1336,9 +1336,6 @@ static int wm8350_resume(struct platform_device *pdev)
wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm8350_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; }
diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index af8cb69..d352700 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -556,7 +556,7 @@ static int wm8510_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8510_set_bias_level(codec, codec->suspend_bias_level); + return 0; }
diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index f350c5e..5acd35b 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -375,7 +375,7 @@ static int wm8711_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8711_set_bias_level(codec, codec->suspend_bias_level); + return 0; }
diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 3fb653b..bf56a58 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -237,7 +237,7 @@ static int wm8728_resume(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec;
- wm8728_set_bias_level(codec, codec->suspend_bias_level); + wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0; } diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 63f90c4..c4b6353 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -460,7 +460,6 @@ static int wm8731_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8731_set_bias_level(codec, codec->suspend_bias_level);
return 0; } diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 5a2510c..05b28cf 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1628,7 +1628,6 @@ static int wm8903_resume(struct platform_device *pdev)
/* Bring the codec back up to standby first to minimise pop/clicks */ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8903_set_bias_level(codec, codec->suspend_bias_level);
/* Sync back everything else */ if (tmp_cache) { diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index b2f1282..e313ab5 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -691,7 +691,6 @@ static int wm8940_resume(struct platform_device *pdev) ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret) goto error_ret; - ret = wm8940_set_bias_level(codec, codec->suspend_bias_level);
error_ret: return ret; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 50b2376..518c84e 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -823,7 +823,7 @@ static int wm8960_resume(struct platform_device *pdev) }
codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); - codec->set_bias_level(codec, codec->suspend_bias_level); + return 0; }
diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 270eb95..44841fe 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -608,7 +608,7 @@ static int wm8974_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8974_set_bias_level(codec, codec->suspend_bias_level); + return 0; }
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index e237bf6..dd4b6ce 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -631,9 +631,6 @@ static int wm9712_soc_resume(struct platform_device *pdev) } }
- if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm9712_set_bias_level(codec, SND_SOC_BIAS_ON); - return ret; }
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index d2244c9..6a30b84 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -1200,9 +1200,6 @@ static int wm9713_soc_resume(struct platform_device *pdev) } }
- if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); - return ret; }

We now manage suspend within the main power analysis rather than by flipping the state of widgets.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 1 - sound/soc/soc-dapm.c | 10 ---------- 2 files changed, 0 insertions(+), 11 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index d5d6ba8..55c6d3d 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -427,7 +427,6 @@ struct snd_soc_dapm_widget { unsigned char connected:1; /* connected codec pin */ unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ - unsigned char suspend:1; /* was active before suspend */ unsigned char force:1; /* force state */
int (*power_check)(struct snd_soc_dapm_widget *w); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 476dbe6..b30b0a2 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2012,18 +2012,8 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, w->active = 0; break; case SND_SOC_DAPM_STREAM_SUSPEND: - if (w->active) - w->suspend = 1; - w->active = 0; - break; case SND_SOC_DAPM_STREAM_RESUME: - if (w->suspend) { - w->active = 1; - w->suspend = 0; - } - break; case SND_SOC_DAPM_STREAM_PAUSE_PUSH: - break; case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: break; }

Instead of using stream events to handle power down during suspend integrate the handling with the normal widget path checking by replacing all cases where we report a connected endpoint in a path with a function snd_soc_dapm_suspend_check() which looks at the ALSA power state for the card and reports false if we are in a D3 state.
Since the core moves us into D3 prior to initating the suspend all power checks during suspend will cause the widgets to be powered down. In order to ensure that widgets are powered up on resume set the card to D2 at the start of resume handling (ALSA API calls require D0 so we are still protected against userspace access).
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/soc-core.c | 3 ++ sound/soc/soc-dapm.c | 53 ++++++++++++++++++++++++++++--------------------- 2 files changed, 33 insertions(+), 23 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4079223..9573976 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -963,6 +963,9 @@ static void soc_resume_deferred(struct work_struct *work)
dev_dbg(socdev->dev, "starting resume work\n");
+ /* Bring us up into D2 so that DAPM starts enabling things */ + snd_power_change_state(codec->card, SNDRV_CTL_POWER_D2); + if (card->resume_pre) card->resume_pre(pdev);
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b30b0a2..8c8b291 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -430,6 +430,23 @@ static inline void dapm_clear_walk(struct snd_soc_codec *codec) p->walked = 0; }
+/* We implement power down on suspend by checking the power state of + * the ALSA card - when we are suspending the ALSA state for the card + * is set to D3. + */ +static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_codec *codec = widget->codec; + + switch (snd_power_get_state(codec->card)) { + case SNDRV_CTL_POWER_D3hot: + case SNDRV_CTL_POWER_D3cold: + return 0; + default: + return 1; + } +} + /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. @@ -446,7 +463,7 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: if (widget->active) - return 1; + return snd_soc_dapm_suspend_check(widget); default: break; } @@ -454,12 +471,12 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) if (widget->connected) { /* connected pin ? */ if (widget->id == snd_soc_dapm_output && !widget->ext) - return 1; + return snd_soc_dapm_suspend_check(widget);
/* connected jack or spk ? */ if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk || (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources))) - return 1; + return snd_soc_dapm_suspend_check(widget); }
list_for_each_entry(path, &widget->sinks, list_source) { @@ -492,7 +509,7 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: if (widget->active) - return 1; + return snd_soc_dapm_suspend_check(widget); default: break; } @@ -500,16 +517,16 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) if (widget->connected) { /* connected pin ? */ if (widget->id == snd_soc_dapm_input && !widget->ext) - return 1; + return snd_soc_dapm_suspend_check(widget);
/* connected VMID/Bias for lower pops */ if (widget->id == snd_soc_dapm_vmid) - return 1; + return snd_soc_dapm_suspend_check(widget);
/* connected jack ? */ if (widget->id == snd_soc_dapm_mic || (widget->id == snd_soc_dapm_line && !list_empty(&widget->sinks))) - return 1; + return snd_soc_dapm_suspend_check(widget); }
list_for_each_entry(path, &widget->sources, list_sink) { @@ -897,22 +914,12 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) if (!w->power_check) continue;
- /* If we're suspending then pull down all the - * power. */ - switch (event) { - case SND_SOC_DAPM_STREAM_SUSPEND: - power = 0; - break; - - default: - if (!w->force) - power = w->power_check(w); - else - power = 1; - if (power) - sys_power = 1; - break; - } + if (!w->force) + power = w->power_check(w); + else + power = 1; + if (power) + sys_power = 1;
if (w->power == power) continue;

Some devices can usefully run audio while the Linux system is suspended. One of the most common examples is smartphone systems, which are normally designed to allow audio to be run between the baseband and the CODEC without passing through the CPU and so can suspend the CPU when on a voice call for additional power savings.
Support such systems by providing an API snd_soc_dapm_ignore_suspend(). This can be used to mark DAPM endpoints as not being sensitive to system suspend. When the system is being suspended paths between endpoints which are marked as ignoring suspend will be kept active. Both source and sink must be marked, and there must already be an active path between the two endpoints prior to suspend.
When paths are active over suspend the bias management will hold the device bias in the ON state. This is used to avoid suspending the CODEC while it is still in use.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 2 ++ sound/soc/soc-core.c | 32 ++++++++++++++++++++++++++++---- sound/soc/soc-dapm.c | 31 ++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 5 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 55c6d3d..66ff4c1 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -341,6 +341,7 @@ int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_sync(struct snd_soc_codec *codec); int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, const char *pin); +int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin);
/* dapm widget types */ enum snd_soc_dapm_type { @@ -428,6 +429,7 @@ struct snd_soc_dapm_widget { unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ unsigned char force:1; /* force state */ + unsigned char ignore_suspend:1; /* kept enabled over suspend */
int (*power_check)(struct snd_soc_dapm_widget *w);
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 9573976..8661e5b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -927,8 +927,19 @@ static int soc_suspend(struct device *dev) SND_SOC_DAPM_STREAM_SUSPEND); }
- if (codec_dev->suspend) - codec_dev->suspend(pdev, PMSG_SUSPEND); + /* If there are paths active then the CODEC will be held with + * bias _ON and should not be suspended. */ + if (codec_dev->suspend) { + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + codec_dev->suspend(pdev, PMSG_SUSPEND); + break; + default: + dev_dbg(socdev->dev, "CODEC is on over suspend\n"); + break; + } + }
for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; @@ -975,8 +986,21 @@ static void soc_resume_deferred(struct work_struct *work) cpu_dai->resume(cpu_dai); }
- if (codec_dev->resume) - codec_dev->resume(pdev); + /* If the CODEC was idle over suspend then it will have been + * left with bias OFF or STANDBY and suspended so we must now + * resume. Otherwise the suspend was suppressed. + */ + if (codec_dev->resume) { + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + codec_dev->resume(pdev); + break; + default: + dev_dbg(socdev->dev, "CODEC was on over suspend\n"); + break; + } + }
for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8c8b291..fefb6c4 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -441,7 +441,9 @@ static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) switch (snd_power_get_state(codec->card)) { case SNDRV_CTL_POWER_D3hot: case SNDRV_CTL_POWER_D3cold: - return 0; + if (widget->ignore_suspend) + pr_debug("%s ignoring suspend\n", widget->name); + return widget->ignore_suspend; default: return 1; } @@ -2137,6 +2139,33 @@ int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin) EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status);
/** + * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint + * @codec: audio codec + * @pin: audio signal pin endpoint (or start point) + * + * Mark the given endpoint or pin as ignoring suspend. When the + * system is disabled a path between two endpoints flagged as ignoring + * suspend will not be disabled. The path must already be enabled via + * normal means at suspend time, it will not be turned on if it was not + * already enabled. + */ +int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!strcmp(w->name, pin)) { + w->ignore_suspend = 1; + return 0; + } + } + + pr_err("Unknown DAPM pin: %s\n", pin); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend); + +/** * snd_soc_dapm_free - free dapm resources * @socdev: SoC device *

Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/s3c24xx/neo1973_gta02_wm8753.c | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c index dea83d3..209c259 100644 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -362,6 +362,14 @@ static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) snd_soc_dapm_disable_pin(codec, "Handset Mic"); snd_soc_dapm_disable_pin(codec, "Handset Spk");
+ /* allow audio paths from the GSM modem to run during suspend */ + snd_soc_dapm_ignore_suspend(codec, "Stereo Out"); + snd_soc_dapm_ignore_suspend(codec, "GSM Line Out"); + snd_soc_dapm_ignore_suspend(codec, "GSM Line In"); + snd_soc_dapm_ignore_suspend(codec, "Headset Mic"); + snd_soc_dapm_ignore_suspend(codec, "Handset Mic"); + snd_soc_dapm_ignore_suspend(codec, "Handset Spk"); + snd_soc_dapm_sync(codec);
return 0;

As well as allowing DAPM pins to be marked as ignoring suspend allow DAI links to be similarly marked. This is primarily intended for digital links between CODECs and non-CPU devices such as basebands in mobile phones and will suppress all suspend calls for the DAI link. It is likely that this will need to be revisited if used with devices which are part of the SoC CPU.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com ---
This patch is (as the timestamp and numbering indicate) a late addition to the series and has not been tested at all yet but hopefully it or something close to it will do the trick for digital links.
include/sound/soc.h | 3 +++ sound/soc/soc-core.c | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index 9f306f0..8326fc3 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -505,6 +505,9 @@ struct snd_soc_dai_link { /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_codec *codec);
+ /* Keep DAI active over suspend */ + unsigned int ignore_suspend:1; + /* Symmetry requirements */ unsigned int symmetric_rates:1;
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 8661e5b..34f71bf 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -893,19 +893,31 @@ static int soc_suspend(struct device *dev) /* mute any active DAC's */ for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *dai = card->dai_link[i].codec_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (dai->ops->digital_mute && dai->playback.active) dai->ops->digital_mute(dai, 1); }
/* suspend all pcms */ - for (i = 0; i < card->num_links; i++) + for (i = 0; i < card->num_links; i++) { + if (card->dai_link[i].ignore_suspend) + continue; + snd_pcm_suspend_all(card->dai_link[i].pcm); + }
if (card->suspend_pre) card->suspend_pre(pdev, PMSG_SUSPEND);
for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->suspend && !cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); if (platform->suspend) @@ -918,6 +930,10 @@ static int soc_suspend(struct device *dev)
for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; + + if (card->dai_link[i].ignore_suspend) + continue; + if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_SUSPEND); @@ -943,6 +959,10 @@ static int soc_suspend(struct device *dev)
for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->suspend && cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); } @@ -982,6 +1002,10 @@ static void soc_resume_deferred(struct work_struct *work)
for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->resume && cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); } @@ -1004,6 +1028,10 @@ static void soc_resume_deferred(struct work_struct *work)
for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; + + if (card->dai_link[i].ignore_suspend) + continue; + if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_RESUME); @@ -1016,12 +1044,20 @@ static void soc_resume_deferred(struct work_struct *work) /* unmute any active DACs */ for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *dai = card->dai_link[i].codec_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (dai->ops->digital_mute && dai->playback.active) dai->ops->digital_mute(dai, 0); }
for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->resume && !cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); if (platform->resume)

On Sunday 09 May 2010 15:07:41 ext Mark Brown wrote:
The following patch series implements support for keeping some paths in the audio codec enabled while the system is suspended. This is primarily intended for mobile phones, where the GSM modem is normally capable of supporting a voice call with no intervention from the CPU so power can be saved by suspending the Linux system. This is done by allowing DAPM nodes to be marked as ignoring suspend. Any audio paths between two widgets set up like this at suspend time will be kept active and the CODEC device will not be suspended.
The existing testing is very light (especially for digital paths), the intention is to do more of that get this merged into Takashi's tree early next week.
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git suspend-enable
Mark Brown (5): ASoC: Remove unneeded suspend bias managment from CODEC drivers ASoC: Remove unused DAPM suspend flag ASoC: Refactor DAPM suspend handling ASoC: Support leaving paths enabled over system suspend ASoC: Allow active paths from the GSM modem while the GTA02 is suspended
I have not tested the actual purpose of this series, but at least it is not breaking the twl4030, and tlv320dac33 codec driver normal usage.
Tested-by: Peter Ujfalusi peter.ujfalusi@nokia.com

On Sun, 2010-05-09 at 13:07 +0100, Mark Brown wrote:
The following patch series implements support for keeping some paths in the audio codec enabled while the system is suspended. This is primarily intended for mobile phones, where the GSM modem is normally capable of supporting a voice call with no intervention from the CPU so power can be saved by suspending the Linux system. This is done by allowing DAPM nodes to be marked as ignoring suspend. Any audio paths between two widgets set up like this at suspend time will be kept active and the CODEC device will not be suspended.
The existing testing is very light (especially for digital paths), the intention is to do more of that get this merged into Takashi's tree early next week.
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git suspend-enable
Mark Brown (5): ASoC: Remove unneeded suspend bias managment from CODEC drivers ASoC: Remove unused DAPM suspend flag ASoC: Refactor DAPM suspend handling ASoC: Support leaving paths enabled over system suspend ASoC: Allow active paths from the GSM modem while the GTA02 is suspended
All :-
Acked-by: Liam Girdwood lrg@slimlogic.co.uk
participants (3)
-
Liam Girdwood
-
Mark Brown
-
Peter Ujfalusi