ASoC: wcd9xxx: Handle ADSP Subsystem Restart

Exercise codec reset as part of ADSP subsystem restart handling
and also update codec and ASoC cache registers to defaults. This
is required to clean up codec state post ADSP restart. Add flag
to avoid codec reset during device bootup.

If the software workaround to prevent leakage on the spkdrv is
enabled, handle the workaround post subsystem restart by turning
on PA and bandgap again.

Change-Id: Ieb7f9892b45bc01f0e2ea38485c4f63298f83999
Signed-off-by: Ravishankar Sarawadi <crsaraw@codeaurora.org>
diff --git a/drivers/mfd/wcd9xxx-core.c b/drivers/mfd/wcd9xxx-core.c
index bc129da..cc021c0 100644
--- a/drivers/mfd/wcd9xxx-core.c
+++ b/drivers/mfd/wcd9xxx-core.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -334,7 +334,7 @@
 {
 	int ret;
 
-	if (wcd9xxx->reset_gpio) {
+	if (wcd9xxx->reset_gpio && wcd9xxx->slim_device_bootup) {
 		ret = gpio_request(wcd9xxx->reset_gpio, "CDC_RESET");
 		if (ret) {
 			pr_err("%s: Failed to request gpio %d\n", __func__,
@@ -342,7 +342,8 @@
 			wcd9xxx->reset_gpio = 0;
 			return ret;
 		}
-
+	}
+	if (wcd9xxx->reset_gpio) {
 		gpio_direction_output(wcd9xxx->reset_gpio, 0);
 		msleep(20);
 		gpio_direction_output(wcd9xxx->reset_gpio, 1);
@@ -1259,6 +1260,7 @@
 	wcd9xxx->reset_gpio = pdata->reset_gpio;
 	wcd9xxx->dev = &slim->dev;
 	wcd9xxx->mclk_rate = pdata->mclk_rate;
+	wcd9xxx->slim_device_bootup = true;
 
 	ret = wcd9xxx_enable_supplies(wcd9xxx, pdata);
 	if (ret)
@@ -1378,6 +1380,29 @@
 	return ret;
 }
 
+static int wcd9xxx_device_up(struct wcd9xxx *wcd9xxx)
+{
+	int ret = 0;
+
+	if (wcd9xxx->slim_device_bootup) {
+		wcd9xxx->slim_device_bootup = false;
+		return 0;
+	}
+	ret = wcd9xxx_reset(wcd9xxx);
+	if (ret)
+		pr_err("%s: Resetting Codec failed\n", __func__);
+
+	wcd9xxx_bring_up(wcd9xxx);
+	wcd9xxx->post_reset(wcd9xxx);
+	return ret;
+}
+
+static int wcd9xxx_slim_device_up(struct slim_device *sldev)
+{
+	struct wcd9xxx *wcd9xxx = slim_get_devicedata(sldev);
+	return wcd9xxx_device_up(wcd9xxx);
+}
+
 static int wcd9xxx_slim_resume(struct slim_device *sldev)
 {
 	struct wcd9xxx *wcd9xxx = slim_get_devicedata(sldev);
@@ -1533,6 +1558,7 @@
 	.id_table = taiko_slimtest_id,
 	.resume = wcd9xxx_slim_resume,
 	.suspend = wcd9xxx_slim_suspend,
+	.device_up = wcd9xxx_slim_device_up,
 };
 
 static const struct slim_device_id tapan_slimtest_id[] = {
diff --git a/include/linux/mfd/wcd9xxx/core.h b/include/linux/mfd/wcd9xxx/core.h
index 4b2ad66..1402fd0 100644
--- a/include/linux/mfd/wcd9xxx/core.h
+++ b/include/linux/mfd/wcd9xxx/core.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -156,6 +156,10 @@
 			int bytes, void *dest, bool interface_reg);
 	int (*write_dev)(struct wcd9xxx *wcd9xxx, unsigned short reg,
 			int bytes, void *src, bool interface_reg);
+	int (*post_reset)(struct wcd9xxx *wcd9xxx);
+
+	void *ssr_priv;
+	bool slim_device_bootup;
 
 	u32 num_of_supplies;
 	struct regulator_bulk_data *supplies;
diff --git a/sound/soc/codecs/wcd9320.c b/sound/soc/codecs/wcd9320.c
index 1546b9e..ad1546d 100644
--- a/sound/soc/codecs/wcd9320.c
+++ b/sound/soc/codecs/wcd9320.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -4892,12 +4892,53 @@
 }
 EXPORT_SYMBOL_GPL(taiko_hs_detect);
 
+static int taiko_post_reset_cb(struct wcd9xxx *wcd9xxx)
+{
+	int ret = 0;
+	struct snd_soc_codec *codec;
+	struct taiko_priv *taiko;
+
+	codec = (struct snd_soc_codec *)(wcd9xxx->ssr_priv);
+	taiko = snd_soc_codec_get_drvdata(codec);
+	mutex_lock(&codec->mutex);
+	WCD9XXX_BCL_LOCK(&taiko->resmgr);
+	if (spkr_drv_wrnd == 1) {
+		wcd9xxx_resmgr_post_ssr(&taiko->resmgr);
+		snd_soc_update_bits(codec, TAIKO_A_SPKR_DRV_EN, 0x80, 0x80);
+	}
+	WCD9XXX_BCL_UNLOCK(&taiko->resmgr);
+
+	if (codec->reg_def_copy) {
+		pr_debug("%s: Update ASOC cache", __func__);
+		kfree(codec->reg_cache);
+		codec->reg_cache = kmemdup(codec->reg_def_copy,
+						codec->reg_size, GFP_KERNEL);
+	}
+
+	taiko_update_reg_defaults(codec);
+	taiko_codec_init_reg(codec);
+	ret = taiko_handle_pdata(taiko);
+	if (IS_ERR_VALUE(ret))
+		pr_err("%s: bad pdata\n", __func__);
+	mutex_unlock(&codec->mutex);
+	return ret;
+}
+
+
 static struct wcd9xxx_reg_address taiko_reg_address = {
 	.micb_4_mbhc = TAIKO_A_MICB_4_MBHC,
 	.micb_4_int_rbias = TAIKO_A_MICB_4_INT_RBIAS,
 	.micb_4_ctl = TAIKO_A_MICB_4_CTL,
 };
 
+static int wcd9xxx_ssr_register(struct wcd9xxx *control,
+		int (*post_reset_cb)(struct wcd9xxx *wcd9xxx), void *priv)
+{
+	control->post_reset = post_reset_cb;
+	control->ssr_priv = priv;
+	return 0;
+}
+
 static int taiko_codec_probe(struct snd_soc_codec *codec)
 {
 	struct wcd9xxx *control;
@@ -4912,6 +4953,8 @@
 	codec->control_data = dev_get_drvdata(codec->dev->parent);
 	control = codec->control_data;
 
+	wcd9xxx_ssr_register(control, taiko_post_reset_cb, (void *)codec);
+
 	dev_info(codec->dev, "%s()\n", __func__);
 
 	taiko = kzalloc(sizeof(struct taiko_priv), GFP_KERNEL);
diff --git a/sound/soc/codecs/wcd9xxx-resmgr.c b/sound/soc/codecs/wcd9xxx-resmgr.c
index d744cb9..0ab650e 100644
--- a/sound/soc/codecs/wcd9xxx-resmgr.c
+++ b/sound/soc/codecs/wcd9xxx-resmgr.c
@@ -195,6 +195,24 @@
 	pr_debug("%s: leave\n", __func__);
 }
 
+void wcd9xxx_resmgr_post_ssr(struct wcd9xxx_resmgr *resmgr)
+{
+	int old_bg_audio_users, old_bg_mbhc_users;
+
+	WCD9XXX_BCL_ASSERT_LOCKED(resmgr);
+
+	old_bg_audio_users = resmgr->bg_audio_users;
+	resmgr->bg_audio_users = 0;
+	old_bg_mbhc_users = resmgr->bg_mbhc_users;
+	resmgr->bg_mbhc_users = 0;
+
+	while (old_bg_audio_users && --old_bg_audio_users)
+		wcd9xxx_resmgr_get_bandgap(resmgr, WCD9XXX_BANDGAP_AUDIO_MODE);
+
+	while (old_bg_mbhc_users && --old_bg_mbhc_users)
+		wcd9xxx_resmgr_get_bandgap(resmgr, WCD9XXX_BANDGAP_MBHC_MODE);
+}
+
 /*
  * wcd9xxx_resmgr_get_bandgap : Vote for bandgap ref
  * choice : WCD9XXX_BANDGAP_AUDIO_MODE, WCD9XXX_BANDGAP_MBHC_MODE
diff --git a/sound/soc/codecs/wcd9xxx-resmgr.h b/sound/soc/codecs/wcd9xxx-resmgr.h
index 2d04102..6c30eeb 100644
--- a/sound/soc/codecs/wcd9xxx-resmgr.h
+++ b/sound/soc/codecs/wcd9xxx-resmgr.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -158,6 +158,7 @@
 			      enum wcd9xxx_cfilt_sel cfilt_sel);
 
 void wcd9xxx_resmgr_bcl_lock(struct wcd9xxx_resmgr *resmgr);
+void wcd9xxx_resmgr_post_ssr(struct wcd9xxx_resmgr *resmgr);
 #define WCD9XXX_BCL_LOCK(resmgr)			\
 {							\
 	pr_debug("%s: Acquiring BCL\n", __func__);	\