ASoC: WCD9304: Enable GPIO based headset detection

WCD9304 supports mechanical switch / GPIO based headset detection.
Enable mechanical detection of plug insertion / removal and plug type
determination to determine if the inserted accessory is a headphone
or headset.

Change-Id: Ib76d31f3c14fb91ed9bf1d39237d8910a0e2fc57
Signed-off-by: Bhalchandra Gajare <gajare@codeaurora.org>
diff --git a/sound/soc/codecs/wcd9304.c b/sound/soc/codecs/wcd9304.c
index 6b590c9..5ab8c51 100644
--- a/sound/soc/codecs/wcd9304.c
+++ b/sound/soc/codecs/wcd9304.c
@@ -31,6 +31,8 @@
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/pm_runtime.h>
+#include <linux/kernel.h>
+#include <linux/gpio.h>
 #include "wcd9304.h"
 
 #define WCD9304_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
@@ -66,6 +68,21 @@
 
 #define SITAR_FAKE_INS_THRESHOLD_MS 2500
 #define SITAR_FAKE_REMOVAL_MIN_PERIOD_MS 50
+#define SITAR_MBHC_BUTTON_MIN 0x8000
+#define SITAR_GPIO_IRQ_DEBOUNCE_TIME_US 5000
+
+#define SITAR_ACQUIRE_LOCK(x) do { mutex_lock(&x); } while (0)
+#define SITAR_RELEASE_LOCK(x) do { mutex_unlock(&x); } while (0)
+
+#define MBHC_NUM_DCE_PLUG_DETECT 3
+#define SITAR_MBHC_FAKE_INSERT_LOW 10
+#define SITAR_MBHC_FAKE_INSERT_HIGH 80
+#define SITAR_MBHC_FAKE_INSERT_VOLT_DELTA_MV 500
+#define SITAR_HS_DETECT_PLUG_TIME_MS (5 * 1000)
+#define SITAR_HS_DETECT_PLUG_INERVAL_MS 100
+#define NUM_ATTEMPTS_TO_REPORT 5
+#define SITAR_MBHC_STATUS_REL_DETECTION 0x0C
+#define SITAR_MBHC_GPIO_REL_DEBOUNCE_TIME_MS 200
 
 static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0);
 static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1);
@@ -139,6 +156,21 @@
 	u8 nbounce_wait;
 };
 
+enum sitar_mbhc_plug_type {
+	PLUG_TYPE_INVALID = -1,
+	PLUG_TYPE_NONE,
+	PLUG_TYPE_HEADSET,
+	PLUG_TYPE_HEADPHONE,
+	PLUG_TYPE_HIGH_HPH,
+};
+
+enum sitar_mbhc_state {
+	MBHC_STATE_NONE = -1,
+	MBHC_STATE_POTENTIAL,
+	MBHC_STATE_POTENTIAL_RECOVERY,
+	MBHC_STATE_RELEASE,
+};
+
 struct sitar_priv {
 	struct snd_soc_codec *codec;
 	u32 mclk_freq;
@@ -167,15 +199,10 @@
 	void *calibration;
 	struct mbhc_internal_cal_data mbhc_data;
 
-	struct snd_soc_jack *headset_jack;
-	struct snd_soc_jack *button_jack;
-
 	struct wcd9xxx_pdata *pdata;
 	u32 anc_slot;
 
 	bool no_mic_headset_override;
-	/* Delayed work to report long button press */
-	struct delayed_work btn0_dwork;
 
 	struct mbhc_micbias_regs mbhc_bias_regs;
 	u8 cfilt_k_value;
@@ -207,6 +234,23 @@
 
 	/* num of slim ports required */
 	struct sitar_codec_dai_data dai[NUM_CODEC_DAIS];
+
+	/* Currently, only used for mbhc purpose, to protect
+	 * concurrent execution of mbhc threaded irq handlers and
+	 * kill race between DAPM and MBHC.But can serve as a
+	 * general lock to protect codec resource
+	 */
+	struct mutex codec_resource_lock;
+
+	struct sitar_mbhc_config mbhc_cfg;
+	bool in_gpio_handler;
+	u8 current_plug;
+	bool lpi_enabled;
+	enum sitar_mbhc_state mbhc_state;
+	struct work_struct hs_correct_plug_work;
+	bool hs_detect_work_stop;
+	struct delayed_work mbhc_btn_dwork;
+	unsigned long mbhc_last_resume; /* in jiffies */
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -910,27 +954,41 @@
 	return 0;
 }
 
-
-static void sitar_codec_disable_button_presses(struct snd_soc_codec *codec)
-{
-	snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B4_CTL, 0x80);
-	snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B3_CTL, 0x00);
-}
-
 static void sitar_codec_start_hs_polling(struct snd_soc_codec *codec)
 {
 	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	int mbhc_state = sitar->mbhc_state;
+
+	pr_debug("%s: enter\n", __func__);
+	if (!sitar->mbhc_polling_active) {
+		pr_debug("Polling is not active, do not start polling\n");
+		return;
+	}
+	snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84);
+
+
+	if (!sitar->no_mic_headset_override) {
+		if (mbhc_state == MBHC_STATE_POTENTIAL) {
+			pr_debug("%s recovering MBHC state macine\n", __func__);
+			sitar->mbhc_state = MBHC_STATE_POTENTIAL_RECOVERY;
+			/* set to max button press threshold */
+			snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B2_CTL,
+				      0x7F);
+			snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B1_CTL,
+				      0xFF);
+			snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B4_CTL,
+				       0x7F);
+			snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B3_CTL,
+				      0xFF);
+			/* set to max */
+			snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B6_CTL,
+				      0x7F);
+			snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B5_CTL,
+				      0xFF);
+		}
+	}
 
 	snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84);
-	wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL);
-	if (!sitar->no_mic_headset_override) {
-		wcd9xxx_enable_irq(codec->control_data,
-				SITAR_IRQ_MBHC_POTENTIAL);
-		wcd9xxx_enable_irq(codec->control_data,
-				SITAR_IRQ_MBHC_RELEASE);
-	} else {
-		sitar_codec_disable_button_presses(codec);
-	}
 	snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x1);
 	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0);
 	snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x1);
@@ -940,14 +998,15 @@
 {
 	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
 
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8);
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL);
-	if (!sitar->no_mic_headset_override) {
-		wcd9xxx_disable_irq(codec->control_data,
-			SITAR_IRQ_MBHC_POTENTIAL);
-		wcd9xxx_disable_irq(codec->control_data,
-			SITAR_IRQ_MBHC_RELEASE);
+	pr_debug("%s: enter\n", __func__);
+	if (!sitar->mbhc_polling_active) {
+		pr_debug("polling not active, nothing to pause\n");
+		return;
 	}
+
+	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8);
+	pr_debug("%s: leave\n", __func__);
+
 }
 
 static void sitar_codec_switch_cfilt_mode(struct snd_soc_codec *codec,
@@ -966,6 +1025,7 @@
 			sitar->mbhc_bias_regs.cfilt_ctl) & 0x40;
 
 	if (cur_mode_val != reg_mode_val) {
+		SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
 		if (sitar->mbhc_polling_active) {
 			sitar_codec_pause_hs_polling(codec);
 			mbhc_was_polling = true;
@@ -974,6 +1034,7 @@
 			sitar->mbhc_bias_regs.cfilt_ctl, 0x40, reg_mode_val);
 		if (mbhc_was_polling)
 			sitar_codec_start_hs_polling(codec);
+		SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
 		pr_debug("%s: CFILT mode change (%x to %x)\n", __func__,
 			cur_mode_val, reg_mode_val);
 	} else {
@@ -1077,10 +1138,10 @@
 	u8 hph_reg_val = 0;
 	if (left)
 		hph_reg_val = snd_soc_read(codec,
-					  SITAR_A_RX_HPH_L_DAC_CTL);
+					   SITAR_A_RX_HPH_L_DAC_CTL);
 	else
 		hph_reg_val = snd_soc_read(codec,
-					  SITAR_A_RX_HPH_R_DAC_CTL);
+					   SITAR_A_RX_HPH_R_DAC_CTL);
 
 	return (hph_reg_val & 0xC0) ? true : false;
 }
@@ -1094,7 +1155,8 @@
 
 	switch (vddio_switch) {
 	case 1:
-		if (sitar->mbhc_polling_active) {
+		if (sitar->mbhc_micbias_switched == 0 &&
+			sitar->mbhc_polling_active) {
 
 			sitar_codec_pause_hs_polling(codec);
 			/* Enable Mic Bias switch to VDDIO */
@@ -1176,9 +1238,11 @@
 	switch (event) {
 	case SND_SOC_DAPM_PRE_PMU:
 		/* Decide whether to switch the micbias for MBHC */
-		if ((w->reg == sitar->mbhc_bias_regs.ctl_reg)
-				&& sitar->mbhc_micbias_switched)
+		if (w->reg == sitar->mbhc_bias_regs.ctl_reg) {
+			SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
 			sitar_codec_switch_micbias(codec, 0);
+			SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
+		}
 
 		snd_soc_update_bits(codec, w->reg, 0x1E, 0x00);
 		sitar_codec_update_cfilt_usage(codec, cfilt_sel_val, 1);
@@ -1191,8 +1255,10 @@
 	case SND_SOC_DAPM_POST_PMU:
 		if (sitar->mbhc_polling_active &&
 		    sitar->micbias == micb_line) {
+			SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
 			sitar_codec_pause_hs_polling(codec);
 			sitar_codec_start_hs_polling(codec);
+			SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
 		}
 		break;
 	case SND_SOC_DAPM_POST_PMD:
@@ -1333,18 +1399,22 @@
 {
 	struct snd_soc_codec *codec;
 
-	if (sitar) {
-		pr_info("%s: clear ocp status %x\n", __func__, jack_status);
-		codec = sitar->codec;
+	if (!sitar) {
+		pr_err("%s: Bad sitar private data\n", __func__);
+		return;
+	}
+
+	pr_info("%s: clear ocp status %x\n", __func__, jack_status);
+	codec = sitar->codec;
+	if (sitar->hph_status & jack_status) {
 		sitar->hph_status &= ~jack_status;
-		if (sitar->headset_jack)
-			sitar_snd_soc_jack_report(sitar, sitar->headset_jack,
-						 sitar->hph_status,
-						 SITAR_JACK_MASK);
-		snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10,
-		0x00);
-		snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10,
-		0x10);
+		if (sitar->mbhc_cfg.headset_jack)
+			sitar_snd_soc_jack_report(sitar,
+						sitar->mbhc_cfg.headset_jack,
+						sitar->hph_status,
+						SITAR_JACK_MASK);
+		snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, 0x00);
+		snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, 0x10);
 		/* reset retry counter as PA is turned off signifying
 		* start of new OCP detection session
 		*/
@@ -1353,8 +1423,6 @@
 		else
 			sitar->hphrocp_cnt = 0;
 		wcd9xxx_enable_irq(codec->control_data, irq);
-	} else {
-		pr_err("%s: Bad sitar private data\n", __func__);
 	}
 }
 
@@ -1385,9 +1453,11 @@
 		mbhc_micb_ctl_val = snd_soc_read(codec,
 				sitar->mbhc_bias_regs.ctl_reg);
 
-		if (!(mbhc_micb_ctl_val & 0x80)
-				&& !sitar->mbhc_micbias_switched)
+		if (!(mbhc_micb_ctl_val & 0x80)) {
+			SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
 			sitar_codec_switch_micbias(codec, 1);
+			SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
+		}
 
 		break;
 
@@ -1413,8 +1483,9 @@
 				schedule_work(&sitar->hphrocp_work);
 		}
 
-		if (sitar->mbhc_micbias_switched)
-			sitar_codec_switch_micbias(codec, 0);
+		SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
+		sitar_codec_switch_micbias(codec, 0);
+		SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
 
 		pr_debug("%s: sleep 10 ms after %s PA disable.\n", __func__,
 				w->name);
@@ -2015,9 +2086,9 @@
 
 static int sitar_codec_mclk_index(const struct sitar_priv *sitar)
 {
-	if (sitar->mclk_freq == SITAR_MCLK_RATE_12288KHZ)
+	if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_12288KHZ)
 		return 0;
-	else if (sitar->mclk_freq == SITAR_MCLK_RATE_9600KHZ)
+	else if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_9600KHZ)
 		return 1;
 	else {
 		BUG_ON(1);
@@ -2031,7 +2102,7 @@
 	struct sitar_mbhc_btn_detect_cfg *btn_det;
 	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
 
-	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->calibration);
+	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration);
 
 	snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B1_CTL,
 		      sitar->mbhc_data.v_ins_hu & 0xFF);
@@ -2096,12 +2167,14 @@
 		substream->name, substream->stream);
 }
 
-int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable)
+int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, bool dapm)
 {
 	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
 
 	pr_debug("%s() mclk_enable = %u\n", __func__, mclk_enable);
 
+	if (dapm)
+		SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
 	if (mclk_enable) {
 		sitar->mclk_enabled = true;
 
@@ -2120,6 +2193,8 @@
 	} else {
 
 		if (!sitar->mclk_enabled) {
+			if (dapm)
+				SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
 			pr_err("Error, MCLK already diabled\n");
 			return -EINVAL;
 		}
@@ -2143,6 +2218,8 @@
 				SITAR_BANDGAP_OFF);
 		}
 	}
+	if (dapm)
+		SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
 	return 0;
 }
 
@@ -2558,13 +2635,25 @@
 	return bias_value;
 }
 
-static short sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce)
+static void sitar_turn_onoff_rel_detection(struct snd_soc_codec *codec,
+				bool on)
 {
-	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x02, on << 1);
+}
+
+static short __sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce,
+				   bool override_bypass, bool noreldetection)
+{
 	short bias_value;
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+
+	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
+	if (noreldetection)
+		sitar_turn_onoff_rel_detection(codec, false);
 
 	/* Turn on the override */
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x4, 0x4);
+	if (!override_bypass)
+		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x4, 0x4);
 	if (dce) {
 		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8);
 		snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x4);
@@ -2589,24 +2678,73 @@
 		snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x0);
 	}
 	/* Turn off the override after measuring mic voltage */
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x00);
+	if (!override_bypass)
+		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x00);
+
+	if (noreldetection)
+		sitar_turn_onoff_rel_detection(codec, true);
+	wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
 
 	return bias_value;
 }
 
+static short sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce,
+				bool norel)
+{
+	return __sitar_codec_sta_dce(codec, dce, false, norel);
+}
+
+static void sitar_codec_shutdown_hs_removal_detect(struct snd_soc_codec *codec)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	const struct sitar_mbhc_general_cfg *generic =
+	    SITAR_MBHC_CAL_GENERAL_PTR(sitar->mbhc_cfg.calibration);
+
+	if (!sitar->mclk_enabled && !sitar->mbhc_polling_active)
+		sitar_codec_enable_config_mode(codec, 1);
+
+	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2);
+	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x6, 0x0);
+
+	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x00);
+
+	usleep_range(generic->t_shutdown_plug_rem,
+		     generic->t_shutdown_plug_rem);
+
+	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0xA, 0x8);
+	if (!sitar->mclk_enabled && !sitar->mbhc_polling_active)
+		sitar_codec_enable_config_mode(codec, 0);
+
+	snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x00);
+}
+
+static void sitar_codec_cleanup_hs_polling(struct snd_soc_codec *codec)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+
+	sitar_codec_shutdown_hs_removal_detect(codec);
+
+	if (!sitar->mclk_enabled) {
+		sitar_codec_disable_clock_block(codec);
+		sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF);
+	}
+
+	sitar->mbhc_polling_active = false;
+	sitar->mbhc_state = MBHC_STATE_NONE;
+}
+
+/* called only from interrupt which is under codec_resource_lock acquisition */
 static short sitar_codec_setup_hs_polling(struct snd_soc_codec *codec)
 {
 	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
 	short bias_value;
 	u8 cfilt_mode;
 
-	if (!sitar->calibration) {
+	if (!sitar->mbhc_cfg.calibration) {
 		pr_err("Error, no sitar calibration\n");
 		return -ENODEV;
 	}
 
-	sitar->mbhc_polling_active = true;
-
 	if (!sitar->mclk_enabled) {
 		sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_MBHC_MODE);
 		sitar_enable_rx_bias(codec, 1);
@@ -2615,13 +2753,11 @@
 
 	snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x01);
 
-	snd_soc_update_bits(codec, SITAR_A_TX_COM_BIAS, 0xE0, 0xE0);
-
 	/* Make sure CFILT is in fast mode, save current mode */
-	cfilt_mode = snd_soc_read(codec,
-		sitar->mbhc_bias_regs.cfilt_ctl);
-	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl,
-		0x70, 0x00);
+	cfilt_mode = snd_soc_read(codec, sitar->mbhc_bias_regs.cfilt_ctl);
+	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x70, 0x00);
+
+	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x1F, 0x16);
 
 	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2);
 	snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84);
@@ -2639,7 +2775,8 @@
 
 	sitar_codec_calibrate_hs_polling(codec);
 
-	bias_value = sitar_codec_sta_dce(codec, 0);
+	/* don't flip override */
+	bias_value = __sitar_codec_sta_dce(codec, 1, true, true);
 	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40,
 			    cfilt_mode);
 	snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x13, 0x00);
@@ -2647,111 +2784,21 @@
 	return bias_value;
 }
 
-static int sitar_codec_enable_hs_detect(struct snd_soc_codec *codec,
-		int insertion)
+static int sitar_cancel_btn_work(struct sitar_priv *sitar)
 {
-	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
-	int central_bias_enabled = 0;
-	const struct sitar_mbhc_general_cfg *generic =
-	    SITAR_MBHC_CAL_GENERAL_PTR(sitar->calibration);
-	const struct sitar_mbhc_plug_detect_cfg *plug_det =
-	    SITAR_MBHC_CAL_PLUG_DET_PTR(sitar->calibration);
-	u8 wg_time;
+	int r = 0;
+	struct wcd9xxx *core = dev_get_drvdata(sitar->codec->dev->parent);
 
-	if (!sitar->calibration) {
-		pr_err("Error, no sitar calibration\n");
-		return -EINVAL;
+	if (cancel_delayed_work_sync(&sitar->mbhc_btn_dwork)) {
+		/* if scheduled mbhc_btn_dwork is canceled from here,
+		 * we have to unlock from here instead btn_work */
+		wcd9xxx_unlock_sleep(core);
+		r = 1;
 	}
-
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x1, 0);
-
-	if (insertion) {
-		/* Make sure mic bias and Mic line schmitt trigger
-		* are turned OFF
-		*/
-		snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg,
-			0x81, 0x01);
-		snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg,
-			0x90, 0x00);
-		wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ;
-		wg_time += 1;
-
-		/* Enable HPH Schmitt Trigger */
-		snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x11, 0x11);
-		snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x0C,
-			plug_det->hph_current << 2);
-
-		/* Turn off HPH PAs and DAC's during insertion detection to
-		* avoid false insertion interrupts
-		*/
-		if (sitar->mbhc_micbias_switched)
-			sitar_codec_switch_micbias(codec, 0);
-		snd_soc_update_bits(codec, SITAR_A_RX_HPH_CNP_EN, 0x30, 0x00);
-		snd_soc_update_bits(codec, SITAR_A_RX_HPH_L_DAC_CTL,
-			0xC0, 0x00);
-		snd_soc_update_bits(codec, SITAR_A_RX_HPH_R_DAC_CTL,
-			0xC0, 0x00);
-		usleep_range(wg_time * 1000, wg_time * 1000);
-
-		/* setup for insetion detection */
-		snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x02, 0x02);
-		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x2, 0);
-	} else {
-		/* Make sure the HPH schmitt trigger is OFF */
-		snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x12, 0x00);
-
-		/* enable the mic line schmitt trigger */
-		snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg, 0x60,
-			plug_det->mic_current << 5);
-		snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg,
-			0x80, 0x80);
-		usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid);
-		snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg,
-			0x10, 0x10);
-
-		/* Setup for low power removal detection */
-		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x2, 0x2);
-	}
-
-	if (snd_soc_read(codec, SITAR_A_CDC_MBHC_B1_CTL) & 0x4) {
-		if (!(sitar->clock_active)) {
-			sitar_codec_enable_config_mode(codec, 1);
-			snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL,
-				0x06, 0);
-			usleep_range(generic->t_shutdown_plug_rem,
-				generic->t_shutdown_plug_rem);
-			sitar_codec_enable_config_mode(codec, 0);
-		} else
-			snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL,
-				0x06, 0);
-	}
-
-	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.int_rbias, 0x80, 0);
-
-	/* If central bandgap disabled */
-	if (!(snd_soc_read(codec, SITAR_A_PIN_CTL_OE1) & 1)) {
-		snd_soc_update_bits(codec, SITAR_A_PIN_CTL_OE1, 0x3, 0x3);
-		usleep_range(generic->t_bg_fast_settle,
-			generic->t_bg_fast_settle);
-		central_bias_enabled = 1;
-	}
-
-	/* If LDO_H disabled */
-	if (snd_soc_read(codec, SITAR_A_PIN_CTL_OE0) & 0x80) {
-		snd_soc_update_bits(codec, SITAR_A_PIN_CTL_OE0, 0x10, 0);
-		snd_soc_update_bits(codec, SITAR_A_PIN_CTL_OE0, 0x80, 0x80);
-		usleep_range(generic->t_ldoh, generic->t_ldoh);
-		snd_soc_update_bits(codec, SITAR_A_PIN_CTL_OE0, 0x80, 0);
-
-		if (central_bias_enabled)
-			snd_soc_update_bits(codec, SITAR_A_PIN_CTL_OE1, 0x1, 0);
-	}
-
-	wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION);
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x1, 0x1);
-	return 0;
+	return r;
 }
 
+
 static u16 sitar_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce,
 				 s16 vin_mv)
 {
@@ -2783,58 +2830,63 @@
 				 u16 bias_value)
 {
 	struct sitar_priv *sitar;
+	s16 value, z, mb;
 	s32 mv;
 
 	sitar = snd_soc_codec_get_drvdata(codec);
+	value = bias_value;
 
 	if (dce) {
-		mv = ((s32)bias_value - (s32)sitar->mbhc_data.dce_z) *
-		     (s32)sitar->mbhc_data.micb_mv /
-		     (s32)(sitar->mbhc_data.dce_mb - sitar->mbhc_data.dce_z);
+		z = (sitar->mbhc_data.dce_z);
+		mb = (sitar->mbhc_data.dce_mb);
+		mv = (value - z) * (s32)sitar->mbhc_data.micb_mv / (mb - z);
 	} else {
-		mv = ((s32)bias_value - (s32)sitar->mbhc_data.sta_z) *
-		     (s32)sitar->mbhc_data.micb_mv /
-		     (s32)(sitar->mbhc_data.sta_mb - sitar->mbhc_data.sta_z);
+		z = (sitar->mbhc_data.sta_z);
+		mb = (sitar->mbhc_data.sta_mb);
+		mv = (value - z) * (s32)sitar->mbhc_data.micb_mv / (mb - z);
 	}
 
 	return mv;
 }
 
-static void btn0_lpress_fn(struct work_struct *work)
+static void btn_lpress_fn(struct work_struct *work)
 {
 	struct delayed_work *delayed_work;
 	struct sitar_priv *sitar;
 	short bias_value;
 	int dce_mv, sta_mv;
-	struct sitar *core;
+	struct wcd9xxx *core;
 
 	pr_debug("%s:\n", __func__);
 
 	delayed_work = to_delayed_work(work);
-	sitar = container_of(delayed_work, struct sitar_priv, btn0_dwork);
+	sitar = container_of(delayed_work, struct sitar_priv, mbhc_btn_dwork);
 	core = dev_get_drvdata(sitar->codec->dev->parent);
 
 	if (sitar) {
-		if (sitar->button_jack) {
+		if (sitar->mbhc_cfg.button_jack) {
 			bias_value = sitar_codec_read_sta_result(sitar->codec);
 			sta_mv = sitar_codec_sta_dce_v(sitar->codec, 0,
-						bias_value);
+							bias_value);
 			bias_value = sitar_codec_read_dce_result(sitar->codec);
 			dce_mv = sitar_codec_sta_dce_v(sitar->codec, 1,
-						bias_value);
+							bias_value);
 			pr_debug("%s: Reporting long button press event"
-				 " STA: %d, DCE: %d\n", __func__,
-				 sta_mv, dce_mv);
-			sitar_snd_soc_jack_report(sitar, sitar->button_jack,
-						  SND_JACK_BTN_0,
-						  SND_JACK_BTN_0);
+			" STA: %d, DCE: %d\n", __func__, sta_mv, dce_mv);
+			sitar_snd_soc_jack_report(sitar,
+						sitar->mbhc_cfg.button_jack,
+						sitar->buttons_pressed,
+						sitar->buttons_pressed);
 		}
 	} else {
 		pr_err("%s: Bad sitar private data\n", __func__);
 	}
 
+	pr_debug("%s: leave\n", __func__);
+	wcd9xxx_unlock_sleep(core);
 }
 
+
 void sitar_mbhc_cal(struct snd_soc_codec *codec)
 {
 	struct sitar_priv *sitar;
@@ -2844,24 +2896,29 @@
 	u32 mclk_rate;
 	u32 dce_wait, sta_wait;
 	u8 *n_cic;
+	void *calibration;
 
 	sitar = snd_soc_codec_get_drvdata(codec);
+	calibration = sitar->mbhc_cfg.calibration;
+
+	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
+	sitar_turn_onoff_rel_detection(codec, false);
 
 	/* First compute the DCE / STA wait times
 	 * depending on tunable parameters.
 	 * The value is computed in microseconds
 	 */
-	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->calibration);
+	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(calibration);
 	n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC);
 	ncic = n_cic[sitar_codec_mclk_index(sitar)];
-	nmeas = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->calibration)->n_meas;
-	navg = SITAR_MBHC_CAL_GENERAL_PTR(sitar->calibration)->mbhc_navg;
-	mclk_rate = sitar->mclk_freq;
-	dce_wait = (1000 * 512 * ncic * (nmeas + 1)) / (mclk_rate / 1000);
+	nmeas = SITAR_MBHC_CAL_BTN_DET_PTR(calibration)->n_meas;
+	navg = SITAR_MBHC_CAL_GENERAL_PTR(calibration)->mbhc_navg;
+	mclk_rate = sitar->mbhc_cfg.mclk_rate;
+	dce_wait = (1000 * 512 * 60 * (nmeas + 1)) / (mclk_rate / 1000);
 	sta_wait = (1000 * 128 * (navg + 1)) / (mclk_rate / 1000);
 
-	sitar->mbhc_data.t_dce = dce_wait;
-	sitar->mbhc_data.t_sta = sta_wait;
+	sitar->mbhc_data.t_dce = DEFAULT_DCE_WAIT;
+	sitar->mbhc_data.t_sta = DEFAULT_STA_WAIT;
 
 	/* LDOH and CFILT are already configured during pdata handling.
 	 * Only need to make sure CFILT and bandgap are in Fast mode.
@@ -2876,9 +2933,10 @@
 	 * to perform ADC calibration
 	 */
 	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x60,
-			    sitar->micbias << 5);
+			    sitar->mbhc_cfg.micbias << 5);
 	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x01, 0x00);
 	snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x60, 0x60);
+	snd_soc_write(codec, SITAR_A_TX_4_MBHC_TEST_CTL, 0x78);
 	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x04);
 
 	/* DCE measurement for 0 volts */
@@ -2925,6 +2983,9 @@
 
 	snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84);
 	usleep_range(100, 100);
+
+	wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
+	sitar_turn_onoff_rel_detection(codec, true);
 }
 
 void *sitar_mbhc_cal_btn_det_mp(const struct sitar_mbhc_btn_detect_cfg* btn_det,
@@ -2962,21 +3023,22 @@
 	int i;
 
 	sitar = snd_soc_codec_get_drvdata(codec);
-	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->calibration);
-	plug_type = SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->calibration);
+	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration);
+	plug_type = SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration);
 
 	n_ready = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_READY);
-	if (sitar->mclk_freq == SITAR_MCLK_RATE_12288KHZ) {
+	if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_12288KHZ) {
 		sitar->mbhc_data.npoll = 9;
 		sitar->mbhc_data.nbounce_wait = 30;
-	} else if (sitar->mclk_freq == SITAR_MCLK_RATE_9600KHZ) {
+	} else if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_9600KHZ) {
 		sitar->mbhc_data.npoll = 7;
 		sitar->mbhc_data.nbounce_wait = 23;
 	}
 
-	sitar->mbhc_data.t_sta_dce = ((1000 * 256) / (sitar->mclk_freq / 1000) *
-				      n_ready[sitar_codec_mclk_index(sitar)]) +
-				     10;
+	sitar->mbhc_data.t_sta_dce = ((1000 * 256) /
+				(sitar->mbhc_cfg.mclk_rate / 1000) *
+				n_ready[sitar_codec_mclk_index(sitar)]) +
+				10;
 	sitar->mbhc_data.v_ins_hu =
 	    sitar_codec_v_sta_dce(codec, STA, plug_type->v_hs_max);
 	sitar->mbhc_data.v_ins_h =
@@ -2998,7 +3060,7 @@
 	    sitar_codec_v_sta_dce(codec, DCE, btn_delta_mv);
 
 	sitar->mbhc_data.v_brh = sitar->mbhc_data.v_b1_h;
-	sitar->mbhc_data.v_brl = 0xFA55;
+	sitar->mbhc_data.v_brl = SITAR_MBHC_BUTTON_MIN;
 
 	sitar->mbhc_data.v_no_mic =
 	    sitar_codec_v_sta_dce(codec, STA, plug_type->v_no_mic);
@@ -3012,9 +3074,10 @@
 	int n;
 	u8 *n_cic, *gain;
 
+	pr_err("%s(): ENTER\n", __func__);
 	sitar = snd_soc_codec_get_drvdata(codec);
-	generic = SITAR_MBHC_CAL_GENERAL_PTR(sitar->calibration);
-	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->calibration);
+	generic = SITAR_MBHC_CAL_GENERAL_PTR(sitar->mbhc_cfg.calibration);
+	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration);
 
 	for (n = 0; n < 8; n++) {
 		if (n != 7) {
@@ -3049,7 +3112,13 @@
 	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x78,
 			    btn_det->mbhc_nsc << 3);
 
+	snd_soc_update_bits(codec, SITAR_A_MICB_1_MBHC, 0x03,
+						sitar->mbhc_cfg.micbias);
+
 	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x02, 0x02);
+
+	snd_soc_update_bits(codec, SITAR_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0);
+
 }
 
 static bool sitar_mbhc_fw_validate(const struct firmware *fw)
@@ -3083,13 +3152,563 @@
 
 	return true;
 }
+
+
+static void sitar_turn_onoff_override(struct snd_soc_codec *codec, bool on)
+{
+	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, on << 2);
+}
+
+/* called under codec_resource_lock acquisition */
+void sitar_set_and_turnoff_hph_padac(struct snd_soc_codec *codec)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	u8 wg_time;
+
+	wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ;
+	wg_time += 1;
+
+	/* If headphone PA is on, check if userspace receives
+	 * removal event to sync-up PA's state */
+	if (sitar_is_hph_pa_on(codec)) {
+		pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__);
+		set_bit(SITAR_HPHL_PA_OFF_ACK, &sitar->hph_pa_dac_state);
+		set_bit(SITAR_HPHR_PA_OFF_ACK, &sitar->hph_pa_dac_state);
+	} else {
+		pr_debug("%s PA is off\n", __func__);
+	}
+
+	if (sitar_is_hph_dac_on(codec, 1))
+		set_bit(SITAR_HPHL_DAC_OFF_ACK, &sitar->hph_pa_dac_state);
+	if (sitar_is_hph_dac_on(codec, 0))
+		set_bit(SITAR_HPHR_DAC_OFF_ACK, &sitar->hph_pa_dac_state);
+
+	snd_soc_update_bits(codec, SITAR_A_RX_HPH_CNP_EN, 0x30, 0x00);
+	snd_soc_update_bits(codec, SITAR_A_RX_HPH_L_DAC_CTL,
+			    0xC0, 0x00);
+	snd_soc_update_bits(codec, SITAR_A_RX_HPH_R_DAC_CTL,
+			    0xC0, 0x00);
+	usleep_range(wg_time * 1000, wg_time * 1000);
+}
+
+static void sitar_clr_and_turnon_hph_padac(struct sitar_priv *sitar)
+{
+	bool pa_turned_on = false;
+	struct snd_soc_codec *codec = sitar->codec;
+	u8 wg_time;
+
+	wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ;
+	wg_time += 1;
+
+	if (test_and_clear_bit(SITAR_HPHR_DAC_OFF_ACK,
+			       &sitar->hph_pa_dac_state)) {
+		pr_debug("%s: HPHR clear flag and enable DAC\n", __func__);
+		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_R_DAC_CTL,
+				    0xC0, 0xC0);
+	}
+	if (test_and_clear_bit(SITAR_HPHL_DAC_OFF_ACK,
+			       &sitar->hph_pa_dac_state)) {
+		pr_debug("%s: HPHL clear flag and enable DAC\n", __func__);
+		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_L_DAC_CTL,
+				    0xC0, 0xC0);
+	}
+
+	if (test_and_clear_bit(SITAR_HPHR_PA_OFF_ACK,
+			       &sitar->hph_pa_dac_state)) {
+		pr_debug("%s: HPHR clear flag and enable PA\n", __func__);
+		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x10,
+				    1 << 4);
+		pa_turned_on = true;
+	}
+	if (test_and_clear_bit(SITAR_HPHL_PA_OFF_ACK,
+			       &sitar->hph_pa_dac_state)) {
+		pr_debug("%s: HPHL clear flag and enable PA\n", __func__);
+		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x20,
+				    1 << 5);
+		pa_turned_on = true;
+	}
+
+	if (pa_turned_on) {
+		pr_debug("%s: PA was turned off by MBHC and not by DAPM\n",
+				__func__);
+		usleep_range(wg_time * 1000, wg_time * 1000);
+	}
+}
+
+static void sitar_codec_report_plug(struct snd_soc_codec *codec, int insertion,
+				    enum snd_jack_types jack_type)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+
+	if (!insertion) {
+		/* Report removal */
+		sitar->hph_status &= ~jack_type;
+		if (sitar->mbhc_cfg.headset_jack) {
+			/* cancel possibly scheduled btn work and
+			* report release if we reported button press */
+			if (sitar_cancel_btn_work(sitar)) {
+				pr_debug("%s: button press is canceled\n",
+					__func__);
+			} else if (sitar->buttons_pressed) {
+				pr_debug("%s: Reporting release for reported "
+					 "button press %d\n", __func__,
+					 jack_type);
+				sitar_snd_soc_jack_report(sitar,
+						 sitar->mbhc_cfg.button_jack, 0,
+						 sitar->buttons_pressed);
+				sitar->buttons_pressed &=
+							~SITAR_JACK_BUTTON_MASK;
+			}
+			pr_debug("%s: Reporting removal %d\n", __func__,
+				 jack_type);
+			sitar_snd_soc_jack_report(sitar,
+						  sitar->mbhc_cfg.headset_jack,
+						  sitar->hph_status,
+						  SITAR_JACK_MASK);
+		}
+		sitar_set_and_turnoff_hph_padac(codec);
+		hphocp_off_report(sitar, SND_JACK_OC_HPHR,
+				  SITAR_IRQ_HPH_PA_OCPR_FAULT);
+		hphocp_off_report(sitar, SND_JACK_OC_HPHL,
+				  SITAR_IRQ_HPH_PA_OCPL_FAULT);
+		sitar->current_plug = PLUG_TYPE_NONE;
+		sitar->mbhc_polling_active = false;
+	} else {
+		/* Report insertion */
+		sitar->hph_status |= jack_type;
+
+		if (jack_type == SND_JACK_HEADPHONE)
+			sitar->current_plug = PLUG_TYPE_HEADPHONE;
+		else if (jack_type == SND_JACK_HEADSET) {
+			sitar->mbhc_polling_active = true;
+			sitar->current_plug = PLUG_TYPE_HEADSET;
+		}
+		if (sitar->mbhc_cfg.headset_jack) {
+			pr_debug("%s: Reporting insertion %d\n", __func__,
+				 jack_type);
+			sitar_snd_soc_jack_report(sitar,
+						  sitar->mbhc_cfg.headset_jack,
+						  sitar->hph_status,
+						  SITAR_JACK_MASK);
+		}
+		sitar_clr_and_turnon_hph_padac(sitar);
+	}
+}
+
+
+static bool sitar_hs_gpio_level_remove(struct sitar_priv *sitar)
+{
+	return (gpio_get_value_cansleep(sitar->mbhc_cfg.gpio) !=
+		sitar->mbhc_cfg.gpio_level_insert);
+}
+
+static bool sitar_is_invalid_insert_delta(struct snd_soc_codec *codec,
+					int mic_volt, int mic_volt_prev)
+{
+	int delta = abs(mic_volt - mic_volt_prev);
+	if (delta > SITAR_MBHC_FAKE_INSERT_VOLT_DELTA_MV) {
+		pr_debug("%s: volt delta %dmv\n", __func__, delta);
+		return true;
+	}
+	return false;
+}
+
+static bool sitar_is_invalid_insertion_range(struct snd_soc_codec *codec,
+				       s32 mic_volt)
+{
+	bool invalid = false;
+
+	if (mic_volt < SITAR_MBHC_FAKE_INSERT_HIGH
+			&& (mic_volt > SITAR_MBHC_FAKE_INSERT_LOW)) {
+		invalid = true;
+	}
+
+	return invalid;
+}
+
+static bool sitar_codec_is_invalid_plug(struct snd_soc_codec *codec,
+	s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT],
+	enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT])
+{
+	int i;
+	bool r = false;
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	struct sitar_mbhc_plug_type_cfg *plug_type_ptr =
+		SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration);
+
+	for (i = 0 ; i < MBHC_NUM_DCE_PLUG_DETECT && !r; i++) {
+		if (mic_mv[i] < plug_type_ptr->v_no_mic)
+			plug_type[i] = PLUG_TYPE_HEADPHONE;
+		else if (mic_mv[i] < plug_type_ptr->v_hs_max)
+			plug_type[i] = PLUG_TYPE_HEADSET;
+		else if (mic_mv[i] > plug_type_ptr->v_hs_max)
+			plug_type[i] = PLUG_TYPE_HIGH_HPH;
+
+		r = sitar_is_invalid_insertion_range(codec, mic_mv[i]);
+		if (!r && i > 0) {
+			if (plug_type[i-1] != plug_type[i])
+				r = true;
+			else
+				r = sitar_is_invalid_insert_delta(codec,
+							mic_mv[i],
+							mic_mv[i - 1]);
+		}
+	}
+
+	return r;
+}
+
+/* called under codec_resource_lock acquisition */
+void sitar_find_plug_and_report(struct snd_soc_codec *codec,
+				enum sitar_mbhc_plug_type plug_type)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+
+	if (plug_type == PLUG_TYPE_HEADPHONE
+		&& sitar->current_plug == PLUG_TYPE_NONE) {
+		/* Nothing was reported previously
+		 * reporte a headphone
+		 */
+		sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
+		sitar_codec_cleanup_hs_polling(codec);
+	} else if (plug_type == PLUG_TYPE_HEADSET) {
+		/* If Headphone was reported previously, this will
+		 * only report the mic line
+		 */
+		sitar_codec_report_plug(codec, 1, SND_JACK_HEADSET);
+		msleep(100);
+		sitar_codec_start_hs_polling(codec);
+	} else if (plug_type == PLUG_TYPE_HIGH_HPH) {
+		if (sitar->current_plug == PLUG_TYPE_NONE)
+			sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
+		sitar_codec_cleanup_hs_polling(codec);
+		pr_debug("setup mic trigger for further detection\n");
+		sitar->lpi_enabled = true;
+		/* TODO ::: sitar_codec_enable_hs_detect */
+		pr_err("%s(): High impedence hph not supported\n", __func__);
+	}
+}
+
+/* should be called under interrupt context that hold suspend */
+static void sitar_schedule_hs_detect_plug(struct sitar_priv *sitar)
+{
+	pr_debug("%s: scheduling sitar_hs_correct_gpio_plug\n", __func__);
+	sitar->hs_detect_work_stop = false;
+	wcd9xxx_lock_sleep(sitar->codec->control_data);
+	schedule_work(&sitar->hs_correct_plug_work);
+}
+
+/* called under codec_resource_lock acquisition */
+static void sitar_cancel_hs_detect_plug(struct sitar_priv *sitar)
+{
+	pr_debug("%s: canceling hs_correct_plug_work\n", __func__);
+	sitar->hs_detect_work_stop = true;
+	wmb();
+	SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
+	if (cancel_work_sync(&sitar->hs_correct_plug_work)) {
+		pr_debug("%s: hs_correct_plug_work is canceled\n", __func__);
+		wcd9xxx_unlock_sleep(sitar->codec->control_data);
+	}
+	SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
+}
+
+static void sitar_hs_correct_gpio_plug(struct work_struct *work)
+{
+	struct sitar_priv *sitar;
+	struct snd_soc_codec *codec;
+	int retry = 0, i;
+	bool correction = false;
+	s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT];
+	short mb_v[MBHC_NUM_DCE_PLUG_DETECT];
+	enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT];
+	unsigned long timeout;
+
+	sitar = container_of(work, struct sitar_priv, hs_correct_plug_work);
+	codec = sitar->codec;
+
+	pr_debug("%s: enter\n", __func__);
+	sitar->mbhc_cfg.mclk_cb_fn(codec, 1, false);
+
+	/* Keep override on during entire plug type correction work.
+	 *
+	 * This is okay under the assumption that any GPIO irqs which use
+	 * MBHC block cancel and sync this work so override is off again
+	 * prior to GPIO interrupt handler's MBHC block usage.
+	 * Also while this correction work is running, we can guarantee
+	 * DAPM doesn't use any MBHC block as this work only runs with
+	 * headphone detection.
+	 */
+	sitar_turn_onoff_override(codec, true);
+
+	timeout = jiffies + msecs_to_jiffies(SITAR_HS_DETECT_PLUG_TIME_MS);
+	while (!time_after(jiffies, timeout)) {
+		++retry;
+		rmb();
+		if (sitar->hs_detect_work_stop) {
+			pr_debug("%s: stop requested\n", __func__);
+			break;
+		}
+
+		msleep(SITAR_HS_DETECT_PLUG_INERVAL_MS);
+		if (sitar_hs_gpio_level_remove(sitar)) {
+			pr_debug("%s: GPIO value is low\n", __func__);
+			break;
+		}
+
+		/* can race with removal interrupt */
+		SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
+		for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) {
+			mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true);
+			mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]);
+			pr_debug("%s : DCE run %d, mic_mv = %d(%x)\n",
+				 __func__, retry, mic_mv[i], mb_v[i]);
+		}
+		SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
+
+		if (sitar_codec_is_invalid_plug(codec, mic_mv, plug_type)) {
+			pr_debug("Invalid plug in attempt # %d\n", retry);
+			if (retry == NUM_ATTEMPTS_TO_REPORT &&
+			    sitar->current_plug == PLUG_TYPE_NONE) {
+				sitar_codec_report_plug(codec, 1,
+							SND_JACK_HEADPHONE);
+			}
+		} else if (!sitar_codec_is_invalid_plug(codec, mic_mv,
+							plug_type) &&
+			   plug_type[0] == PLUG_TYPE_HEADPHONE) {
+			pr_debug("Good headphone detected, continue polling mic\n");
+			if (sitar->current_plug == PLUG_TYPE_NONE) {
+				sitar_codec_report_plug(codec, 1,
+							SND_JACK_HEADPHONE);
+			}
+		} else {
+			SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
+			/* Turn off override */
+			sitar_turn_onoff_override(codec, false);
+			sitar_find_plug_and_report(codec, plug_type[0]);
+			SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
+			pr_debug("Attempt %d found correct plug %d\n", retry,
+				 plug_type[0]);
+			correction = true;
+			break;
+		}
+	}
+
+	/* Turn off override */
+	if (!correction)
+		sitar_turn_onoff_override(codec, false);
+
+	sitar->mbhc_cfg.mclk_cb_fn(codec, 0, false);
+	pr_debug("%s: leave\n", __func__);
+	/* unlock sleep */
+	wcd9xxx_unlock_sleep(sitar->codec->control_data);
+}
+
+/* called under codec_resource_lock acquisition */
+static void sitar_codec_decide_gpio_plug(struct snd_soc_codec *codec)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	short mb_v[MBHC_NUM_DCE_PLUG_DETECT];
+	s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT];
+	enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT];
+	int i;
+
+	pr_debug("%s: enter\n", __func__);
+
+	sitar_turn_onoff_override(codec, true);
+	mb_v[0] = sitar_codec_setup_hs_polling(codec);
+	mic_mv[0] = sitar_codec_sta_dce_v(codec, 1, mb_v[0]);
+	pr_debug("%s: DCE run 1, mic_mv = %d\n", __func__, mic_mv[0]);
+
+	for (i = 1; i < MBHC_NUM_DCE_PLUG_DETECT; i++) {
+		mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true);
+		mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]);
+		pr_debug("%s: DCE run %d, mic_mv = %d\n", __func__, i + 1,
+			 mic_mv[i]);
+	}
+	sitar_turn_onoff_override(codec, false);
+
+	if (sitar_hs_gpio_level_remove(sitar)) {
+		pr_debug("%s: GPIO value is low when determining plug\n",
+			 __func__);
+		return;
+	}
+
+	if (sitar_codec_is_invalid_plug(codec, mic_mv, plug_type)) {
+		sitar_schedule_hs_detect_plug(sitar);
+	} else if (plug_type[0] == PLUG_TYPE_HEADPHONE) {
+		sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
+		sitar_schedule_hs_detect_plug(sitar);
+	} else if (plug_type[0] == PLUG_TYPE_HEADSET) {
+		pr_debug("%s: Valid plug found, determine plug type\n",
+			 __func__);
+		sitar_find_plug_and_report(codec, plug_type[0]);
+	}
+
+}
+
+/* called under codec_resource_lock acquisition */
+static void sitar_codec_detect_plug_type(struct snd_soc_codec *codec)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	const struct sitar_mbhc_plug_detect_cfg *plug_det =
+	    SITAR_MBHC_CAL_PLUG_DET_PTR(sitar->mbhc_cfg.calibration);
+
+	if (plug_det->t_ins_complete > 20)
+		msleep(plug_det->t_ins_complete);
+	else
+		usleep_range(plug_det->t_ins_complete * 1000,
+			     plug_det->t_ins_complete * 1000);
+
+	if (sitar_hs_gpio_level_remove(sitar))
+		pr_debug("%s: GPIO value is low when determining "
+				 "plug\n", __func__);
+	else
+		sitar_codec_decide_gpio_plug(codec);
+
+	return;
+}
+
+static void sitar_hs_gpio_handler(struct snd_soc_codec *codec)
+{
+	bool insert;
+	struct sitar_priv *priv = snd_soc_codec_get_drvdata(codec);
+	bool is_removed = false;
+
+	pr_debug("%s: enter\n", __func__);
+
+	priv->in_gpio_handler = true;
+	/* Wait here for debounce time */
+	usleep_range(SITAR_GPIO_IRQ_DEBOUNCE_TIME_US,
+		     SITAR_GPIO_IRQ_DEBOUNCE_TIME_US);
+
+	SITAR_ACQUIRE_LOCK(priv->codec_resource_lock);
+
+	/* cancel pending button press */
+	if (sitar_cancel_btn_work(priv))
+		pr_debug("%s: button press is canceled\n", __func__);
+
+	insert = (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) ==
+		  priv->mbhc_cfg.gpio_level_insert);
+	if ((priv->current_plug == PLUG_TYPE_NONE) && insert) {
+		priv->lpi_enabled = false;
+		wmb();
+
+		/* cancel detect plug */
+		sitar_cancel_hs_detect_plug(priv);
+
+		/* Disable Mic Bias pull down and HPH Switch to GND */
+		snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01,
+				    0x00);
+		snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x01, 0x00);
+		sitar_codec_detect_plug_type(codec);
+	} else if ((priv->current_plug != PLUG_TYPE_NONE) && !insert) {
+		priv->lpi_enabled = false;
+		wmb();
+
+		/* cancel detect plug */
+		sitar_cancel_hs_detect_plug(priv);
+
+		if (priv->current_plug == PLUG_TYPE_HEADPHONE) {
+			sitar_codec_report_plug(codec, 0, SND_JACK_HEADPHONE);
+			is_removed = true;
+		} else if (priv->current_plug == PLUG_TYPE_HEADSET) {
+			sitar_codec_pause_hs_polling(codec);
+			sitar_codec_cleanup_hs_polling(codec);
+			sitar_codec_report_plug(codec, 0, SND_JACK_HEADSET);
+			is_removed = true;
+		}
+
+		if (is_removed) {
+			/* Enable Mic Bias pull down and HPH Switch to GND */
+			snd_soc_update_bits(codec,
+					    priv->mbhc_bias_regs.ctl_reg, 0x01,
+					    0x01);
+			snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x01,
+					    0x01);
+			/* Make sure mic trigger is turned off */
+			snd_soc_update_bits(codec,
+					    priv->mbhc_bias_regs.ctl_reg,
+					    0x01, 0x01);
+			snd_soc_update_bits(codec,
+					    priv->mbhc_bias_regs.mbhc_reg,
+					    0x90, 0x00);
+			/* Reset MBHC State Machine */
+			snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL,
+					    0x08, 0x08);
+			snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL,
+					    0x08, 0x00);
+			/* Turn off override */
+			sitar_turn_onoff_override(codec, false);
+		}
+	}
+
+	priv->in_gpio_handler = false;
+	SITAR_RELEASE_LOCK(priv->codec_resource_lock);
+	pr_debug("%s: leave\n", __func__);
+}
+
+static irqreturn_t sitar_mechanical_plug_detect_irq(int irq, void *data)
+{
+	int r = IRQ_HANDLED;
+	struct snd_soc_codec *codec = data;
+
+	if (unlikely(wcd9xxx_lock_sleep(codec->control_data) == false)) {
+		pr_warn("%s(): Failed to hold suspend\n", __func__);
+		r = IRQ_NONE;
+	} else {
+		sitar_hs_gpio_handler(codec);
+		wcd9xxx_unlock_sleep(codec->control_data);
+	}
+	return r;
+}
+
+static int sitar_mbhc_init_and_calibrate(struct snd_soc_codec *codec)
+{
+	int rc = 0;
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+
+	sitar->mbhc_cfg.mclk_cb_fn(codec, 1, false);
+	sitar_mbhc_init(codec);
+	sitar_mbhc_cal(codec);
+	sitar_mbhc_calc_thres(codec);
+	sitar->mbhc_cfg.mclk_cb_fn(codec, 0, false);
+	sitar_codec_calibrate_hs_polling(codec);
+
+	/* Enable Mic Bias pull down and HPH Switch to GND */
+	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg,
+						0x01, 0x01);
+	snd_soc_update_bits(codec, SITAR_A_MBHC_HPH,
+						0x01, 0x01);
+
+	rc = request_threaded_irq(sitar->mbhc_cfg.gpio_irq,
+				NULL,
+				sitar_mechanical_plug_detect_irq,
+				(IRQF_TRIGGER_RISING |
+				IRQF_TRIGGER_FALLING),
+				"sitar-hs-gpio", codec);
+
+	if (!IS_ERR_VALUE(rc)) {
+		rc = enable_irq_wake(sitar->mbhc_cfg.gpio_irq);
+		snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL,
+							0x10, 0x10);
+		wcd9xxx_enable_irq(codec->control_data,
+					SITAR_IRQ_HPH_PA_OCPL_FAULT);
+		wcd9xxx_enable_irq(codec->control_data,
+					SITAR_IRQ_HPH_PA_OCPR_FAULT);
+		/* Bootup time detection */
+		sitar_hs_gpio_handler(codec);
+	}
+
+	return rc;
+}
+
 static void mbhc_fw_read(struct work_struct *work)
 {
 	struct delayed_work *dwork;
 	struct sitar_priv *sitar;
 	struct snd_soc_codec *codec;
 	const struct firmware *fw;
-	int ret = -1, retry = 0, rc;
+	int ret = -1, retry = 0;
 
 	dwork = to_delayed_work(work);
 	sitar = container_of(dwork, struct sitar_priv,
@@ -3124,80 +3743,53 @@
 		sitar->mbhc_fw = fw;
 	}
 
-	sitar->mclk_cb(codec, 1);
-	sitar_mbhc_init(codec);
-	sitar_mbhc_cal(codec);
-	sitar_mbhc_calc_thres(codec);
-	sitar->mclk_cb(codec, 0);
-	sitar_codec_calibrate_hs_polling(codec);
-	rc = sitar_codec_enable_hs_detect(codec, 1);
-
-	if (IS_ERR_VALUE(rc))
-		pr_err("%s: Failed to setup MBHC detection\n", __func__);
-
+	sitar_mbhc_init_and_calibrate(codec);
 }
 
 int sitar_hs_detect(struct snd_soc_codec *codec,
-		    struct snd_soc_jack *headset_jack,
-		    struct snd_soc_jack *button_jack,
-		    void *calibration, enum sitar_micbias_num micbias,
-		    int (*mclk_cb_fn) (struct snd_soc_codec*, int),
-		    int read_fw_bin, u32 mclk_rate)
+			const struct sitar_mbhc_config *cfg)
 {
 	struct sitar_priv *sitar;
 	int rc = 0;
 
-	if (!codec || !calibration) {
+	if (!codec || !cfg->calibration) {
 		pr_err("Error: no codec or calibration\n");
 		return -EINVAL;
 	}
 
-	if (mclk_rate != SITAR_MCLK_RATE_12288KHZ) {
-		if (mclk_rate == SITAR_MCLK_RATE_9600KHZ)
+	if (cfg->mclk_rate != SITAR_MCLK_RATE_12288KHZ) {
+		if (cfg->mclk_rate == SITAR_MCLK_RATE_9600KHZ)
 			pr_err("Error: clock rate %dHz is not yet supported\n",
-				mclk_rate);
+				cfg->mclk_rate);
 		else
-			pr_err("Error: unsupported clock rate %d\n", mclk_rate);
+			pr_err("Error: unsupported clock rate %d\n",
+				   cfg->mclk_rate);
 		return -EINVAL;
 	}
 
 	sitar = snd_soc_codec_get_drvdata(codec);
-	sitar->headset_jack = headset_jack;
-	sitar->button_jack = button_jack;
-	sitar->micbias = micbias;
-	sitar->calibration = calibration;
-	sitar->mclk_cb = mclk_cb_fn;
-	sitar->mclk_freq = mclk_rate;
+	sitar->mbhc_cfg = *cfg;
+	sitar->in_gpio_handler = false;
+	sitar->current_plug = PLUG_TYPE_NONE;
+	sitar->lpi_enabled = false;
 	sitar_get_mbhc_micbias_regs(codec, &sitar->mbhc_bias_regs);
 
 	/* Put CFILT in fast mode by default */
 	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl,
-		0x40, SITAR_CFILT_FAST_MODE);
+			    0x40, SITAR_CFILT_FAST_MODE);
+
 	INIT_DELAYED_WORK(&sitar->mbhc_firmware_dwork, mbhc_fw_read);
-	INIT_DELAYED_WORK(&sitar->btn0_dwork, btn0_lpress_fn);
+	INIT_DELAYED_WORK(&sitar->mbhc_btn_dwork, btn_lpress_fn);
 	INIT_WORK(&sitar->hphlocp_work, hphlocp_off_report);
 	INIT_WORK(&sitar->hphrocp_work, hphrocp_off_report);
+	INIT_WORK(&sitar->hs_correct_plug_work,
+			  sitar_hs_correct_gpio_plug);
 
-	if (!read_fw_bin) {
-		sitar->mclk_cb(codec, 1);
-		sitar_mbhc_init(codec);
-		sitar_mbhc_cal(codec);
-		sitar_mbhc_calc_thres(codec);
-		sitar->mclk_cb(codec, 0);
-		sitar_codec_calibrate_hs_polling(codec);
-		rc =  sitar_codec_enable_hs_detect(codec, 1);
+	if (!sitar->mbhc_cfg.read_fw_bin) {
+		rc = sitar_mbhc_init_and_calibrate(codec);
 	} else {
 		schedule_delayed_work(&sitar->mbhc_firmware_dwork,
-				usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
-	}
-
-	if (!IS_ERR_VALUE(rc)) {
-		snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10,
-			0x10);
-		wcd9xxx_enable_irq(codec->control_data,
-			SITAR_IRQ_HPH_PA_OCPL_FAULT);
-		wcd9xxx_enable_irq(codec->control_data,
-			SITAR_IRQ_HPH_PA_OCPR_FAULT);
+					usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
 	}
 
 	return rc;
@@ -3211,7 +3803,7 @@
 	struct sitar_mbhc_btn_detect_cfg *btn_det;
 	int i, btn = -1;
 
-	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(priv->calibration);
+	btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration);
 	v_btn_low = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_V_BTN_LOW);
 	v_btn_high = sitar_mbhc_cal_btn_det_mp(btn_det,
 				SITAR_BTN_DET_V_BTN_HIGH);
@@ -3261,33 +3853,76 @@
 	return mask;
 }
 
+
 static irqreturn_t sitar_dce_handler(int irq, void *data)
 {
 	int i, mask;
-	short bias_value_dce;
-	s32 bias_mv_dce;
+	short dce, sta, bias_value_dce;
+	s32 mv, stamv, bias_mv_dce;
 	int btn = -1, meas = 0;
 	struct sitar_priv *priv = data;
 	const struct sitar_mbhc_btn_detect_cfg *d =
-	    SITAR_MBHC_CAL_BTN_DET_PTR(priv->calibration);
+	    SITAR_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration);
 	short btnmeas[d->n_btn_meas + 1];
 	struct snd_soc_codec *codec = priv->codec;
 	struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent);
+	int n_btn_meas = d->n_btn_meas;
+	u8 mbhc_status = snd_soc_read(codec, SITAR_A_CDC_MBHC_B1_STATUS) & 0x3E;
 
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL);
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
+	pr_debug("%s: enter\n", __func__);
 
-	bias_value_dce = sitar_codec_read_dce_result(codec);
-	bias_mv_dce = sitar_codec_sta_dce_v(codec, 1, bias_value_dce);
+	SITAR_ACQUIRE_LOCK(priv->codec_resource_lock);
+	if (priv->mbhc_state == MBHC_STATE_POTENTIAL_RECOVERY) {
+		pr_debug("%s: mbhc is being recovered, skip button press\n",
+			 __func__);
+		goto done;
+	}
+
+	priv->mbhc_state = MBHC_STATE_POTENTIAL;
+
+	if (!priv->mbhc_polling_active) {
+		pr_warn("%s: mbhc polling is not active, skip button press\n",
+			__func__);
+		goto done;
+	}
+
+	dce = sitar_codec_read_dce_result(codec);
+	mv = sitar_codec_sta_dce_v(codec, 1, dce);
+
+	/* If GPIO interrupt already kicked in, ignore button press */
+	if (priv->in_gpio_handler) {
+		pr_debug("%s: GPIO State Changed, ignore button press\n",
+			 __func__);
+		btn = -1;
+		goto done;
+	}
+
+	if (mbhc_status != SITAR_MBHC_STATUS_REL_DETECTION) {
+		if (priv->mbhc_last_resume &&
+		    !time_after(jiffies, priv->mbhc_last_resume + HZ)) {
+			pr_debug("%s: Button is already released shortly after "
+				 "resume\n", __func__);
+			n_btn_meas = 0;
+		} else {
+			pr_debug("%s: Button is already released without "
+				 "resume", __func__);
+			sta = sitar_codec_read_sta_result(codec);
+			stamv = sitar_codec_sta_dce_v(codec, 0, sta);
+			btn = sitar_determine_button(priv, mv);
+			if (btn != sitar_determine_button(priv, stamv))
+				btn = -1;
+			goto done;
+		}
+	}
 
 	/* determine pressed button */
-	btnmeas[meas++] = sitar_determine_button(priv, bias_mv_dce);
+	btnmeas[meas++] = sitar_determine_button(priv, mv);
 	pr_debug("%s: meas %d - DCE %d,%d, button %d\n", __func__,
-		 meas - 1, bias_value_dce, bias_mv_dce, btnmeas[meas - 1]);
-	if (d->n_btn_meas == 0)
+		 meas - 1, dce, mv, btnmeas[meas - 1]);
+	if (n_btn_meas == 0)
 		btn = btnmeas[0];
 	for (; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); meas++) {
-		bias_value_dce = sitar_codec_sta_dce(codec, 1);
+		bias_value_dce = sitar_codec_sta_dce(codec, 1, false);
 		bias_mv_dce = sitar_codec_sta_dce_v(codec, 1, bias_value_dce);
 		btnmeas[meas] = sitar_determine_button(priv, bias_mv_dce);
 		pr_debug("%s: meas %d - DCE %d,%d, button %d\n",
@@ -3305,147 +3940,133 @@
 				/* button pressed */
 				btn = btnmeas[meas];
 				break;
+			} else if ((n_btn_meas - meas) < (d->n_btn_con - 1)) {
+				/* if left measurements are less than n_btn_con,
+				 * it's impossible to find button number */
+				break;
 			}
 		}
-		/* if left measurements are less than n_btn_con,
-		 * it's impossible to find button number */
-		if ((d->n_btn_meas - meas) < d->n_btn_con)
-			break;
 	}
 
 	if (btn >= 0) {
+		if (priv->in_gpio_handler) {
+			pr_debug("%s: GPIO already triggered, ignore button "
+				 "press\n", __func__);
+			goto done;
+		}
 		mask = sitar_get_button_mask(btn);
 		priv->buttons_pressed |= mask;
-
-		msleep(100);
-
-		/* XXX: assuming button 0 has the lowest micbias voltage */
-		if (btn == 0) {
-			wcd9xxx_lock_sleep(core);
-			if (schedule_delayed_work(&priv->btn0_dwork,
-						  msecs_to_jiffies(400)) == 0) {
-				WARN(1, "Button pressed twice without release"
-				     "event\n");
-				wcd9xxx_unlock_sleep(core);
-			}
-		} else {
-			pr_debug("%s: Reporting short button %d(0x%x) press\n",
-				  __func__, btn, mask);
-			sitar_snd_soc_jack_report(priv, priv->button_jack, mask,
-						  mask);
+		wcd9xxx_lock_sleep(core);
+		if (schedule_delayed_work(&priv->mbhc_btn_dwork,
+					  msecs_to_jiffies(400)) == 0) {
+			WARN(1, "Button pressed twice without release"
+			     "event\n");
+			wcd9xxx_unlock_sleep(core);
 		}
 	} else {
 		pr_debug("%s: bogus button press, too short press?\n",
 			 __func__);
 	}
 
+ done:
+	pr_debug("%s: leave\n", __func__);
+	SITAR_RELEASE_LOCK(priv->codec_resource_lock);
 	return IRQ_HANDLED;
 }
 
+static int sitar_is_fake_press(struct sitar_priv *priv)
+{
+	int i;
+	int r = 0;
+	struct snd_soc_codec *codec = priv->codec;
+	const int dces = MBHC_NUM_DCE_PLUG_DETECT;
+	short mb_v;
+
+	for (i = 0; i < dces; i++) {
+		usleep_range(10000, 10000);
+		if (i == 0) {
+			mb_v = sitar_codec_sta_dce(codec, 0, true);
+			pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v,
+			sitar_codec_sta_dce_v(codec, 0, mb_v));
+			if (mb_v < (short)priv->mbhc_data.v_b1_hu ||
+				mb_v > (short)priv->mbhc_data.v_ins_hu) {
+				r = 1;
+				break;
+			}
+		} else {
+			mb_v = sitar_codec_sta_dce(codec, 1, true);
+			pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v,
+			sitar_codec_sta_dce_v(codec, 1, mb_v));
+			if (mb_v < (short)priv->mbhc_data.v_b1_h ||
+				mb_v > (short)priv->mbhc_data.v_ins_h) {
+				r = 1;
+				break;
+			}
+		}
+	}
+
+	return r;
+}
+
 static irqreturn_t sitar_release_handler(int irq, void *data)
 {
 	int ret;
-	short mb_v;
 	struct sitar_priv *priv = data;
 	struct snd_soc_codec *codec = priv->codec;
-	struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent);
 
 	pr_debug("%s: enter\n", __func__);
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE);
 
-	if (priv->buttons_pressed & SND_JACK_BTN_0) {
-		ret = cancel_delayed_work(&priv->btn0_dwork);
+	SITAR_ACQUIRE_LOCK(priv->codec_resource_lock);
+	priv->mbhc_state = MBHC_STATE_RELEASE;
 
+	if (priv->buttons_pressed & SITAR_JACK_BUTTON_MASK) {
+		ret = sitar_cancel_btn_work(priv);
 		if (ret == 0) {
-			pr_debug("%s: Reporting long button 0 release event\n",
+			pr_debug("%s: Reporting long button release event\n",
 				 __func__);
-			if (priv->button_jack)
+			if (priv->mbhc_cfg.button_jack)
 				sitar_snd_soc_jack_report(priv,
-							  priv->button_jack, 0,
-							  SND_JACK_BTN_0);
+						  priv->mbhc_cfg.button_jack, 0,
+						  priv->buttons_pressed);
 		} else {
-			/* if scheduled btn0_dwork is canceled from here,
-			 * we have to unlock from here instead btn0_work */
-			wcd9xxx_unlock_sleep(core);
-			mb_v = sitar_codec_sta_dce(codec, 0);
-			pr_debug("%s: Mic Voltage on release STA: %d,%d\n",
-				 __func__, mb_v,
-				 sitar_codec_sta_dce_v(codec, 0, mb_v));
-
-			if (mb_v < (short)priv->mbhc_data.v_b1_hu ||
-			    mb_v > (short)priv->mbhc_data.v_ins_hu)
-				pr_debug("%s: Fake buttton press interrupt\n",
+			if (sitar_is_fake_press(priv)) {
+				pr_debug("%s: Fake button press interrupt\n",
 					 __func__);
-			else if (priv->button_jack) {
-				pr_debug("%s: Reporting short button 0 "
-					 "press and release\n", __func__);
-				sitar_snd_soc_jack_report(priv,
-							  priv->button_jack,
-							  SND_JACK_BTN_0,
-							  SND_JACK_BTN_0);
-				sitar_snd_soc_jack_report(priv,
-							  priv->button_jack, 0,
-							  SND_JACK_BTN_0);
+			} else if (priv->mbhc_cfg.button_jack) {
+				if (priv->in_gpio_handler) {
+					pr_debug("%s: GPIO kicked in, ignore\n",
+						 __func__);
+				} else {
+					pr_debug("%s: Reporting short button 0 "
+						 "press and release\n",
+						 __func__);
+					sitar_snd_soc_jack_report(priv,
+						priv->mbhc_cfg.button_jack,
+						priv->buttons_pressed,
+						priv->buttons_pressed);
+					sitar_snd_soc_jack_report(priv,
+						priv->mbhc_cfg.button_jack, 0,
+						priv->buttons_pressed);
+				}
 			}
 		}
 
-		priv->buttons_pressed &= ~SND_JACK_BTN_0;
-	}
-
-	if (priv->buttons_pressed) {
-		pr_debug("%s:reporting button release mask 0x%x\n", __func__,
-			 priv->buttons_pressed);
-		sitar_snd_soc_jack_report(priv, priv->button_jack, 0,
-					  priv->buttons_pressed);
-		/* hardware doesn't detect another button press until
-		 * already pressed button is released.
-		 * therefore buttons_pressed has only one button's mask. */
 		priv->buttons_pressed &= ~SITAR_JACK_BUTTON_MASK;
 	}
 
+	sitar_codec_calibrate_hs_polling(codec);
+
+	if (priv->mbhc_cfg.gpio)
+		msleep(SITAR_MBHC_GPIO_REL_DEBOUNCE_TIME_MS);
+
 	sitar_codec_start_hs_polling(codec);
+
+	pr_debug("%s: leave\n", __func__);
+	SITAR_RELEASE_LOCK(priv->codec_resource_lock);
+
 	return IRQ_HANDLED;
 }
 
-static void sitar_codec_shutdown_hs_removal_detect(struct snd_soc_codec *codec)
-{
-	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
-	const struct sitar_mbhc_general_cfg *generic =
-	    SITAR_MBHC_CAL_GENERAL_PTR(sitar->calibration);
-
-	if (!sitar->mclk_enabled && !sitar->mbhc_polling_active)
-		sitar_codec_enable_config_mode(codec, 1);
-
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2);
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x6, 0x0);
-
-	snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x00);
-
-	usleep_range(generic->t_shutdown_plug_rem,
-		     generic->t_shutdown_plug_rem);
-
-	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0xA, 0x8);
-	if (!sitar->mclk_enabled && !sitar->mbhc_polling_active)
-		sitar_codec_enable_config_mode(codec, 0);
-
-	snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x00);
-}
-
-static void sitar_codec_shutdown_hs_polling(struct snd_soc_codec *codec)
-{
-	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
-
-	sitar_codec_shutdown_hs_removal_detect(codec);
-
-	if (!sitar->mclk_enabled) {
-		snd_soc_update_bits(codec, SITAR_A_TX_COM_BIAS, 0xE0, 0x00);
-		sitar_codec_disable_clock_block(codec);
-		sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF);
-	}
-
-	sitar->mbhc_polling_active = false;
-}
-
 static irqreturn_t sitar_hphl_ocp_irq(int irq, void *data)
 {
 	struct sitar_priv *sitar = data;
@@ -3466,11 +4087,11 @@
 					  SITAR_IRQ_HPH_PA_OCPL_FAULT);
 			sitar->hphlocp_cnt = 0;
 			sitar->hph_status |= SND_JACK_OC_HPHL;
-			if (sitar->headset_jack)
+			if (sitar->mbhc_cfg.headset_jack)
 				sitar_snd_soc_jack_report(sitar,
-							  sitar->headset_jack,
-							  sitar->hph_status,
-							  SITAR_JACK_MASK);
+						sitar->mbhc_cfg.headset_jack,
+						sitar->hph_status,
+						SITAR_JACK_MASK);
 		}
 	} else {
 		pr_err("%s: Bad sitar private data\n", __func__);
@@ -3499,11 +4120,11 @@
 					 SITAR_IRQ_HPH_PA_OCPR_FAULT);
 			sitar->hphrocp_cnt = 0;
 			sitar->hph_status |= SND_JACK_OC_HPHR;
-			if (sitar->headset_jack)
+			if (sitar->mbhc_cfg.headset_jack)
 				sitar_snd_soc_jack_report(sitar,
-							 sitar->headset_jack,
-							 sitar->hph_status,
-							 SITAR_JACK_MASK);
+						sitar->mbhc_cfg.headset_jack,
+						sitar->hph_status,
+						SITAR_JACK_MASK);
 		}
 	} else {
 		pr_err("%s: Bad sitar private data\n", __func__);
@@ -3512,252 +4133,131 @@
 	return IRQ_HANDLED;
 }
 
-static void sitar_sync_hph_state(struct sitar_priv *sitar)
-{
-	if (test_and_clear_bit(SITAR_HPHR_PA_OFF_ACK,
-				&sitar->hph_pa_dac_state)) {
-		pr_err("%s: HPHR clear flag and enable PA\n", __func__);
-		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x10,
-				   1 << 4);
-	}
-	if (test_and_clear_bit(SITAR_HPHL_PA_OFF_ACK,
-				&sitar->hph_pa_dac_state)) {
-		pr_err("%s: HPHL clear flag and enable PA\n", __func__);
-		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x20,
-				   1 << 5);
-	}
-
-	if (test_and_clear_bit(SITAR_HPHR_DAC_OFF_ACK,
-				&sitar->hph_pa_dac_state)) {
-		pr_err("%s: HPHR clear flag and enable DAC\n", __func__);
-		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_R_DAC_CTL,
-				   0xC0, 0xC0);
-	}
-	if (test_and_clear_bit(SITAR_HPHL_DAC_OFF_ACK,
-				&sitar->hph_pa_dac_state)) {
-		pr_err("%s: HPHL clear flag and enable DAC\n", __func__);
-		snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_L_DAC_CTL,
-				   0xC0, 0xC0);
-	}
-}
-
 static irqreturn_t sitar_hs_insert_irq(int irq, void *data)
 {
 	struct sitar_priv *priv = data;
 	struct snd_soc_codec *codec = priv->codec;
-	const struct sitar_mbhc_plug_detect_cfg *plug_det =
-	    SITAR_MBHC_CAL_PLUG_DET_PTR(priv->calibration);
-	int ldo_h_on, micb_cfilt_on;
-	short mb_v;
-	u8 is_removal;
-	int mic_mv;
 
 	pr_debug("%s: enter\n", __func__);
+	SITAR_ACQUIRE_LOCK(priv->codec_resource_lock);
 	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION);
 
-	is_removal = snd_soc_read(codec, SITAR_A_CDC_MBHC_INT_CTL) & 0x02;
 	snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x03, 0x00);
 
 	/* Turn off both HPH and MIC line schmitt triggers */
 	snd_soc_update_bits(codec, priv->mbhc_bias_regs.mbhc_reg, 0x90, 0x00);
 	snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x13, 0x00);
+	snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00);
 
-	if (priv->mbhc_fake_ins_start &&
-	    time_after(jiffies, priv->mbhc_fake_ins_start +
-			msecs_to_jiffies(SITAR_FAKE_INS_THRESHOLD_MS))) {
-		pr_debug("%s: fake context interrupt, reset insertion\n",
-			 __func__);
-		priv->mbhc_fake_ins_start = 0;
-		sitar_codec_shutdown_hs_polling(codec);
-		sitar_codec_enable_hs_detect(codec, 1);
-		return IRQ_HANDLED;
-	}
+	pr_debug("%s: MIC trigger insertion interrupt\n", __func__);
 
-	ldo_h_on = snd_soc_read(codec, SITAR_A_LDO_H_MODE_1) & 0x80;
-	micb_cfilt_on = snd_soc_read(codec, priv->mbhc_bias_regs.cfilt_ctl)
-			    & 0x80;
+	rmb();
+	if (priv->lpi_enabled)
+		msleep(100);
 
-	if (!ldo_h_on)
-		snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x80, 0x80);
-	if (!micb_cfilt_on)
-		snd_soc_update_bits(codec, priv->mbhc_bias_regs.cfilt_ctl,
-				    0x80, 0x80);
-	if (plug_det->t_ins_complete > 20)
-		msleep(plug_det->t_ins_complete);
-	else
-		usleep_range(plug_det->t_ins_complete * 1000,
-			     plug_det->t_ins_complete * 1000);
-
-	if (!ldo_h_on)
-		snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x80, 0x0);
-	if (!micb_cfilt_on)
-		snd_soc_update_bits(codec, priv->mbhc_bias_regs.cfilt_ctl,
-							0x80, 0x0);
-
-	if (is_removal) {
-		/*
-		* If headphone is removed while playback is in progress,
-		* it is possible that micbias will be switched to VDDIO.
-		*/
-		if (priv->mbhc_micbias_switched)
-			sitar_codec_switch_micbias(codec, 0);
-		priv->hph_status &= ~SND_JACK_HEADPHONE;
-
-		/* If headphone PA is on, check if userspace receives
-		* removal event to sync-up PA's state */
-		if (sitar_is_hph_pa_on(codec)) {
-			set_bit(SITAR_HPHL_PA_OFF_ACK, &priv->hph_pa_dac_state);
-			set_bit(SITAR_HPHR_PA_OFF_ACK, &priv->hph_pa_dac_state);
-		}
-
-		if (sitar_is_hph_dac_on(codec, 1))
-			set_bit(SITAR_HPHL_DAC_OFF_ACK,
-				&priv->hph_pa_dac_state);
-		if (sitar_is_hph_dac_on(codec, 0))
-			set_bit(SITAR_HPHR_DAC_OFF_ACK,
-				&priv->hph_pa_dac_state);
-
-		if (priv->headset_jack) {
-			pr_err("%s: Reporting removal\n", __func__);
-			sitar_snd_soc_jack_report(priv, priv->headset_jack,
-						 priv->hph_status,
-						 SITAR_JACK_MASK);
-		}
-		sitar_codec_shutdown_hs_removal_detect(codec);
-		sitar_codec_enable_hs_detect(codec, 1);
-		return IRQ_HANDLED;
-	}
-
-	mb_v = sitar_codec_setup_hs_polling(codec);
-	mic_mv = sitar_codec_sta_dce_v(codec, 0, mb_v);
-
-	if (mb_v > (short) priv->mbhc_data.v_ins_hu) {
-		pr_debug("%s: Fake insertion interrupt since %dmsec ago, "
-			 "STA : %d,%d\n", __func__,
-			 (priv->mbhc_fake_ins_start ?
-			     jiffies_to_msecs(jiffies -
-					      priv->mbhc_fake_ins_start) :
-			     0),
-			 mb_v, mic_mv);
-		if (time_after(jiffies,
-			priv->mbhc_fake_ins_start +
-			msecs_to_jiffies(SITAR_FAKE_INS_THRESHOLD_MS))) {
-			/* Disable HPH trigger and enable MIC line trigger */
-			snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x12,
-					    0x00);
-			snd_soc_update_bits(codec,
-					    priv->mbhc_bias_regs.mbhc_reg, 0x60,
-					    plug_det->mic_current << 5);
-			snd_soc_update_bits(codec,
-					    priv->mbhc_bias_regs.mbhc_reg,
-					    0x80, 0x80);
-			usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid);
-			snd_soc_update_bits(codec,
-					    priv->mbhc_bias_regs.mbhc_reg,
-					    0x10, 0x10);
-		} else {
-			if (priv->mbhc_fake_ins_start == 0)
-				priv->mbhc_fake_ins_start = jiffies;
-			/* Setup normal insert detection
-			 * Enable HPH Schmitt Trigger
-			 */
-			snd_soc_update_bits(codec, SITAR_A_MBHC_HPH,
-					    0x13 | 0x0C,
-					    0x13 | plug_det->hph_current << 2);
-		}
-		/* Setup for insertion detection */
-		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x2, 0);
-		wcd9xxx_enable_irq(codec->control_data,
-					SITAR_IRQ_MBHC_INSERTION);
-		snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x1, 0x1);
-
-	} else if (mb_v < (short) priv->mbhc_data.v_no_mic) {
-		pr_debug("%s: Headphone Detected, mb_v: %d,%d\n",
-			 __func__, mb_v, mic_mv);
-		priv->mbhc_fake_ins_start = 0;
-		priv->hph_status |= SND_JACK_HEADPHONE;
-		if (priv->headset_jack) {
-			pr_debug("%s: Reporting insertion %d\n", __func__,
-				 SND_JACK_HEADPHONE);
-			sitar_snd_soc_jack_report(priv, priv->headset_jack,
-						  priv->hph_status,
-						  SITAR_JACK_MASK);
-		}
-		sitar_codec_shutdown_hs_polling(codec);
-		sitar_codec_enable_hs_detect(codec, 0);
-		sitar_sync_hph_state(priv);
+	rmb();
+	if (!priv->lpi_enabled) {
+		pr_debug("%s: lpi is disabled\n", __func__);
+	} else if (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) ==
+		   priv->mbhc_cfg.gpio_level_insert) {
+		pr_debug("%s: Valid insertion, "
+			 "detect plug type\n", __func__);
+		sitar_codec_decide_gpio_plug(codec);
 	} else {
-		pr_debug("%s: Headset detected, mb_v: %d,%d\n",
-			__func__, mb_v, mic_mv);
-		priv->mbhc_fake_ins_start = 0;
-		priv->hph_status |= SND_JACK_HEADSET;
-		if (priv->headset_jack) {
-			pr_debug("%s: Reporting insertion %d\n", __func__,
-				 SND_JACK_HEADSET);
-			sitar_snd_soc_jack_report(priv, priv->headset_jack,
-						  priv->hph_status,
-						  SITAR_JACK_MASK);
+		pr_debug("%s: Invalid insertion, "
+			 "stop plug detection\n", __func__);
+	}
+	SITAR_RELEASE_LOCK(priv->codec_resource_lock);
+	return IRQ_HANDLED;
+}
+
+static bool is_valid_mic_voltage(struct snd_soc_codec *codec, s32 mic_mv)
+{
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+	struct sitar_mbhc_plug_type_cfg *plug_type =
+		SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration);
+
+	return (!(mic_mv > SITAR_MBHC_FAKE_INSERT_LOW
+				&& mic_mv < SITAR_MBHC_FAKE_INSERT_HIGH)
+			&& (mic_mv > plug_type->v_no_mic)
+			&& (mic_mv < plug_type->v_hs_max)) ? true : false;
+}
+
+/* called under codec_resource_lock acquisition
+ * returns true if mic voltage range is back to normal insertion
+ * returns false either if timedout or removed */
+static bool sitar_hs_remove_settle(struct snd_soc_codec *codec)
+{
+	int i;
+	bool timedout, settled = false;
+	s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT];
+	short mb_v[MBHC_NUM_DCE_PLUG_DETECT];
+	unsigned long retry = 0, timeout;
+	struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
+
+	timeout = jiffies + msecs_to_jiffies(SITAR_HS_DETECT_PLUG_TIME_MS);
+	while (!(timedout = time_after(jiffies, timeout))) {
+		retry++;
+		if (sitar_hs_gpio_level_remove(sitar)) {
+			pr_debug("%s: GPIO indicates removal\n", __func__);
+			break;
 		}
-		/* avoid false button press detect */
-		msleep(50);
-		sitar_codec_start_hs_polling(codec);
-		sitar_sync_hph_state(priv);
+
+		if (retry > 1)
+			msleep(250);
+		else
+			msleep(50);
+
+		if (sitar_hs_gpio_level_remove(sitar)) {
+			pr_debug("%s: GPIO indicates removal\n", __func__);
+			break;
+		}
+
+		sitar_turn_onoff_override(codec, true);
+		for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) {
+			mb_v[i] = __sitar_codec_sta_dce(codec, 1,  true, true);
+			mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]);
+			pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n",
+				 __func__, retry, mic_mv[i], mb_v[i]);
+		}
+		sitar_turn_onoff_override(codec, false);
+
+		if (sitar_hs_gpio_level_remove(sitar)) {
+			pr_debug("%s: GPIO indicates removal\n", __func__);
+			break;
+		}
+
+		for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++)
+			if (!is_valid_mic_voltage(codec, mic_mv[i]))
+				break;
+
+		if (i == MBHC_NUM_DCE_PLUG_DETECT) {
+			pr_debug("%s: MIC voltage settled\n", __func__);
+			settled = true;
+			msleep(200);
+			break;
+		}
 	}
 
-	return IRQ_HANDLED;
+	if (timedout)
+		pr_debug("%s: Microphone did not settle in %d seconds\n",
+			 __func__, SITAR_HS_DETECT_PLUG_TIME_MS);
+	return settled;
 }
 
 static irqreturn_t sitar_hs_remove_irq(int irq, void *data)
 {
-	short bias_value;
 	struct sitar_priv *priv = data;
 	struct snd_soc_codec *codec = priv->codec;
-	const struct sitar_mbhc_general_cfg *generic =
-	    SITAR_MBHC_CAL_GENERAL_PTR(priv->calibration);
-	int fake_removal = 0;
-	int min_us = SITAR_FAKE_REMOVAL_MIN_PERIOD_MS * 1000;
 
 	pr_debug("%s: enter, removal interrupt\n", __func__);
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL);
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE);
 
-	usleep_range(generic->t_shutdown_plug_rem,
-		     generic->t_shutdown_plug_rem);
-
-	do {
-		bias_value = sitar_codec_sta_dce(codec, 1);
-		pr_debug("%s: DCE %d,%d, %d us left\n", __func__, bias_value,
-			 sitar_codec_sta_dce_v(codec, 1, bias_value), min_us);
-		if (bias_value < (short)priv->mbhc_data.v_ins_h) {
-			fake_removal = 1;
-			break;
-		}
-		min_us -= priv->mbhc_data.t_dce;
-	} while (min_us > 0);
-
-	if (fake_removal) {
-		pr_debug("False alarm, headset not actually removed\n");
+	SITAR_ACQUIRE_LOCK(priv->codec_resource_lock);
+	if (sitar_hs_remove_settle(codec))
 		sitar_codec_start_hs_polling(codec);
-	} else {
-		/*
-		 * If this removal is not false, first check the micbias
-		 * switch status and switch it to LDOH if it is already
-		 * switched to VDDIO.
-		 */
-		if (priv->mbhc_micbias_switched)
-			sitar_codec_switch_micbias(codec, 0);
-		priv->hph_status &= ~SND_JACK_HEADSET;
-		if (priv->headset_jack) {
-			pr_err("%s: Reporting removal\n", __func__);
-			sitar_snd_soc_jack_report(priv, priv->headset_jack, 0,
-						 SITAR_JACK_MASK);
-		}
-		sitar_codec_shutdown_hs_polling(codec);
+	pr_debug("%s: remove settle done\n", __func__);
 
-		sitar_codec_enable_hs_detect(codec, 1);
-	}
-
+	SITAR_RELEASE_LOCK(priv->codec_resource_lock);
 	return IRQ_HANDLED;
 }
 
@@ -4015,7 +4515,10 @@
 	sitar->config_mode_active = false;
 	sitar->mbhc_polling_active = false;
 	sitar->no_mic_headset_override = false;
+	mutex_init(&sitar->codec_resource_lock);
 	sitar->codec = codec;
+	sitar->mbhc_state = MBHC_STATE_NONE;
+	sitar->mbhc_last_resume = 0;
 	sitar->pdata = dev_get_platdata(codec->dev->parent);
 	sitar_update_reg_defaults(codec);
 	sitar_codec_init_reg(codec);
@@ -4057,7 +4560,6 @@
 			SITAR_IRQ_MBHC_REMOVAL);
 		goto err_remove_irq;
 	}
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL);
 
 	ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL,
 		sitar_dce_handler, "DC Estimation detect", sitar);
@@ -4066,7 +4568,6 @@
 			SITAR_IRQ_MBHC_POTENTIAL);
 		goto err_potential_irq;
 	}
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL);
 
 	ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE,
 		sitar_release_handler, "Button Release detect", sitar);
@@ -4075,7 +4576,6 @@
 			SITAR_IRQ_MBHC_RELEASE);
 		goto err_release_irq;
 	}
-	wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE);
 
 	ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_SLIMBUS,
 		sitar_slimbus_irq, "SLIMBUS Slave", sitar);
@@ -4151,6 +4651,7 @@
 			SITAR_IRQ_MBHC_INSERTION, sitar);
 err_insert_irq:
 err_pdata:
+	mutex_destroy(&sitar->codec_resource_lock);
 	kfree(sitar);
 	return ret;
 }
@@ -4163,12 +4664,15 @@
 	wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL, sitar);
 	wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL, sitar);
 	wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION, sitar);
+	SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock);
 	sitar_codec_disable_clock_block(codec);
+	SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
 	sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF);
 	if (sitar->mbhc_fw)
 		release_firmware(sitar->mbhc_fw);
 	for (i = 0; i < ARRAY_SIZE(sitar_dai); i++)
 		kfree(sitar->dai[i].ch_num);
+	mutex_destroy(&sitar->codec_resource_lock);
 	kfree(sitar);
 	return 0;
 }
@@ -4232,7 +4736,10 @@
 
 static int sitar_resume(struct device *dev)
 {
+	struct platform_device *pdev = to_platform_device(dev);
+	struct sitar_priv *sitar = platform_get_drvdata(pdev);
 	dev_dbg(dev, "%s: system resume\n", __func__);
+	sitar->mbhc_last_resume = jiffies;
 	return 0;
 }
 
diff --git a/sound/soc/codecs/wcd9304.h b/sound/soc/codecs/wcd9304.h
index cfe839b..70b3f0b 100644
--- a/sound/soc/codecs/wcd9304.h
+++ b/sound/soc/codecs/wcd9304.h
@@ -156,12 +156,29 @@
 	u16 _beta[3];
 } __packed;
 
+struct sitar_mbhc_config {
+	struct snd_soc_jack *headset_jack;
+	struct snd_soc_jack *button_jack;
+	bool read_fw_bin;
+	/* void* calibration contains:
+	 *  struct tabla_mbhc_general_cfg generic;
+	 *  struct tabla_mbhc_plug_detect_cfg plug_det;
+	 *  struct tabla_mbhc_plug_type_cfg plug_type;
+	 *  struct tabla_mbhc_btn_detect_cfg btn_det;
+	 *  struct tabla_mbhc_imped_detect_cfg imped_det;
+	 * Note: various size depends on btn_det->num_btn
+	 */
+	void *calibration;
+	enum sitar_micbias_num micbias;
+	int (*mclk_cb_fn) (struct snd_soc_codec*, int, bool);
+	unsigned int mclk_rate;
+	unsigned int gpio;
+	unsigned int gpio_irq;
+	int gpio_level_insert;
+};
+
 extern int sitar_hs_detect(struct snd_soc_codec *codec,
-			   struct snd_soc_jack *headset_jack,
-			   struct snd_soc_jack *button_jack,
-			   void *calibration, enum sitar_micbias_num micbis,
-			   int (*mclk_cb_fn) (struct snd_soc_codec*, int),
-			   int read_fw_bin, u32 mclk_rate);
+			const struct sitar_mbhc_config *cfg);
 
 #ifndef anc_header_dec
 struct anc_header {
@@ -171,7 +188,8 @@
 #define anc_header_dec
 #endif
 
-extern int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable);
+extern int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable,
+							 bool dapm);
 
 extern void *sitar_mbhc_cal_btn_det_mp(const struct sitar_mbhc_btn_detect_cfg
 				       *btn_det,
diff --git a/sound/soc/msm/msm8930.c b/sound/soc/msm/msm8930.c
index 04b0fb0..a2c6f5f 100644
--- a/sound/soc/msm/msm8930.c
+++ b/sound/soc/msm/msm8930.c
@@ -55,7 +55,24 @@
 
 static struct snd_soc_jack hs_jack;
 static struct snd_soc_jack button_jack;
-static void *sitar_mbhc_cal;
+
+static int msm8930_enable_codec_ext_clk(
+		struct snd_soc_codec *codec, int enable,
+		bool dapm);
+
+static struct sitar_mbhc_config mbhc_cfg = {
+	.headset_jack = &hs_jack,
+	.button_jack = &button_jack,
+	.read_fw_bin = false,
+	.calibration = NULL,
+	.micbias = SITAR_MICBIAS2,
+	.mclk_cb_fn = msm8930_enable_codec_ext_clk,
+	.mclk_rate = SITAR_EXT_CLK_RATE,
+	.gpio = 0,
+	.gpio_irq = 0,
+	.gpio_level_insert = 1,
+};
+
 
 static void msm8930_ext_control(struct snd_soc_codec *codec)
 {
@@ -102,8 +119,9 @@
 	return 0;
 }
 
-int msm8930_enable_codec_ext_clk(
-		struct snd_soc_codec *codec, int enable)
+static int msm8930_enable_codec_ext_clk(
+		struct snd_soc_codec *codec, int enable,
+		bool dapm)
 {
 	pr_debug("%s: enable = %d\n", __func__, enable);
 	if (enable) {
@@ -115,7 +133,7 @@
 		if (codec_clk) {
 			clk_set_rate(codec_clk, SITAR_EXT_CLK_RATE);
 			clk_prepare_enable(codec_clk);
-			sitar_mclk_enable(codec, 1);
+			sitar_mclk_enable(codec, 1, dapm);
 		} else {
 			pr_err("%s: Error setting Sitar MCLK\n", __func__);
 			clk_users--;
@@ -129,7 +147,7 @@
 		if (!clk_users) {
 			pr_debug("%s: disabling MCLK. clk_users = %d\n",
 					 __func__, clk_users);
-			sitar_mclk_enable(codec, 0);
+			sitar_mclk_enable(codec, 0, dapm);
 			clk_disable_unprepare(codec_clk);
 		}
 	}
@@ -143,9 +161,9 @@
 
 	switch (event) {
 	case SND_SOC_DAPM_PRE_PMU:
-		return msm8930_enable_codec_ext_clk(w->codec, 1);
+		return msm8930_enable_codec_ext_clk(w->codec, 1, true);
 	case SND_SOC_DAPM_POST_PMD:
-		return msm8930_enable_codec_ext_clk(w->codec, 0);
+		return msm8930_enable_codec_ext_clk(w->codec, 0, true);
 	}
 	return 0;
 }
@@ -520,6 +538,10 @@
 	}
 	codec_clk = clk_get(cpu_dai->dev, "osr_clk");
 
+	mbhc_cfg.gpio = 37;
+	mbhc_cfg.gpio_irq = gpio_to_irq(mbhc_cfg.gpio);
+	sitar_hs_detect(codec, &mbhc_cfg);
+
 	return 0;
 }
 
@@ -978,8 +1000,8 @@
 		pr_err("%s: Not the right machine type\n", __func__);
 		return -ENODEV ;
 	}
-	sitar_mbhc_cal = def_sitar_mbhc_cal();
-	if (!sitar_mbhc_cal) {
+	mbhc_cfg.calibration = def_sitar_mbhc_cal();
+	if (!mbhc_cfg.calibration) {
 		pr_err("Calibration data allocation failed\n");
 		return -ENOMEM;
 	}
@@ -987,7 +1009,7 @@
 	msm8930_snd_device = platform_device_alloc("soc-audio", 0);
 	if (!msm8930_snd_device) {
 		pr_err("Platform device allocation failed\n");
-		kfree(sitar_mbhc_cal);
+		kfree(mbhc_cfg.calibration);
 		return -ENOMEM;
 	}
 
@@ -995,7 +1017,7 @@
 	ret = platform_device_add(msm8930_snd_device);
 	if (ret) {
 		platform_device_put(msm8930_snd_device);
-		kfree(sitar_mbhc_cal);
+		kfree(mbhc_cfg.calibration);
 		return ret;
 	}
 
@@ -1018,7 +1040,7 @@
 	}
 	msm8930_free_headset_mic_gpios();
 	platform_device_unregister(msm8930_snd_device);
-	kfree(sitar_mbhc_cal);
+	kfree(mbhc_cfg.calibration);
 }
 module_exit(msm8930_audio_exit);