ASoC: wcd9310: Add high impedance headphone detection support

If HPHL and HPHR have relatively high impedance, internal MBHC HPHL
trigger cannot trigger so results in headphone/headset detection
failure.
Utilize MIC trigger as well as HPHL trigger in order to detect headphone
and headset which have high impedance on headphone line.

CRs-fixed: 339390, 337590, 336847, 334339
Change-Id: Ibfd4ba254197233f2b5e253f5d1fa2a98c848c97
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/drivers/mfd/wcd9xxx-irq.c b/drivers/mfd/wcd9xxx-irq.c
index 86c01ee..c1febcf 100644
--- a/drivers/mfd/wcd9xxx-irq.c
+++ b/drivers/mfd/wcd9xxx-irq.c
@@ -141,12 +141,30 @@
 }
 EXPORT_SYMBOL_GPL(wcd9xxx_unlock_sleep);
 
+static void wcd9xxx_irq_dispatch(struct wcd9xxx *wcd9xxx, int irqbit)
+{
+	if ((irqbit <= TABLA_IRQ_MBHC_INSERTION) &&
+	    (irqbit >= TABLA_IRQ_MBHC_REMOVAL)) {
+		wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 +
+				  BIT_BYTE(irqbit), BYTE_BIT_MASK(irqbit));
+		if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
+			wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MODE, 0x02);
+		handle_nested_irq(wcd9xxx->irq_base + irqbit);
+	} else {
+		handle_nested_irq(wcd9xxx->irq_base + irqbit);
+		wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 +
+				  BIT_BYTE(irqbit), BYTE_BIT_MASK(irqbit));
+		if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
+			wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MODE, 0x02);
+	}
+}
+
 static irqreturn_t wcd9xxx_irq_thread(int irq, void *data)
 {
 	int ret;
 	struct wcd9xxx *wcd9xxx = data;
 	u8 status[WCD9XXX_NUM_IRQ_REGS];
-	unsigned int i;
+	int i;
 
 	wcd9xxx_lock_sleep(wcd9xxx);
 	ret = wcd9xxx_bulk_read(wcd9xxx, TABLA_A_INTR_STATUS0,
@@ -164,28 +182,22 @@
 	/* Find out which interrupt was triggered and call that interrupt's
 	 * handler function
 	 */
-	for (i = 0; i < TABLA_NUM_IRQS; i++) {
-		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i)) {
-			if ((i <= TABLA_IRQ_MBHC_INSERTION) &&
-				(i >= TABLA_IRQ_MBHC_REMOVAL)) {
-				wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 +
-					BIT_BYTE(i), BYTE_BIT_MASK(i));
-				if (wcd9xxx_get_intf_type() ==
-					WCD9XXX_INTERFACE_TYPE_I2C)
-					wcd9xxx_reg_write(wcd9xxx,
-						TABLA_A_INTR_MODE, 0x02);
-				handle_nested_irq(wcd9xxx->irq_base + i);
-			} else {
-				handle_nested_irq(wcd9xxx->irq_base + i);
-				wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 +
-					BIT_BYTE(i), BYTE_BIT_MASK(i));
-				if (wcd9xxx_get_intf_type() ==
-					WCD9XXX_INTERFACE_TYPE_I2C)
-					wcd9xxx_reg_write(wcd9xxx,
-						TABLA_A_INTR_MODE, 0x02);
-			}
-			break;
-		}
+	if (status[BIT_BYTE(TABLA_IRQ_SLIMBUS)] &
+	    BYTE_BIT_MASK(TABLA_IRQ_SLIMBUS))
+		wcd9xxx_irq_dispatch(wcd9xxx, TABLA_IRQ_SLIMBUS);
+
+	/* Since codec has only one hardware irq line which is shared by
+	 * codec's different internal interrupts, so it's possible master irq
+	 * handler dispatches multiple nested irq handlers after breaking
+	 * order.  Dispatch MBHC interrupts order to follow MBHC state
+	 * machine's order */
+	for (i = TABLA_IRQ_MBHC_INSERTION; i >= TABLA_IRQ_MBHC_REMOVAL; i--) {
+		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i))
+			wcd9xxx_irq_dispatch(wcd9xxx, i);
+	}
+	for (i = TABLA_IRQ_BG_PRECHARGE; i < TABLA_NUM_IRQS; i++) {
+		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i))
+			wcd9xxx_irq_dispatch(wcd9xxx, i);
 	}
 	wcd9xxx_unlock_sleep(wcd9xxx);
 
diff --git a/include/linux/mfd/wcd9xxx/core.h b/include/linux/mfd/wcd9xxx/core.h
index 0d99e37..1a04773 100644
--- a/include/linux/mfd/wcd9xxx/core.h
+++ b/include/linux/mfd/wcd9xxx/core.h
@@ -173,5 +173,11 @@
 		return;
 	disable_irq_nosync(wcd9xxx->irq_base + irq);
 }
+static inline void wcd9xxx_disable_irq_sync(struct wcd9xxx *wcd9xxx, int irq)
+{
+	if (!wcd9xxx->irq_base)
+		return;
+	disable_irq(wcd9xxx->irq_base + irq);
+}
 
 #endif
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index 828aaca..851722a 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -43,6 +43,13 @@
 #define MBHC_FW_READ_ATTEMPTS 15
 #define MBHC_FW_READ_TIMEOUT 2000000
 
+enum {
+	MBHC_USE_HPHL_TRIGGER = 1,
+	MBHC_USE_MB_TRIGGER = 2
+};
+
+#define MBHC_NUM_DCE_PLUG_DETECT 3
+
 #define TABLA_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR)
 
 #define TABLA_I2S_MASTER_MODE_MASK 0x08
@@ -68,6 +75,16 @@
 #define TABLA_FAKE_INS_THRESHOLD_MS 2500
 #define TABLA_FAKE_REMOVAL_MIN_PERIOD_MS 50
 
+#define TABLA_MBHC_BUTTON_MIN 0x8000
+
+#define PLUG_TYPE_HEADPHONE (1 << 0)
+#define PLUG_TYPE_HEADSET (1 << 1)
+
+#define TABLA_MBHC_FAKE_INSERT_LOW 10
+#define TABLA_MBHC_FAKE_INSERT_HIGH 150
+
+#define TABLA_MBHC_STATUS_REL_DETECTION 0x0C
+
 static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0);
 static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1);
 static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1);
@@ -180,6 +197,7 @@
 	bool mbhc_polling_active;
 	unsigned long mbhc_fake_ins_start;
 	int buttons_pressed;
+	int mbhc_state;
 
 	enum tabla_micbias_num micbias;
 	/* void* calibration contains:
@@ -201,7 +219,7 @@
 
 	bool no_mic_headset_override;
 	/* Delayed work to report long button press */
-	struct delayed_work btn0_dwork;
+	struct delayed_work mbhc_btn_dwork;
 
 	struct mbhc_micbias_regs mbhc_bias_regs;
 	u8 cfilt_k_value;
@@ -242,6 +260,10 @@
 	int aux_pga_cnt;
 	u8 aux_l_gain;
 	u8 aux_r_gain;
+
+	struct mutex mbhc_mutex;
+	struct delayed_work mbhc_insert_dwork;
+	unsigned long mbhc_last_resume; /* in jiffies */
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1398,10 +1420,12 @@
 		tabla->adc_count--;
 		if (!tabla->adc_count) {
 			snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL,
-				0x2, 0x0);
+					    0x2, 0x0);
+			mutex_lock(&tabla->mbhc_mutex);
 			if (!tabla->mbhc_polling_active)
 				snd_soc_update_bits(codec, TABLA_A_TX_COM_BIAS,
-					0xE0, 0x0);
+						    0xE0, 0x0);
+			mutex_unlock(&tabla->mbhc_mutex);
 		}
 	}
 }
@@ -1458,7 +1482,6 @@
 
 static void tabla_codec_enable_audio_mode_bandgap(struct snd_soc_codec *codec)
 {
-	snd_soc_write(codec, TABLA_A_BIAS_REF_CTL, 0x1C);
 	snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80,
 		0x80);
 	snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x04,
@@ -1925,10 +1948,14 @@
 	snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL, 0x00);
 }
 
+/* called after acquiring mbhc_mutex */
 static void tabla_codec_start_hs_polling(struct snd_soc_codec *codec)
 {
 	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+	struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent);
+	int mbhc_state = tabla->mbhc_state;
 
+	pr_debug("%s: enter\n", __func__);
 	snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84);
 	wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
 	if (!tabla->no_mic_headset_override) {
@@ -1939,15 +1966,41 @@
 	} else {
 		tabla_codec_disable_button_presses(codec);
 	}
+
+	if (!tabla->no_mic_headset_override) {
+		if (mbhc_state == TABLA_IRQ_MBHC_POTENTIAL) {
+			pr_debug("%s:%d recovering MBHC state macine\n",
+				 __func__, __LINE__);
+			/* set to max button press threshold */
+			snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL,
+				      0x7F);
+			snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL,
+				      0xFF);
+			snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B4_CTL,
+				      (TABLA_IS_1_X(tabla_core->version) ?
+				       0x07 : 0x7F));
+			snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL,
+				      0xFF);
+			/* set to max */
+			snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B6_CTL,
+				      0x7F);
+			snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B5_CTL,
+				      0xFF);
+		}
+	}
+
 	snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1);
 	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0);
 	snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1);
+	pr_debug("%s: leave\n", __func__);
 }
 
+/* called after acquiring mbhc_mutex */
 static void tabla_codec_pause_hs_polling(struct snd_soc_codec *codec)
 {
 	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
 
+	pr_debug("%s: enter\n", __func__);
 	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8);
 	wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
 	if (!tabla->no_mic_headset_override) {
@@ -1956,10 +2009,10 @@
 		wcd9xxx_disable_irq(codec->control_data,
 			TABLA_IRQ_MBHC_RELEASE);
 	}
+	pr_debug("%s: leave\n", __func__);
 }
 
-static void tabla_codec_switch_cfilt_mode(struct snd_soc_codec *codec,
-		int mode)
+static void tabla_codec_switch_cfilt_mode(struct snd_soc_codec *codec, int mode)
 {
 	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
 	u8 reg_mode_val, cur_mode_val;
@@ -1974,6 +2027,7 @@
 					tabla->mbhc_bias_regs.cfilt_ctl) & 0x40;
 
 	if (cur_mode_val != reg_mode_val) {
+		mutex_lock(&tabla->mbhc_mutex);
 		if (tabla->mbhc_polling_active) {
 			tabla_codec_pause_hs_polling(codec);
 			mbhc_was_polling = true;
@@ -1982,6 +2036,7 @@
 			tabla->mbhc_bias_regs.cfilt_ctl, 0x40, reg_mode_val);
 		if (mbhc_was_polling)
 			tabla_codec_start_hs_polling(codec);
+		mutex_unlock(&tabla->mbhc_mutex);
 		pr_debug("%s: CFILT mode change (%x to %x)\n", __func__,
 			cur_mode_val, reg_mode_val);
 	} else {
@@ -2102,12 +2157,13 @@
 {
 	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
 	int cfilt_k_val;
-	bool mbhc_was_polling =  false;
+	bool mbhc_was_polling = false;
 
+	mutex_lock(&tabla->mbhc_mutex);
 	switch (vddio_switch) {
 	case 1:
-		if (tabla->mbhc_polling_active) {
-
+		if (tabla->mbhc_micbias_switched == 0 &&
+		    tabla->mbhc_polling_active) {
 			tabla_codec_pause_hs_polling(codec);
 			/* VDDIO switch enabled */
 			tabla->cfilt_k_value = snd_soc_read(codec,
@@ -2153,6 +2209,7 @@
 		}
 		break;
 	}
+	mutex_unlock(&tabla->mbhc_mutex);
 }
 
 static int tabla_codec_enable_micbias(struct snd_soc_dapm_widget *w,
@@ -2198,8 +2255,7 @@
 	switch (event) {
 	case SND_SOC_DAPM_PRE_PMU:
 		/* Decide whether to switch the micbias for MBHC */
-		if ((w->reg == tabla->mbhc_bias_regs.ctl_reg)
-				&& tabla->mbhc_micbias_switched)
+		if (w->reg == tabla->mbhc_bias_regs.ctl_reg)
 			tabla_codec_switch_micbias(codec, 0);
 
 		snd_soc_update_bits(codec, w->reg, 0x0E, 0x0A);
@@ -2216,15 +2272,17 @@
 	case SND_SOC_DAPM_POST_PMU:
 		if (tabla->mbhc_polling_active &&
 		    tabla->micbias == micb_line) {
+			mutex_lock(&tabla->mbhc_mutex);
 			tabla_codec_pause_hs_polling(codec);
 			tabla_codec_start_hs_polling(codec);
+			mutex_unlock(&tabla->mbhc_mutex);
 		}
 		break;
 
 	case SND_SOC_DAPM_POST_PMD:
 
-		if ((w->reg == tabla->mbhc_bias_regs.ctl_reg)
-				&& tabla_is_hph_pa_on(codec))
+		if ((w->reg == tabla->mbhc_bias_regs.ctl_reg) &&
+		    tabla_is_hph_pa_on(codec))
 			tabla_codec_switch_micbias(codec, 1);
 
 		if (strnstr(w->name, internal1_text, 30))
@@ -2338,17 +2396,21 @@
 				      int mask)
 {
 	/* XXX: wake_lock_timeout()? */
-	snd_soc_jack_report(jack, status, mask);
+	snd_soc_jack_report_no_dapm(jack, status, mask);
 }
 
 static void hphocp_off_report(struct tabla_priv *tabla,
 	u32 jack_status, int irq)
 {
 	struct snd_soc_codec *codec;
+	if (!tabla) {
+		pr_err("%s: Bad tabla private data\n", __func__);
+		return;
+	}
 
-	if (tabla) {
-		pr_info("%s: clear ocp status %x\n", __func__, jack_status);
-		codec = tabla->codec;
+	pr_info("%s: clear ocp status %x\n", __func__, jack_status);
+	codec = tabla->codec;
+	if (tabla->hph_status & jack_status) {
 		tabla->hph_status &= ~jack_status;
 		if (tabla->headset_jack)
 			tabla_snd_soc_jack_report(tabla, tabla->headset_jack,
@@ -2364,8 +2426,6 @@
 		else
 			tabla->hphrocp_cnt = 0;
 		wcd9xxx_enable_irq(codec->control_data, irq);
-	} else {
-		pr_err("%s: Bad tabla private data\n", __func__);
 	}
 }
 
@@ -2396,8 +2456,7 @@
 		mbhc_micb_ctl_val = snd_soc_read(codec,
 				tabla->mbhc_bias_regs.ctl_reg);
 
-		if (!(mbhc_micb_ctl_val & 0x80)
-				&& !tabla->mbhc_micbias_switched)
+		if (!(mbhc_micb_ctl_val & 0x80))
 			tabla_codec_switch_micbias(codec, 1);
 
 		break;
@@ -2424,8 +2483,7 @@
 				schedule_work(&tabla->hphrocp_work);
 		}
 
-		if (tabla->mbhc_micbias_switched)
-			tabla_codec_switch_micbias(codec, 0);
+		tabla_codec_switch_micbias(codec, 0);
 
 		pr_debug("%s: sleep 10 ms after %s PA disable.\n", __func__,
 				w->name);
@@ -2602,7 +2660,6 @@
 	{"SLIM TX10 MUX", "DEC9", "DEC9 MUX"},
 	{"SLIM TX10 MUX", "DEC10", "DEC10 MUX"},
 
-
 	/* Earpiece (RX MIX1) */
 	{"EAR", NULL, "EAR PA"},
 	{"EAR PA", NULL, "EAR_PA_MIXER"},
@@ -4009,6 +4066,7 @@
 		return -ENODEV;
 	}
 
+	mutex_lock(&tabla->mbhc_mutex);
 	tabla->mbhc_polling_active = true;
 
 	if (!tabla->mclk_enabled) {
@@ -4016,6 +4074,7 @@
 		tabla_enable_rx_bias(codec, 1);
 		tabla_codec_enable_clock_block(codec, 1);
 	}
+	mutex_unlock(&tabla->mbhc_mutex);
 
 	snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x01);
 
@@ -4043,7 +4102,9 @@
 
 	tabla_codec_calibrate_hs_polling(codec);
 
-	bias_value = tabla_codec_sta_dce(codec, 0);
+	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x00);
+	bias_value = tabla_codec_sta_dce(codec, 1);
+	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02);
 	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40,
 			    cfilt_mode);
 	snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00);
@@ -4051,8 +4112,30 @@
 	return bias_value;
 }
 
+/* called after acquiring codec mutex */
+static void tabla_set_pa_dac_state(struct snd_soc_codec *codec)
+{
+	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+
+	/* If headphone PA is on, check if userspace receives
+	 * removal event to sync-up PA's state */
+	if (tabla_is_hph_pa_on(codec)) {
+		pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__);
+		set_bit(TABLA_HPHL_PA_OFF_ACK, &tabla->hph_pa_dac_state);
+		set_bit(TABLA_HPHR_PA_OFF_ACK, &tabla->hph_pa_dac_state);
+	} else {
+		pr_debug("%s PA is off\n", __func__);
+	}
+
+	if (tabla_is_hph_dac_on(codec, 1))
+		set_bit(TABLA_HPHL_DAC_OFF_ACK, &tabla->hph_pa_dac_state);
+	if (tabla_is_hph_dac_on(codec, 0))
+		set_bit(TABLA_HPHR_DAC_OFF_ACK, &tabla->hph_pa_dac_state);
+}
+
 static int tabla_codec_enable_hs_detect(struct snd_soc_codec *codec,
-		int insertion)
+					int insertion, int trigger,
+					bool padac_off)
 {
 	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
 	int central_bias_enabled = 0;
@@ -4069,42 +4152,68 @@
 
 	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0);
 
+	/* Make sure mic bias and Mic line schmitt trigger
+	 * are turned OFF
+	 */
+	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x01);
+	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x90, 0x00);
+
 	if (insertion) {
-		/* Make sure mic bias and Mic line schmitt trigger
-		 * are turned OFF
-		 */
-		snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg,
-			0x81, 0x01);
-		snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg,
-			0x90, 0x00);
+
 		wg_time = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_WG_TIME) ;
 		wg_time += 1;
+		tabla_codec_switch_micbias(codec, 0);
 
-		/* Enable HPH Schmitt Trigger */
-		snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x11, 0x11);
-		snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x0C,
-				    plug_det->hph_current << 2);
+		/* DAPM can manipulate PA/DAC bits concurrently */
+		if (padac_off == true) {
+			mutex_lock(&codec->mutex);
+			tabla_set_pa_dac_state(codec);
+			snd_soc_update_bits(codec, TABLA_A_RX_HPH_CNP_EN, 0x30,
+					    0x00);
+			snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_DAC_CTL,
+					    0xC0, 0x00);
+			snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_DAC_CTL,
+					    0xC0, 0x00);
+			usleep_range(wg_time * 1000, wg_time * 1000);
+			mutex_unlock(&codec->mutex);
+		}
 
-		/* Turn off HPH PAs and DAC's during insertion detection to
-		 * avoid false insertion interrupts
-		 */
-		if (tabla->mbhc_micbias_switched)
-			tabla_codec_switch_micbias(codec, 0);
-		snd_soc_update_bits(codec, TABLA_A_RX_HPH_CNP_EN, 0x30, 0x00);
-		snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_DAC_CTL,
-				    0xC0, 0x00);
-		snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_DAC_CTL,
-				    0xC0, 0x00);
-		usleep_range(wg_time * 1000, wg_time * 1000);
+		if (trigger == MBHC_USE_HPHL_TRIGGER) {
+			/* Enable HPH Schmitt Trigger */
+			snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x11,
+					    0x11);
+			snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x0C,
+					    plug_det->hph_current << 2);
+			snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x02,
+					    0x02);
+		} else if (trigger == MBHC_USE_MB_TRIGGER) {
+			/* enable the mic line schmitt trigger */
+			snd_soc_update_bits(codec,
+					    tabla->mbhc_bias_regs.mbhc_reg,
+					    0x60, plug_det->mic_current << 5);
+			snd_soc_update_bits(codec,
+					    tabla->mbhc_bias_regs.mbhc_reg,
+					    0x80, 0x80);
+			usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid);
+			snd_soc_update_bits(codec,
+					    tabla->mbhc_bias_regs.ctl_reg, 0x01,
+					    0x00);
+			snd_soc_update_bits(codec,
+					    tabla->mbhc_bias_regs.mbhc_reg,
+					    0x10, 0x10);
+		}
 
 		/* setup for insetion detection */
-		snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x02, 0x02);
 		snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0);
+
 	} else {
+		pr_debug("setup for removal detection\n");
 		/* Make sure the HPH schmitt trigger is OFF */
 		snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x12, 0x00);
 
 		/* enable the mic line schmitt trigger */
+		snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg,
+				    0x01, 0x00);
 		snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x60,
 				    plug_det->mic_current << 5);
 		snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg,
@@ -4118,6 +4227,7 @@
 	}
 
 	if (snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x4) {
+		mutex_lock(&tabla->mbhc_mutex);
 		if (!(tabla->clock_active)) {
 			tabla_codec_enable_config_mode(codec, 1);
 			snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL,
@@ -4128,6 +4238,7 @@
 		} else
 			snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL,
 				0x06, 0);
+		mutex_unlock(&tabla->mbhc_mutex);
 	}
 
 	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.int_rbias, 0x80, 0);
@@ -4162,9 +4273,10 @@
 static u16 tabla_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce,
 				 s16 vin_mv)
 {
-	short diff, zero;
 	struct tabla_priv *tabla;
+	s16 diff, zero;
 	u32 mb_mv, in;
+	u16 value;
 
 	tabla = snd_soc_codec_get_drvdata(codec);
 	mb_mv = tabla->mbhc_data.micb_mv;
@@ -4175,50 +4287,52 @@
 	}
 
 	if (dce) {
-		diff = tabla->mbhc_data.dce_mb - tabla->mbhc_data.dce_z;
-		zero = tabla->mbhc_data.dce_z;
+		diff = (tabla->mbhc_data.dce_mb) - (tabla->mbhc_data.dce_z);
+		zero = (tabla->mbhc_data.dce_z);
 	} else {
-		diff = tabla->mbhc_data.sta_mb - tabla->mbhc_data.sta_z;
-		zero = tabla->mbhc_data.sta_z;
+		diff = (tabla->mbhc_data.sta_mb) - (tabla->mbhc_data.sta_z);
+		zero = (tabla->mbhc_data.sta_z);
 	}
 	in = (u32) diff * vin_mv;
 
-	return (u16) (in / mb_mv) + zero;
+	value = (u16) (in / mb_mv) + zero;
+	return value;
 }
 
 static s32 tabla_codec_sta_dce_v(struct snd_soc_codec *codec, s8 dce,
 				 u16 bias_value)
 {
 	struct tabla_priv *tabla;
+	s16 value, z, mb;
 	s32 mv;
 
 	tabla = snd_soc_codec_get_drvdata(codec);
-
+	value = bias_value;
 	if (dce) {
-		mv = ((s32)bias_value - (s32)tabla->mbhc_data.dce_z) *
-		     (s32)tabla->mbhc_data.micb_mv /
-		     (s32)(tabla->mbhc_data.dce_mb - tabla->mbhc_data.dce_z);
+		z = (tabla->mbhc_data.dce_z);
+		mb = (tabla->mbhc_data.dce_mb);
+		mv = (value - z) * (s32)tabla->mbhc_data.micb_mv / (mb - z);
 	} else {
-		mv = ((s32)bias_value - (s32)tabla->mbhc_data.sta_z) *
-		     (s32)tabla->mbhc_data.micb_mv /
-		     (s32)(tabla->mbhc_data.sta_mb - tabla->mbhc_data.sta_z);
+		z = (tabla->mbhc_data.sta_z);
+		mb = (tabla->mbhc_data.sta_mb);
+		mv = (value - z) * (s32)tabla->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 tabla_priv *tabla;
 	short bias_value;
 	int dce_mv, sta_mv;
-	struct tabla *core;
+	struct wcd9xxx *core;
 
 	pr_debug("%s:\n", __func__);
 
 	delayed_work = to_delayed_work(work);
-	tabla = container_of(delayed_work, struct tabla_priv, btn0_dwork);
+	tabla = container_of(delayed_work, struct tabla_priv, mbhc_btn_dwork);
 	core = dev_get_drvdata(tabla->codec->dev->parent);
 
 	if (tabla) {
@@ -4233,13 +4347,14 @@
 				 " STA: %d, DCE: %d\n", __func__,
 				 sta_mv, dce_mv);
 			tabla_snd_soc_jack_report(tabla, tabla->button_jack,
-						  SND_JACK_BTN_0,
-						  SND_JACK_BTN_0);
+						  tabla->buttons_pressed,
+						  tabla->buttons_pressed);
 		}
 	} else {
 		pr_err("%s: Bad tabla private data\n", __func__);
 	}
 
+	wcd9xxx_unlock_sleep(core);
 }
 
 void tabla_mbhc_cal(struct snd_soc_codec *codec)
@@ -4375,7 +4490,7 @@
 
 	n_ready = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_READY);
 	if (tabla->mclk_freq == TABLA_MCLK_RATE_12288KHZ) {
-		tabla->mbhc_data.npoll = 9;
+		tabla->mbhc_data.npoll = 4;
 		tabla->mbhc_data.nbounce_wait = 30;
 	} else if (tabla->mclk_freq == TABLA_MCLK_RATE_9600KHZ) {
 		tabla->mbhc_data.npoll = 7;
@@ -4406,7 +4521,7 @@
 	    tabla_codec_v_sta_dce(codec, DCE, btn_delta_mv);
 
 	tabla->mbhc_data.v_brh = tabla->mbhc_data.v_b1_h;
-	tabla->mbhc_data.v_brl = 0xFA55;
+	tabla->mbhc_data.v_brl = TABLA_MBHC_BUTTON_MIN;
 
 	tabla->mbhc_data.v_no_mic =
 	    tabla_codec_v_sta_dce(codec, STA, plug_type->v_no_mic);
@@ -4462,6 +4577,8 @@
 			    TABLA_MICBIAS2);
 
 	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02);
+
+	snd_soc_update_bits(codec, TABLA_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0);
 }
 
 static bool tabla_mbhc_fw_validate(const struct firmware *fw)
@@ -4495,6 +4612,7 @@
 
 	return true;
 }
+
 static void mbhc_fw_read(struct work_struct *work)
 {
 	struct delayed_work *dwork;
@@ -4542,80 +4660,13 @@
 	tabla_mbhc_calc_thres(codec);
 	tabla->mclk_cb(codec, 0);
 	tabla_codec_calibrate_hs_polling(codec);
-	rc = tabla_codec_enable_hs_detect(codec, 1);
+	rc = tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_MB_TRIGGER, false);
 
 	if (IS_ERR_VALUE(rc))
 		pr_err("%s: Failed to setup MBHC detection\n", __func__);
 
 }
 
-int tabla_hs_detect(struct snd_soc_codec *codec,
-		    struct snd_soc_jack *headset_jack,
-		    struct snd_soc_jack *button_jack,
-		    void *calibration, enum tabla_micbias_num micbias,
-		    int (*mclk_cb_fn) (struct snd_soc_codec*, int),
-		    int read_fw_bin, u32 mclk_rate)
-{
-	struct tabla_priv *tabla;
-	int rc = 0;
-
-	if (!codec || !calibration) {
-		pr_err("Error: no codec or calibration\n");
-		return -EINVAL;
-	}
-
-	if (mclk_rate != TABLA_MCLK_RATE_12288KHZ) {
-		if (mclk_rate == TABLA_MCLK_RATE_9600KHZ)
-			pr_err("Error: clock rate %dHz is not yet supported\n",
-				mclk_rate);
-		else
-			pr_err("Error: unsupported clock rate %d\n", mclk_rate);
-		return -EINVAL;
-	}
-
-	tabla = snd_soc_codec_get_drvdata(codec);
-	tabla->headset_jack = headset_jack;
-	tabla->button_jack = button_jack;
-	tabla->micbias = micbias;
-	tabla->calibration = calibration;
-	tabla->mclk_cb = mclk_cb_fn;
-	tabla->mclk_freq = mclk_rate;
-	tabla_get_mbhc_micbias_regs(codec, &tabla->mbhc_bias_regs);
-
-	/* Put CFILT in fast mode by default */
-	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl,
-		0x40, TABLA_CFILT_FAST_MODE);
-	INIT_DELAYED_WORK(&tabla->mbhc_firmware_dwork, mbhc_fw_read);
-	INIT_DELAYED_WORK(&tabla->btn0_dwork, btn0_lpress_fn);
-	INIT_WORK(&tabla->hphlocp_work, hphlocp_off_report);
-	INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report);
-
-	if (!read_fw_bin) {
-		tabla->mclk_cb(codec, 1);
-		tabla_mbhc_init(codec);
-		tabla_mbhc_cal(codec);
-		tabla_mbhc_calc_thres(codec);
-		tabla->mclk_cb(codec, 0);
-		tabla_codec_calibrate_hs_polling(codec);
-		rc =  tabla_codec_enable_hs_detect(codec, 1);
-	} else {
-		schedule_delayed_work(&tabla->mbhc_firmware_dwork,
-				usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
-	}
-
-	if (!IS_ERR_VALUE(rc)) {
-		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
-			0x10);
-		wcd9xxx_enable_irq(codec->control_data,
-			TABLA_IRQ_HPH_PA_OCPL_FAULT);
-		wcd9xxx_enable_irq(codec->control_data,
-			TABLA_IRQ_HPH_PA_OCPR_FAULT);
-	}
-
-	return rc;
-}
-EXPORT_SYMBOL_GPL(tabla_hs_detect);
-
 static int tabla_determine_button(const struct tabla_priv *priv,
 				  const s32 bias_mv)
 {
@@ -4676,8 +4727,8 @@
 static irqreturn_t tabla_dce_handler(int irq, void *data)
 {
 	int i, mask;
-	short bias_value_dce;
-	s32 bias_mv_dce;
+	short dce, sta;
+	s32 mv, stamv;
 	int btn = -1, meas = 0;
 	struct tabla_priv *priv = data;
 	const struct tabla_mbhc_btn_detect_cfg *d =
@@ -4685,26 +4736,49 @@
 	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, TABLA_A_CDC_MBHC_B1_STATUS) & 0x3E;
 
 	wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
 	wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL);
 
-	bias_value_dce = tabla_codec_read_dce_result(codec);
-	bias_mv_dce = tabla_codec_sta_dce_v(codec, 1, bias_value_dce);
+	mutex_lock(&priv->mbhc_mutex);
+	priv->mbhc_state = TABLA_IRQ_MBHC_POTENTIAL;
+	mutex_unlock(&priv->mbhc_mutex);
+
+	dce = tabla_codec_read_dce_result(codec);
+	mv = tabla_codec_sta_dce_v(codec, 1, dce);
+
+	if (mbhc_status != TABLA_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 = tabla_codec_read_sta_result(codec);
+			stamv = tabla_codec_sta_dce_v(codec, 0, sta);
+			btn = tabla_determine_button(priv, mv);
+			if (btn != tabla_determine_button(priv, stamv))
+				btn = -1;
+			goto done;
+		}
+	}
 
 	/* determine pressed button */
-	btnmeas[meas++] = tabla_determine_button(priv, bias_mv_dce);
+	btnmeas[meas++] = tabla_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 = tabla_codec_sta_dce(codec, 1);
-		bias_mv_dce = tabla_codec_sta_dce_v(codec, 1, bias_value_dce);
-		btnmeas[meas] = tabla_determine_button(priv, bias_mv_dce);
+	for (; ((n_btn_meas) && (meas < (n_btn_meas + 1))); meas++) {
+		dce = tabla_codec_sta_dce(codec, 1);
+		mv = tabla_codec_sta_dce_v(codec, 1, dce);
+		btnmeas[meas] = tabla_determine_button(priv, mv);
 		pr_debug("%s: meas %d - DCE %d,%d, button %d\n",
-			 __func__, meas, bias_value_dce, bias_mv_dce,
-			 btnmeas[meas]);
+			 __func__, meas, dce, mv, btnmeas[meas]);
 		/* if large enough measurements are collected,
 		 * start to check if last all n_btn_con measurements were
 		 * in same button low/high range */
@@ -4717,104 +4791,121 @@
 				/* 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) {
 		mask = tabla_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);
-			tabla_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:
 	return IRQ_HANDLED;
 }
 
+static int tabla_is_fake_press(struct tabla_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 = tabla_codec_sta_dce(codec, 0);
+			pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v,
+				 tabla_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 = tabla_codec_sta_dce(codec, 1);
+			pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v,
+				 tabla_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 tabla_release_handler(int irq, void *data)
 {
 	int ret;
-	short mb_v;
 	struct tabla_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__);
+
+	mutex_lock(&priv->mbhc_mutex);
+	priv->mbhc_state = TABLA_IRQ_MBHC_RELEASE;
+	mutex_unlock(&priv->mbhc_mutex);
+
 	wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE);
 
-	if (priv->buttons_pressed & SND_JACK_BTN_0) {
-		ret = cancel_delayed_work(&priv->btn0_dwork);
+	if (priv->buttons_pressed & TABLA_JACK_BUTTON_MASK) {
+		ret = cancel_delayed_work(&priv->mbhc_btn_dwork);
 		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)
 				tabla_snd_soc_jack_report(priv,
-							  priv->button_jack, 0,
-							  SND_JACK_BTN_0);
+							priv->button_jack, 0,
+							priv->buttons_pressed);
 		} else {
-			/* if scheduled btn0_dwork is canceled from here,
-			 * we have to unlock from here instead btn0_work */
+			/* if scheduled mbhc_btn_dwork is canceled from here,
+			 * we have to unlock from here instead btn_work */
 			wcd9xxx_unlock_sleep(core);
-			mb_v = tabla_codec_sta_dce(codec, 0);
-			pr_debug("%s: Mic Voltage on release STA: %d,%d\n",
-				 __func__, mb_v,
-				 tabla_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 (tabla_is_fake_press(priv)) {
+				pr_debug("%s: Fake button press interrupt\n",
 					 __func__);
-			else if (priv->button_jack) {
+			} else if (priv->button_jack) {
 				pr_debug("%s: Reporting short button 0 "
 					 "press and release\n", __func__);
 				tabla_snd_soc_jack_report(priv,
-							  priv->button_jack,
-							  SND_JACK_BTN_0,
-							  SND_JACK_BTN_0);
+							priv->button_jack,
+							priv->buttons_pressed,
+							priv->buttons_pressed);
 				tabla_snd_soc_jack_report(priv,
-							  priv->button_jack, 0,
-							  SND_JACK_BTN_0);
+							priv->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);
-		tabla_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 &= ~TABLA_JACK_BUTTON_MASK;
 	}
 
+	tabla_codec_calibrate_hs_polling(codec);
+
+	mutex_lock(&priv->mbhc_mutex);
 	tabla_codec_start_hs_polling(codec);
+	mutex_unlock(&priv->mbhc_mutex);
+
 	return IRQ_HANDLED;
 }
 
@@ -4824,6 +4915,7 @@
 	const struct tabla_mbhc_general_cfg *generic =
 	    TABLA_MBHC_CAL_GENERAL_PTR(tabla->calibration);
 
+	mutex_lock(&tabla->mbhc_mutex);
 	if (!tabla->mclk_enabled && !tabla->mbhc_polling_active)
 		tabla_codec_enable_config_mode(codec, 1);
 
@@ -4838,6 +4930,7 @@
 	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0xA, 0x8);
 	if (!tabla->mclk_enabled && !tabla->mbhc_polling_active)
 		tabla_codec_enable_config_mode(codec, 0);
+	mutex_unlock(&tabla->mbhc_mutex);
 
 	snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x00);
 }
@@ -4848,6 +4941,7 @@
 
 	tabla_codec_shutdown_hs_removal_detect(codec);
 
+	mutex_lock(&tabla->mbhc_mutex);
 	if (!tabla->mclk_enabled) {
 		snd_soc_update_bits(codec, TABLA_A_TX_COM_BIAS, 0xE0, 0x00);
 		tabla_codec_disable_clock_block(codec);
@@ -4855,6 +4949,7 @@
 	}
 
 	tabla->mbhc_polling_active = false;
+	mutex_unlock(&tabla->mbhc_mutex);
 }
 
 static irqreturn_t tabla_hphl_ocp_irq(int irq, void *data)
@@ -4952,16 +5047,155 @@
 	}
 }
 
+static bool mbhc_is_fake_insertion(struct snd_soc_codec *codec, s32 mic_volt)
+{
+	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+	struct tabla_mbhc_plug_type_cfg *plug_type =
+		TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->calibration);
+
+	if (mic_volt > plug_type->v_hs_max ||
+	    (mic_volt < TABLA_MBHC_FAKE_INSERT_HIGH &&
+	     mic_volt > TABLA_MBHC_FAKE_INSERT_LOW))
+		return true;
+
+	return false;
+}
+
+static void tabla_codec_detect_plug_type(struct snd_soc_codec *codec)
+{
+	struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+	const struct tabla_mbhc_plug_detect_cfg *plug_det =
+	    TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->calibration);
+	int ldo_h_on, micb_cfilt_on, i;
+	short mb_v[MBHC_NUM_DCE_PLUG_DETECT];
+	s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT];
+	u8 plug_type[MBHC_NUM_DCE_PLUG_DETECT];
+	struct tabla_mbhc_plug_type_cfg *plug_type_ptr;
+
+	ldo_h_on = snd_soc_read(codec, TABLA_A_LDO_H_MODE_1) & 0x80;
+	micb_cfilt_on = snd_soc_read(codec, tabla->mbhc_bias_regs.cfilt_ctl) &
+				     0x80;
+
+	/* Turn on the override */
+	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x4, 0x4);
+
+	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);
+
+	/*
+	 * First DCE measurement,
+	 * IF this is fake, discontinue detection
+	 * and restart insertion detection
+	 */
+	mb_v[0] = tabla_codec_setup_hs_polling(codec);
+	mic_mv[0] = tabla_codec_sta_dce_v(codec, 1, mb_v[0]);
+	if (mbhc_is_fake_insertion(codec, mic_mv[0])) {
+		pr_debug("%s: Detect attempt 1, detected Fake\n", __func__);
+		tabla_codec_shutdown_hs_polling(codec);
+		tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_MB_TRIGGER,
+					     false);
+		return;
+	}
+	pr_debug("%s: Run 1, DCE %x, mic_mv = %d\n", __func__, mb_v[0],
+		 mic_mv[0]);
+
+	/*
+	 * Perform two more DCE measurements,
+	 * IF any of them is fake, discontinue detection
+	 * and restart insertion detection
+	 */
+	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x00);
+	for (i = 1; i < MBHC_NUM_DCE_PLUG_DETECT; i++) {
+		mb_v[i] = tabla_codec_sta_dce(codec, 1);
+		mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]);
+		pr_debug("%s: Run %d, DCE %x, mic_mv = %d\n", __func__, i + 1,
+			 mb_v[1], mic_mv[i]);
+		if (mbhc_is_fake_insertion(codec, mic_mv[i])) {
+			pr_debug("%s: Detect attempt %d, detected Fake\n",
+				 __func__, i + 1);
+			snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL,
+					    0x02, 0x02);
+			tabla_codec_shutdown_hs_polling(codec);
+			tabla_codec_enable_hs_detect(codec, 1,
+						     MBHC_USE_MB_TRIGGER,
+						     false);
+			return;
+		}
+	}
+	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02);
+
+	plug_type_ptr = TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->calibration);
+
+	/*
+	 * If we are here, means none of the three
+	 * measurements are fake, continue plug type detection.
+	 * If all three measurements do not produce same
+	 * plug type, restart insertion detection
+	 */
+	for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) {
+		if (mic_mv[i] < plug_type_ptr->v_no_mic) {
+			plug_type[i] = PLUG_TYPE_HEADPHONE;
+			pr_debug("%s: Detect attempt %d, detected Headphone\n",
+				 __func__, i);
+		} else {
+			plug_type[i] = PLUG_TYPE_HEADSET;
+			pr_debug("%s: Detect attempt %d, detected Headset\n",
+				 __func__, i);
+		}
+
+		if (i > 0 && (plug_type[i - 1] != plug_type[i])) {
+			pr_err("%s: Detect attempt %d and %d are not same",
+			       __func__, i - 1, i);
+			tabla_codec_shutdown_hs_polling(codec);
+			tabla_codec_enable_hs_detect(codec, 1,
+						     MBHC_USE_MB_TRIGGER,
+						     false);
+			return;
+		}
+	}
+
+	if (plug_type[0] == PLUG_TYPE_HEADPHONE) {
+		pr_debug("%s: Headphone Detected\n", __func__);
+		tabla->hph_status |= SND_JACK_HEADPHONE;
+		if (tabla->headset_jack) {
+			pr_debug("%s: Reporting insertion %d\n", __func__,
+				 SND_JACK_HEADPHONE);
+			tabla_snd_soc_jack_report(tabla, tabla->headset_jack,
+						  tabla->hph_status,
+						  TABLA_JACK_MASK);
+		}
+		tabla_codec_shutdown_hs_polling(codec);
+		tabla_codec_enable_hs_detect(codec, 0, 0, false);
+		tabla_sync_hph_state(tabla);
+	} else if (plug_type[0] == PLUG_TYPE_HEADSET) {
+		pr_debug("%s: Headset detected\n", __func__);
+		tabla->hph_status |= SND_JACK_HEADSET;
+		if (tabla->headset_jack) {
+			pr_err("%s: Reporting insertion %d\n", __func__,
+			       SND_JACK_HEADSET);
+			tabla_snd_soc_jack_report(tabla, tabla->headset_jack,
+						  tabla->hph_status,
+						  TABLA_JACK_MASK);
+		}
+		/* avoid false button press detect */
+		msleep(50);
+		mutex_lock(&tabla->mbhc_mutex);
+		tabla_codec_start_hs_polling(codec);
+		mutex_unlock(&tabla->mbhc_mutex);
+		tabla_sync_hph_state(tabla);
+	}
+}
+
 static irqreturn_t tabla_hs_insert_irq(int irq, void *data)
 {
 	struct tabla_priv *priv = data;
 	struct snd_soc_codec *codec = priv->codec;
-	const struct tabla_mbhc_plug_detect_cfg *plug_det =
-	    TABLA_MBHC_CAL_PLUG_DET_PTR(priv->calibration);
-	int ldo_h_on, micb_cfilt_on;
-	short mb_v;
-	u8 is_removal;
-	int mic_mv;
+	u8 is_removal, is_mb_trigger;
+	struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent);
+	int ret;
 
 	pr_debug("%s: enter\n", __func__);
 	wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION);
@@ -4969,150 +5203,64 @@
 	is_removal = snd_soc_read(codec, TABLA_A_CDC_MBHC_INT_CTL) & 0x02;
 	snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x03, 0x00);
 
+	is_mb_trigger = snd_soc_read(codec, priv->mbhc_bias_regs.mbhc_reg) &
+				     0x10;
+
 	/* 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, TABLA_A_MBHC_HPH, 0x13, 0x00);
-
-	if (priv->mbhc_fake_ins_start &&
-	    time_after(jiffies, priv->mbhc_fake_ins_start +
-			msecs_to_jiffies(TABLA_FAKE_INS_THRESHOLD_MS))) {
-		pr_debug("%s: fake context interrupt, reset insertion\n",
-			 __func__);
-		priv->mbhc_fake_ins_start = 0;
-		tabla_codec_shutdown_hs_polling(codec);
-		tabla_codec_enable_hs_detect(codec, 1);
-		return IRQ_HANDLED;
-	}
-
-	ldo_h_on = snd_soc_read(codec, TABLA_A_LDO_H_MODE_1) & 0x80;
-	micb_cfilt_on = snd_soc_read(codec, priv->mbhc_bias_regs.cfilt_ctl)
-			    & 0x80;
-
-	if (!ldo_h_on)
-		snd_soc_update_bits(codec, TABLA_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, TABLA_A_LDO_H_MODE_1, 0x80, 0x0);
-	if (!micb_cfilt_on)
-		snd_soc_update_bits(codec, priv->mbhc_bias_regs.cfilt_ctl,
-				    0x80, 0x0);
+	snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00);
 
 	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)
-			tabla_codec_switch_micbias(codec, 0);
+		tabla_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 (tabla_is_hph_pa_on(codec)) {
-			set_bit(TABLA_HPHL_PA_OFF_ACK, &priv->hph_pa_dac_state);
-			set_bit(TABLA_HPHR_PA_OFF_ACK, &priv->hph_pa_dac_state);
-		}
-
-		if (tabla_is_hph_dac_on(codec, 1))
-			set_bit(TABLA_HPHL_DAC_OFF_ACK,
-				&priv->hph_pa_dac_state);
-		if (tabla_is_hph_dac_on(codec, 0))
-			set_bit(TABLA_HPHR_DAC_OFF_ACK,
-				&priv->hph_pa_dac_state);
-
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting removal\n", __func__);
 			tabla_snd_soc_jack_report(priv, priv->headset_jack,
 						  priv->hph_status,
 						  TABLA_JACK_MASK);
 		}
+
+		hphocp_off_report(priv, SND_JACK_OC_HPHR,
+				  TABLA_IRQ_HPH_PA_OCPR_FAULT);
+		hphocp_off_report(priv, SND_JACK_OC_HPHL,
+				  TABLA_IRQ_HPH_PA_OCPL_FAULT);
+
 		tabla_codec_shutdown_hs_removal_detect(codec);
-		tabla_codec_enable_hs_detect(codec, 1);
+		tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_MB_TRIGGER,
+					     true);
 		return IRQ_HANDLED;
 	}
 
-	mb_v = tabla_codec_setup_hs_polling(codec);
-	mic_mv = tabla_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(TABLA_FAKE_INS_THRESHOLD_MS))) {
-			/* Disable HPH trigger and enable MIC line trigger */
-			snd_soc_update_bits(codec, TABLA_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, TABLA_A_MBHC_HPH,
-					    0x13 | 0x0C,
-					    0x13 | plug_det->hph_current << 2);
+	if (is_mb_trigger && !is_removal) {
+		pr_debug("%s: Waiting for Headphone left trigger\n",
+			__func__);
+		wcd9xxx_lock_sleep(core);
+		if (schedule_delayed_work(&priv->mbhc_insert_dwork,
+					  usecs_to_jiffies(1000000)) == 0) {
+			pr_err("%s: mbhc_insert_dwork is already scheduled\n",
+			       __func__);
+			wcd9xxx_unlock_sleep(core);
 		}
-		/* Setup for insertion detection */
-		snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0);
-		wcd9xxx_enable_irq(codec->control_data,
-					TABLA_IRQ_MBHC_INSERTION);
-		snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0x1);
+		tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_HPHL_TRIGGER,
+					     false);
+		return IRQ_HANDLED;
+	}
 
-	} 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);
-			tabla_snd_soc_jack_report(priv, priv->headset_jack,
-						  priv->hph_status,
-						  TABLA_JACK_MASK);
-		}
-		tabla_codec_shutdown_hs_polling(codec);
-		tabla_codec_enable_hs_detect(codec, 0);
-		tabla_sync_hph_state(priv);
+	ret = cancel_delayed_work(&priv->mbhc_insert_dwork);
+	if (ret != 0) {
+		pr_debug("%s: Complete plug insertion, Detecting plug type\n",
+			 __func__);
+		tabla_codec_detect_plug_type(codec);
+		wcd9xxx_unlock_sleep(core);
 	} 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);
-			tabla_snd_soc_jack_report(priv, priv->headset_jack,
-						  priv->hph_status,
-						  TABLA_JACK_MASK);
-		}
-		/* avoid false button press detect */
-		msleep(50);
-		tabla_codec_start_hs_polling(codec);
-		tabla_sync_hph_state(priv);
+		wcd9xxx_enable_irq(codec->control_data,
+				   TABLA_IRQ_MBHC_INSERTION);
+		pr_err("%s: Error detecting plug insertion\n", __func__);
 	}
 
 	return IRQ_HANDLED;
@@ -5125,8 +5273,8 @@
 	struct snd_soc_codec *codec = priv->codec;
 	const struct tabla_mbhc_general_cfg *generic =
 	    TABLA_MBHC_CAL_GENERAL_PTR(priv->calibration);
-	int fake_removal = 0;
 	int min_us = TABLA_FAKE_REMOVAL_MIN_PERIOD_MS * 1000;
+	int fake_removal = 0;
 
 	pr_debug("%s: enter, removal interrupt\n", __func__);
 	wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
@@ -5137,7 +5285,9 @@
 		     generic->t_shutdown_plug_rem);
 
 	do {
+		snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x00);
 		bias_value = tabla_codec_sta_dce(codec, 1);
+		snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02);
 		pr_debug("%s: DCE %d,%d, %d us left\n", __func__, bias_value,
 			 tabla_codec_sta_dce_v(codec, 1, bias_value), min_us);
 		if (bias_value < (short)priv->mbhc_data.v_ins_h) {
@@ -5149,29 +5299,129 @@
 
 	if (fake_removal) {
 		pr_debug("False alarm, headset not actually removed\n");
+		mutex_lock(&priv->mbhc_mutex);
 		tabla_codec_start_hs_polling(codec);
+		mutex_unlock(&priv->mbhc_mutex);
 	} 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)
-			tabla_codec_switch_micbias(codec, 0);
+		tabla_codec_switch_micbias(codec, 0);
 		priv->hph_status &= ~SND_JACK_HEADSET;
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting removal\n", __func__);
 			tabla_snd_soc_jack_report(priv, priv->headset_jack, 0,
 						  TABLA_JACK_MASK);
 		}
+
+		hphocp_off_report(priv, SND_JACK_OC_HPHR,
+				  TABLA_IRQ_HPH_PA_OCPR_FAULT);
+		hphocp_off_report(priv, SND_JACK_OC_HPHL,
+				  TABLA_IRQ_HPH_PA_OCPL_FAULT);
+
 		tabla_codec_shutdown_hs_polling(codec);
 
-		tabla_codec_enable_hs_detect(codec, 1);
+		tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_MB_TRIGGER,
+					     true);
 	}
 
 	return IRQ_HANDLED;
 }
 
+void mbhc_insert_work(struct work_struct *work)
+{
+	struct delayed_work *dwork;
+	struct tabla_priv *tabla;
+	struct snd_soc_codec *codec;
+	struct wcd9xxx *tabla_core;
+
+	dwork = to_delayed_work(work);
+	tabla = container_of(dwork, struct tabla_priv,
+				mbhc_insert_dwork);
+	codec = tabla->codec;
+	tabla_core = dev_get_drvdata(codec->dev->parent);
+
+	pr_debug("%s:\n", __func__);
+
+	/* Turn off both HPH and MIC line schmitt triggers */
+	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x90, 0x00);
+	snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00);
+	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x00);
+	wcd9xxx_disable_irq_sync(codec->control_data, TABLA_IRQ_MBHC_INSERTION);
+	tabla_codec_detect_plug_type(codec);
+	wcd9xxx_unlock_sleep(tabla_core);
+}
+
+int tabla_hs_detect(struct snd_soc_codec *codec,
+		    struct snd_soc_jack *headset_jack,
+		    struct snd_soc_jack *button_jack,
+		    void *calibration, enum tabla_micbias_num micbias,
+		    int (*mclk_cb_fn) (struct snd_soc_codec*, int),
+		    int read_fw_bin, u32 mclk_rate)
+{
+	struct tabla_priv *tabla;
+	int rc = 0;
+
+	if (!codec || !calibration) {
+		pr_err("Error: no codec or calibration\n");
+		return -EINVAL;
+	}
+
+	if (mclk_rate != TABLA_MCLK_RATE_12288KHZ) {
+		if (mclk_rate == TABLA_MCLK_RATE_9600KHZ)
+			pr_err("Error: clock rate %dHz is not yet supported\n",
+			       mclk_rate);
+		else
+			pr_err("Error: unsupported clock rate %d\n", mclk_rate);
+		return -EINVAL;
+	}
+
+	tabla = snd_soc_codec_get_drvdata(codec);
+	tabla->headset_jack = headset_jack;
+	tabla->button_jack = button_jack;
+	tabla->micbias = micbias;
+	tabla->calibration = calibration;
+	tabla->mclk_cb = mclk_cb_fn;
+	tabla->mclk_freq = mclk_rate;
+	tabla_get_mbhc_micbias_regs(codec, &tabla->mbhc_bias_regs);
+
+	/* Put CFILT in fast mode by default */
+	snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl,
+			    0x40, TABLA_CFILT_FAST_MODE);
+	INIT_DELAYED_WORK(&tabla->mbhc_firmware_dwork, mbhc_fw_read);
+	INIT_DELAYED_WORK(&tabla->mbhc_btn_dwork, btn_lpress_fn);
+	INIT_WORK(&tabla->hphlocp_work, hphlocp_off_report);
+	INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report);
+	INIT_DELAYED_WORK(&tabla->mbhc_insert_dwork, mbhc_insert_work);
+
+	if (!read_fw_bin) {
+		tabla->mclk_cb(codec, 1);
+		tabla_mbhc_init(codec);
+		tabla_mbhc_cal(codec);
+		tabla_mbhc_calc_thres(codec);
+		tabla->mclk_cb(codec, 0);
+		tabla_codec_calibrate_hs_polling(codec);
+		rc =  tabla_codec_enable_hs_detect(codec, 1,
+						   MBHC_USE_MB_TRIGGER, false);
+	} else {
+		schedule_delayed_work(&tabla->mbhc_firmware_dwork,
+				      usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
+	}
+
+	if (!IS_ERR_VALUE(rc)) {
+		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x10);
+		wcd9xxx_enable_irq(codec->control_data,
+				 TABLA_IRQ_HPH_PA_OCPL_FAULT);
+		wcd9xxx_enable_irq(codec->control_data,
+				 TABLA_IRQ_HPH_PA_OCPR_FAULT);
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(tabla_hs_detect);
+
 static unsigned long slimbus_value;
 
 static irqreturn_t tabla_slimbus_irq(int irq, void *data)
@@ -5309,6 +5559,27 @@
 		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL,
 			0xE0, (pdata->ocp.hph_ocp_limit << 5));
 	}
+
+	for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) {
+		if (!strncmp(pdata->regulator[i].name, "CDC_VDDA_RX", 11)) {
+			if (pdata->regulator[i].min_uV == 1800000 &&
+			    pdata->regulator[i].max_uV == 1800000) {
+				snd_soc_write(codec, TABLA_A_BIAS_REF_CTL,
+					      0x1C);
+			} else if (pdata->regulator[i].min_uV == 2200000 &&
+				   pdata->regulator[i].max_uV == 2200000) {
+				snd_soc_write(codec, TABLA_A_BIAS_REF_CTL,
+					      0x1E);
+			} else {
+				pr_err("%s: unsupported CDC_VDDA_RX voltage "
+				       "min %d, max %d\n", __func__,
+				       pdata->regulator[i].min_uV,
+				       pdata->regulator[i].max_uV);
+				rc = -EINVAL;
+			}
+			break;
+		}
+	}
 done:
 	return rc;
 }
@@ -5542,6 +5813,9 @@
 	tabla->mbhc_fake_ins_start = 0;
 	tabla->no_mic_headset_override = false;
 	tabla->codec = codec;
+	mutex_init(&tabla->mbhc_mutex);
+	tabla->mbhc_state = -1;
+	tabla->mbhc_last_resume = 0;
 	for (i = 0; i < COMPANDER_MAX; i++) {
 		tabla->comp_enabled[i] = 0;
 		tabla->comp_fs[i] = COMPANDER_FS_48KHZ;
@@ -5706,6 +5980,7 @@
 	wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla);
 err_insert_irq:
 err_pdata:
+	mutex_destroy(&tabla->mbhc_mutex);
 	kfree(tabla);
 	return ret;
 }
@@ -5718,10 +5993,13 @@
 	wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla);
 	wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla);
 	wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla);
+	mutex_lock(&tabla->mbhc_mutex);
 	tabla_codec_disable_clock_block(codec);
+	mutex_unlock(&tabla->mbhc_mutex);
 	tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF);
 	if (tabla->mbhc_fw)
 		release_firmware(tabla->mbhc_fw);
+	mutex_destroy(&tabla->mbhc_mutex);
 	for (i = 0; i < ARRAY_SIZE(tabla_dai); i++)
 		kfree(tabla->dai[i].ch_num);
 	kfree(tabla);
@@ -5786,7 +6064,10 @@
 
 static int tabla_resume(struct device *dev)
 {
+	struct platform_device *pdev = to_platform_device(dev);
+	struct tabla_priv *tabla = platform_get_drvdata(pdev);
 	dev_dbg(dev, "%s: system resume\n", __func__);
+	tabla->mbhc_last_resume = jiffies;
 	return 0;
 }
 
diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c
index cca0abc..9a8fdba 100644
--- a/sound/soc/msm/apq8064.c
+++ b/sound/soc/msm/apq8064.c
@@ -840,11 +840,11 @@
 
 	codec_clk = clk_get(cpu_dai->dev, "osr_clk");
 
-	tabla_hs_detect(codec, &hs_jack, &button_jack, tabla_mbhc_cal,
-			TABLA_MICBIAS2, msm_enable_codec_ext_clk, 0,
-			TABLA_EXT_CLK_RATE);
+	err = tabla_hs_detect(codec, &hs_jack, &button_jack, tabla_mbhc_cal,
+			      TABLA_MICBIAS2, msm_enable_codec_ext_clk, 0,
+			      TABLA_EXT_CLK_RATE);
 
-	return 0;
+	return err;
 }
 
 static struct snd_soc_dsp_link lpa_fe_media = {
diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c
index 0854dff..a8aa05d 100644
--- a/sound/soc/msm/msm8960.c
+++ b/sound/soc/msm/msm8960.c
@@ -753,11 +753,11 @@
 
 	codec_clk = clk_get(cpu_dai->dev, "osr_clk");
 
-	tabla_hs_detect(codec, &hs_jack, &button_jack, tabla_mbhc_cal,
-			TABLA_MICBIAS2, msm8960_enable_codec_ext_clk, 0,
-			TABLA_EXT_CLK_RATE);
+	err = tabla_hs_detect(codec, &hs_jack, &button_jack, tabla_mbhc_cal,
+			      TABLA_MICBIAS2, msm8960_enable_codec_ext_clk, 0,
+			      TABLA_EXT_CLK_RATE);
 
-	return 0;
+	return err;
 }
 
 static struct snd_soc_dsp_link lpa_fe_media = {