ASoC: Add a shutdown callback

Ensure that the audio subsystem is powered down cleanly when the system
shuts down by providing a shutdown operation. This ensures that all the
components have been returned to an off state cleanly which should avoid
audio issues from partially charged capacitors or noise on digital inputs
if the system is restarted quickly.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Tested-by: Ben Dooks <ben-linux@fluff.org>
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 4414117..55d45c4 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1020,6 +1020,21 @@
 	return 0;
 }
 
+static void soc_shutdown(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_card *card = socdev->card;
+
+	if (!card->instantiated)
+		return;
+
+	/* Flush out pmdown_time work - we actually do want to run it
+	 * now, we're shutting down so no imminent restart. */
+	run_delayed_work(&card->delayed_work);
+
+	snd_soc_dapm_shutdown(socdev);
+}
+
 /* ASoC platform driver */
 static struct platform_driver soc_driver = {
 	.driver		= {
@@ -1030,6 +1045,7 @@
 	.remove		= soc_remove,
 	.suspend	= soc_suspend,
 	.resume		= soc_resume,
+	.shutdown	= soc_shutdown,
 };
 
 /* create a new pcm */
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 6534359..b9129ef 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2032,6 +2032,35 @@
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_free);
 
+/*
+ * snd_soc_dapm_shutdown - callback for system shutdown
+ */
+void snd_soc_dapm_shutdown(struct snd_soc_device *socdev)
+{
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct snd_soc_dapm_widget *w;
+	LIST_HEAD(down_list);
+	int powerdown = 0;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (w->power) {
+			dapm_seq_insert(w, &down_list, dapm_down_seq);
+			powerdown = 1;
+		}
+	}
+
+	/* If there were no widgets to power down we're already in
+	 * standby.
+	 */
+	if (powerdown) {
+		snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_PREPARE);
+		dapm_seq_run(codec, &down_list, 0, dapm_down_seq);
+		snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_STANDBY);
+	}
+
+	snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_OFF);
+}
+
 /* Module information */
 MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
 MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");