Merge "ASoC: wcd9xxx: introduce headphone impedance detection support"
diff --git a/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h b/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h
index 412341a..bf76026 100644
--- a/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h
+++ b/include/linux/mfd/wcd9xxx/wcd9xxx_registers.h
@@ -276,5 +276,37 @@
 #define WCD9XXX_A_CDC_CLSH_V_PA_MIN_HPH			(0x331)
 #define WCD9XXX_A_CDC_CLSH_V_PA_MIN_HPH__POR				(0x00)
 
+#define WCD9XXX_A_CDC_RX1_B6_CTL			(0x2B5)
+#define WCD9XXX_A_CDC_RX1_B6_CTL__POR				(0x80)
+#define WCD9XXX_A_CDC_RX2_B6_CTL			(0x2BD)
+#define WCD9XXX_A_CDC_RX2_B6_CTL__POR				(0x80)
+#define WCD9XXX_A_RX_HPH_L_GAIN				(0x1AE)
+#define WCD9XXX_A_RX_HPH_L_GAIN__POR				(0x00)
+#define WCD9XXX_A_RX_HPH_R_GAIN				(0x1B4)
+#define WCD9XXX_A_RX_HPH_R_GAIN__POR				(0x00)
+#define WCD9XXX_A_RX_HPH_CHOP_CTL			(0x1A5)
+#define WCD9XXX_A_RX_HPH_CHOP_CTL__POR				(0xB4)
+#define WCD9XXX_A_RX_HPH_L_TEST				(0x1AF)
+#define WCD9XXX_A_RX_HPH_L_TEST__POR				(0x00)
+#define WCD9XXX_A_RX_HPH_R_TEST				(0x1B5)
+#define WCD9XXX_A_RX_HPH_R_TEST__POR				(0x00)
+#define WCD9XXX_A_CDC_CLK_RX_B1_CTL			(0x30F)
+#define WCD9XXX_A_CDC_CLK_RX_B1_CTL__POR			(0x00)
+#define WCD9XXX_A_NCP_CLK				(0x193)
+#define WCD9XXX_A_NCP_CLK__POR					(0x94)
+#define WCD9XXX_A_RX_HPH_BIAS_WG_OCP			(0x1A9)
+#define WCD9XXX_A_RX_HPH_BIAS_WG_OCP__POR			(0x2A)
+#define WCD9XXX_A_RX_HPH_CNP_WG_CTL			(0x1AC)
+#define WCD9XXX_A_RX_HPH_CNP_WG_CTL__POR			(0xDE)
+#define WCD9XXX_A_CDC_CONN_RX2_B1_CTL			(0x383)
+#define WCD9XXX_A_CDC_CONN_RX2_B1_CTL__POR			(0x00)
+#define WCD9XXX_A_CDC_PA_RAMP_B1_CTL			(0x361)
+#define WCD9XXX_A_CDC_PA_RAMP_B1_CTL__POR			(0x00)
+#define WCD9XXX_A_CDC_PA_RAMP_B2_CTL			(0x362)
+#define WCD9XXX_A_CDC_PA_RAMP_B2_CTL__POR			(0x00)
+#define WCD9XXX_A_CDC_PA_RAMP_B3_CTL			(0x363)
+#define WCD9XXX_A_CDC_PA_RAMP_B3_CTL__POR			(0x00)
+#define WCD9XXX_A_CDC_PA_RAMP_B4_CTL			(0x364)
+#define WCD9XXX_A_CDC_PA_RAMP_B4_CTL__POR			(0x00)
 
 #endif
diff --git a/sound/soc/codecs/wcd9320-tables.c b/sound/soc/codecs/wcd9320-tables.c
index e834b80..af9725c 100644
--- a/sound/soc/codecs/wcd9320-tables.c
+++ b/sound/soc/codecs/wcd9320-tables.c
@@ -680,7 +680,10 @@
 	[TAIKO_A_CDC_SPKR_CLIPDET_VAL5] = 1,
 	[TAIKO_A_CDC_SPKR_CLIPDET_VAL6] = 1,
 	[TAIKO_A_CDC_SPKR_CLIPDET_VAL7] = 1,
-
+	[TAIKO_A_CDC_PA_RAMP_B1_CTL] = 1,
+	[TAIKO_A_CDC_PA_RAMP_B2_CTL] = 1,
+	[TAIKO_A_CDC_PA_RAMP_B3_CTL] = 1,
+	[TAIKO_A_CDC_PA_RAMP_B4_CTL] = 1,
 };
 
 const u8 taiko_reset_reg_defaults[TAIKO_CACHE_SIZE] = {
diff --git a/sound/soc/codecs/wcd9320.c b/sound/soc/codecs/wcd9320.c
index 4edcec6..03de4e0 100644
--- a/sound/soc/codecs/wcd9320.c
+++ b/sound/soc/codecs/wcd9320.c
@@ -1456,6 +1456,32 @@
 			analog_gain),
 };
 
+static int taiko_hph_impedance_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	uint32_t zl, zr;
+	bool hphr;
+	struct soc_multi_mixer_control *mc;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct taiko_priv *priv = snd_soc_codec_get_drvdata(codec);
+
+	mc = (struct soc_multi_mixer_control *)(kcontrol->private_value);
+
+	hphr = mc->shift;
+	wcd9xxx_mbhc_get_impedance(&priv->mbhc, &zl, &zr);
+	pr_debug("%s: zl %u, zr %u\n", __func__, zl, zr);
+	ucontrol->value.integer.value[0] = hphr ? zr : zl;
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new impedance_detect_controls[] = {
+	SOC_SINGLE_EXT("HPHL Impedance", 0, 0, UINT_MAX, 0,
+		       taiko_hph_impedance_get, NULL),
+	SOC_SINGLE_EXT("HPHR Impedance", 0, 1, UINT_MAX, 0,
+		       taiko_hph_impedance_get, NULL),
+};
+
 static const char * const rx_mix1_text[] = {
 	"ZERO", "SRC1", "SRC2", "IIR1", "IIR2", "RX1", "RX2", "RX3", "RX4",
 		"RX5", "RX6", "RX7"
@@ -6491,6 +6517,9 @@
 			ARRAY_SIZE(taiko_2_x_analog_gain_controls));
 	}
 
+	snd_soc_add_codec_controls(codec, impedance_detect_controls,
+				   ARRAY_SIZE(impedance_detect_controls));
+
 	control->num_rx_port = TAIKO_RX_MAX;
 	control->rx_chs = ptr;
 	memcpy(control->rx_chs, taiko_rx_chs, sizeof(taiko_rx_chs));
diff --git a/sound/soc/codecs/wcd9xxx-mbhc.c b/sound/soc/codecs/wcd9xxx-mbhc.c
index cd11703..e34dec1 100644
--- a/sound/soc/codecs/wcd9xxx-mbhc.c
+++ b/sound/soc/codecs/wcd9xxx-mbhc.c
@@ -18,6 +18,7 @@
 #include <linux/printk.h>
 #include <linux/ratelimit.h>
 #include <linux/debugfs.h>
+#include <linux/list.h>
 #include <linux/mfd/wcd9xxx/core.h>
 #include <linux/mfd/wcd9xxx/wcd9xxx_registers.h>
 #include <linux/mfd/wcd9xxx/wcd9320_registers.h>
@@ -36,6 +37,7 @@
 #include "wcd9306.h"
 #include "wcd9xxx-mbhc.h"
 #include "wcd9xxx-resmgr.h"
+#include "wcd9xxx-common.h"
 
 #define WCD9XXX_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \
 			   SND_JACK_OC_HPHR | SND_JACK_LINEOUT | \
@@ -92,7 +94,10 @@
 #define WCD9XXX_GM_SWAP_THRES_MAX_MV 650
 #define WCD9XXX_THRESHOLD_MIC_THRESHOLD 200
 
-#define WCD9XXX_USLEEP_RANGE_MARGIN_US 1000
+#define WCD9XXX_USLEEP_RANGE_MARGIN_US 100
+
+/* RX_HPH_CNP_WG_TIME increases by 0.24ms */
+#define WCD9XXX_WG_TIME_FACTOR_US	240
 
 #define WCD9XXX_IRQ_MBHC_JACK_SWITCH_DEFAULT 28
 
@@ -112,6 +117,12 @@
 	enum wcd9xxx_mbhc_plug_type _type;
 };
 
+struct wcd9xxx_register_save_node {
+	struct list_head lh;
+	u16 reg;
+	u16 value;
+};
+
 enum meas_type {
 	STA = 0,
 	DCE,
@@ -142,6 +153,9 @@
 	WCD9XXX_CURRENT_V_BR_H,
 };
 
+static int wcd9xxx_detect_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl,
+				    uint32_t *zr);
+
 static bool wcd9xxx_mbhc_polling(struct wcd9xxx_mbhc *mbhc)
 {
 	return mbhc->polling_active;
@@ -728,6 +742,7 @@
 			mbhc->micbias_enable_cb(mbhc->codec, false);
 			mbhc->micbias_enable = false;
 		}
+		mbhc->zl = mbhc->zr = 0;
 		pr_debug("%s: Reporting removal %d(%x)\n", __func__,
 			 jack_type, mbhc->hph_status);
 		wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, mbhc->hph_status,
@@ -752,6 +767,7 @@
 				}
 				pr_debug("%s: Reporting removal (%x)\n",
 						__func__, mbhc->hph_status);
+				mbhc->zl = mbhc->zr = 0;
 				wcd9xxx_jack_report(mbhc, &mbhc->headset_jack,
 						    0, WCD9XXX_JACK_MASK);
 				mbhc->hph_status = 0;
@@ -775,6 +791,8 @@
 			pr_debug("%s: Enabling micbias\n", __func__);
 			mbhc->micbias_enable_cb(mbhc->codec, true);
 		}
+
+		wcd9xxx_detect_impedance(mbhc, &mbhc->zl, &mbhc->zr);
 		pr_debug("%s: Reporting insertion %d(%x)\n", __func__,
 			 jack_type, mbhc->hph_status);
 		wcd9xxx_jack_report(mbhc, &mbhc->headset_jack,
@@ -2820,7 +2838,8 @@
 	dce_wait = (1000 * 512 * ncic * (nmeas + 1)) / (rate / 1000);
 	sta_wait = (1000 * 128 * (navg + 1)) / (rate / 1000);
 	mbhc->mbhc_data.t_dce = dce_wait;
-	mbhc->mbhc_data.t_sta = sta_wait;
+	/* give extra margin to sta for safety */
+	mbhc->mbhc_data.t_sta = sta_wait + 250;
 	mbhc->mbhc_data.t_sta_dce = ((1000 * 256) / (rate / 1000) *
 				     n_ready[idx]) + 10;
 
@@ -3575,6 +3594,300 @@
 	return ret;
 }
 
+static int wcd9xxx_soc_update_bits_push(struct snd_soc_codec *codec,
+					struct list_head *list,
+					uint16_t reg, uint8_t mask,
+					uint8_t value)
+{
+	int rc;
+	struct wcd9xxx_register_save_node *node;
+
+	node = kmalloc(sizeof(*node), GFP_KERNEL);
+	if (unlikely(!node)) {
+		pr_err("%s: Not enough memory\n", __func__);
+		return -ENOMEM;
+	}
+	node->reg = reg;
+	node->value = snd_soc_read(codec, reg);
+	list_add(&node->lh, list);
+	if (mask == 0xFF)
+		rc = snd_soc_write(codec, reg, value);
+	else
+		rc = snd_soc_update_bits(codec, reg, mask, value);
+	return rc;
+}
+
+static int wcd9xxx_prepare_static_pa(struct wcd9xxx_mbhc *mbhc,
+				     struct list_head *lh)
+{
+	int i;
+	struct snd_soc_codec *codec = mbhc->codec;
+
+	const struct wcd9xxx_reg_mask_val reg_set_paon[] = {
+		{WCD9XXX_A_RX_HPH_OCP_CTL, 0x18, 0x00},
+		{WCD9XXX_A_RX_HPH_L_TEST, 0x1, 0x0},
+		{WCD9XXX_A_RX_HPH_R_TEST, 0x1, 0x0},
+		{WCD9XXX_A_RX_HPH_BIAS_WG_OCP, 0xff, 0x1A},
+		{WCD9XXX_A_RX_HPH_CNP_WG_CTL, 0xff, 0xDB},
+		{WCD9XXX_A_RX_HPH_CNP_WG_TIME, 0xff, 0x15},
+		{WCD9XXX_A_CDC_RX1_B6_CTL, 0xff, 0x81},
+		{WCD9XXX_A_CDC_CLK_RX_B1_CTL, 0x01, 0x01},
+		{WCD9XXX_A_RX_HPH_CHOP_CTL, 0xff, 0xA4},
+		{WCD9XXX_A_RX_HPH_L_GAIN, 0xff, 0x2C},
+		{WCD9XXX_A_CDC_RX2_B6_CTL, 0xff, 0x81},
+		{WCD9XXX_A_CDC_CLK_RX_B1_CTL, 0x02, 0x02},
+		{WCD9XXX_A_RX_HPH_R_GAIN, 0xff, 0x2C},
+		{WCD9XXX_A_NCP_CLK, 0xff, 0xFC},
+		{WCD9XXX_A_BUCK_CTRL_CCL_3, 0xff, 0x60},
+		{WCD9XXX_A_RX_COM_BIAS, 0xff, 0x80},
+		{WCD9XXX_A_BUCK_MODE_3, 0xff, 0xC6},
+		{WCD9XXX_A_BUCK_MODE_4, 0xff, 0xE6},
+		{WCD9XXX_A_BUCK_MODE_5, 0xff, 0x02},
+		{WCD9XXX_A_BUCK_MODE_1, 0xff, 0xA1},
+		{WCD9XXX_A_NCP_EN, 0xff, 0xFF},
+		{WCD9XXX_A_BUCK_MODE_5, 0xff, 0x7B},
+		{WCD9XXX_A_CDC_CLSH_B1_CTL, 0xff, 0xE6},
+		{WCD9XXX_A_RX_HPH_L_DAC_CTL, 0xff, 0xC0},
+		{WCD9XXX_A_RX_HPH_R_DAC_CTL, 0xff, 0xC0},
+	};
+
+	for (i = 0; i < ARRAY_SIZE(reg_set_paon); i++)
+		wcd9xxx_soc_update_bits_push(codec, lh,
+					     reg_set_paon[i].reg,
+					     reg_set_paon[i].mask,
+					     reg_set_paon[i].val);
+	pr_debug("%s: PAs are prepared\n", __func__);
+
+	return 0;
+}
+
+static void wcd9xxx_restore_registers(struct wcd9xxx_mbhc *mbhc,
+				      struct list_head *lh)
+{
+	struct wcd9xxx_register_save_node *node, *nodetmp;
+	struct snd_soc_codec *codec = mbhc->codec;
+
+	list_for_each_entry_safe(node, nodetmp, lh, lh) {
+		snd_soc_write(codec, node->reg, node->value);
+		list_del(&node->lh);
+		kfree(node);
+	}
+}
+
+static void wcd9xxx_unprepare_static_pa(struct wcd9xxx_mbhc *mbhc,
+					struct list_head *lh)
+{
+	wcd9xxx_restore_registers(mbhc, lh);
+}
+
+static int wcd9xxx_enable_static_pa(struct wcd9xxx_mbhc *mbhc, bool enable)
+{
+	struct snd_soc_codec *codec = mbhc->codec;
+	const int wg_time = snd_soc_read(codec, WCD9XXX_A_RX_HPH_CNP_WG_TIME) *
+			    WCD9XXX_WG_TIME_FACTOR_US;
+
+	snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_CNP_EN, 0x30,
+			    enable ? 0x30 : 0x0);
+	/* Wait for wave gen time to avoid pop noise */
+	usleep_range(wg_time, wg_time + WCD9XXX_USLEEP_RANGE_MARGIN_US);
+	pr_debug("%s: PAs are %s as static mode (wg_time %d)\n", __func__,
+		 enable ? "enabled" : "disabled", wg_time);
+	return 0;
+}
+
+static int wcd9xxx_detect_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl,
+				    uint32_t *zr)
+{
+	int i;
+	int ret = 0;
+	s16 l[3], r[3];
+	s16 *z[] = {
+		&l[0], &r[0], &r[1], &l[1], &l[2], &r[2],
+	};
+	LIST_HEAD(lh);
+	LIST_HEAD(lhpa);
+	struct snd_soc_codec *codec = mbhc->codec;
+	const int ramp_wait_us = 18 * 1000;
+	const int mux_wait_us = 25;
+	const int alphal = 364; /* 0.005555 * 65536 = 364.05 */
+	const int alphar = 364; /* 0.005555 * 65536 = 364.05 */
+	const int beta = 3855; /* 0.011765 * 5 * 65536 = 3855.15 */
+	const int rref = 11333; /* not scaled up */
+	const int shift = 16;
+	int64_t rl, rr = 0; /* milliohm */
+	const struct wcd9xxx_reg_mask_val reg_set_mux[] = {
+		/* Phase 1 */
+		/* Set MBHC_MUX for HPHL without ical */
+		{WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF0},
+		/* Set MBHC_MUX for HPHR without ical */
+		{WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xA0},
+		/* Set MBHC_MUX for HPHR with ical */
+		{WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF8},
+		/* Set MBHC_MUX for HPHL with ical */
+		{WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xC0},
+
+		/* Phase 2 */
+		{WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF0},
+		/* Set MBHC_MUX for HPHR without ical and wait for 25us */
+		{WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xA0},
+	};
+
+	pr_debug("%s: enter\n", __func__);
+	WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr);
+
+	/*
+	 * Impedance detection is an intrusive function as it mutes RX paths,
+	 * enable PAs and etc.  Therefore codec drvier including ALSA
+	 * shouldn't read and write hardware registers during detection.
+	 */
+	mutex_lock(&codec->mutex);
+
+	WCD9XXX_BG_CLK_LOCK(mbhc->resmgr);
+	wcd9xxx_resmgr_get_bandgap(mbhc->resmgr, WCD9XXX_BANDGAP_MBHC_MODE);
+	wcd9xxx_resmgr_get_clk_block(mbhc->resmgr, WCD9XXX_CLK_RCO);
+	WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr);
+
+	pr_debug("%s: Setting impedance detection\n", __func__);
+	wcd9xxx_prepare_static_pa(mbhc, &lhpa);
+	wcd9xxx_enable_static_pa(mbhc, true);
+
+	/*
+	 * save old value of registers and write the new value to restore old
+	 * value back, WCD9XXX_A_CDC_PA_RAMP_B{1,2,3,4}_CTL registers don't
+	 * need to be restored as those are solely used by impedance detection.
+	 */
+#define __w(reg, mask, value)						  \
+	do {								  \
+		ret = wcd9xxx_soc_update_bits_push(codec, &lh, reg, mask, \
+						   value);		  \
+		if (ret < 0)						  \
+			return ret;					  \
+	} while (0)
+
+	/* Reset the PA Ramp */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B1_CTL, 0x1C);
+	/*
+	 * Connect the PA Ramp to PA chain and release reset with keep it
+	 * connected.
+	 */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B1_CTL, 0x1F);
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B1_CTL, 0x03);
+	/* Program the PA Ramp to FS_48K, L shift 1 and sample num to 24 */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B3_CTL, 0x3 << 4 | 0x6);
+	/* 0x56 for 10mv.  0xC0 is for 50mv */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B4_CTL, 0xC0);
+	/* Enable MBHC MUX, Set MUX current to 37.5uA and ADC7 */
+	__w(WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xC0);
+	__w(WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF0);
+	__w(WCD9XXX_A_TX_7_MBHC_TEST_CTL, 0xFF, 0x78);
+	__w(WCD9XXX_A_TX_7_MBHC_EN, 0xFF, 0x8C);
+	/* Change NSA and NAVG */
+	__w(WCD9XXX_A_CDC_MBHC_TIMER_B4_CTL, 0x4 << 4, 0x4 << 4);
+	__w(WCD9XXX_A_CDC_MBHC_TIMER_B5_CTL, 0xFF, 0x10);
+	/* Reset MBHC and set it up for STA */
+	__w(WCD9XXX_A_CDC_MBHC_CLK_CTL, 0xFF, 0x0A);
+	__w(WCD9XXX_A_CDC_MBHC_EN_CTL, 0xFF, 0x02);
+	__w(WCD9XXX_A_CDC_MBHC_CLK_CTL, 0xFF, 0x02);
+
+	/* Set HPH_MBHC for zdet */
+	__w(WCD9XXX_A_MBHC_HPH, 0xB3, 0x80);
+
+	pr_debug("%s: Performing impedance detection\n", __func__);
+	/* Phase 1 */
+	for (i = 0; i < ARRAY_SIZE(reg_set_mux) - 2; i++) {
+		__w(reg_set_mux[i].reg, reg_set_mux[i].mask,
+		    reg_set_mux[i].val);
+		/* 25us is required after mux change to settle down */
+		usleep_range(mux_wait_us,
+			     mux_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US);
+		*(z[i]) = __wcd9xxx_codec_sta_dce(mbhc, 0, false, false);
+	}
+
+	/* Phase 2 */
+	/* Start the PA ramp on HPH L and R */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B2_CTL, 0x05);
+	/* Ramp generator takes ~17ms */
+	usleep_range(ramp_wait_us,
+		     ramp_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US);
+
+	/* Disable Ical */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B2_CTL, 0x00);
+	/* Ramp generator takes ~17ms */
+	usleep_range(ramp_wait_us,
+		     ramp_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US);
+	for (; i < ARRAY_SIZE(reg_set_mux); i++) {
+		__w(reg_set_mux[i].reg, reg_set_mux[i].mask,
+		    reg_set_mux[i].val);
+		/* 25us is required after mux change to settle down */
+		usleep_range(mux_wait_us,
+			     mux_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US);
+		*(z[i]) = __wcd9xxx_codec_sta_dce(mbhc, 0, false, false);
+	}
+
+	/* Ramp HPH L & R back to Zero */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B2_CTL, 0x0A);
+	/* Ramp generator takes ~17ms */
+	usleep_range(ramp_wait_us,
+		     ramp_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US);
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B2_CTL, 0x00);
+#undef __w
+
+	/* Clean up starts */
+	/* Turn off PA ramp generator */
+	snd_soc_write(codec, WCD9XXX_A_CDC_PA_RAMP_B1_CTL, 0x0);
+	wcd9xxx_enable_static_pa(mbhc, false);
+	wcd9xxx_restore_registers(mbhc, &lh);
+	wcd9xxx_unprepare_static_pa(mbhc, &lhpa);
+
+	mutex_unlock(&codec->mutex);
+
+	WCD9XXX_BG_CLK_LOCK(mbhc->resmgr);
+	wcd9xxx_resmgr_put_bandgap(mbhc->resmgr, WCD9XXX_BANDGAP_MBHC_MODE);
+	wcd9xxx_resmgr_put_clk_block(mbhc->resmgr, WCD9XXX_CLK_RCO);
+	WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr);
+
+	rl = (int)(l[0] - l[1]) * 1000 / (l[0] - l[2]);
+	rl = rl * rref * alphal;
+	rl = rl >> shift;
+	rl = rl * beta;
+	rl = rl >> shift;
+	*zl = rl;
+
+	rr = (int)(r[0] - r[1]) * 1000 / (r[0] - r[2]);
+	rr = rr * rref  * alphar;
+	rr = rr >> shift;
+	rr = rr * beta;
+	rr = rr >> shift;
+	*zr = rr;
+
+	pr_debug("%s: L0: 0x%x(%d), L1: 0x%x(%d), L2: 0x%x(%d), rl: %lld\n",
+		 __func__,
+		 l[0] & 0xffff, l[0], l[1] & 0xffff, l[1], l[2] & 0xffff, l[2],
+		 rl);
+	pr_debug("%s: R0: 0x%x(%d), R1: 0x%x(%d), R2: 0x%x(%d), rr: %lld\n",
+		 __func__,
+		 r[0] & 0xffff, r[0], r[1] & 0xffff, r[1], r[2] & 0xffff, r[2],
+		 rr);
+	pr_debug("%s: RL %d milliohm, RR %d milliohm\n", __func__, *zl, *zr);
+	pr_debug("%s: Impedance detection completed\n", __func__);
+
+	return ret;
+}
+
+int wcd9xxx_mbhc_get_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl,
+			       uint32_t *zr)
+{
+	WCD9XXX_BCL_LOCK(mbhc->resmgr);
+	*zl = mbhc->zl;
+	*zr = mbhc->zr;
+	WCD9XXX_BCL_UNLOCK(mbhc->resmgr);
+
+	if (*zl && *zr)
+		return 0;
+	else
+		return -EINVAL;
+}
+
 /*
  * wcd9xxx_mbhc_init : initialize MBHC internal structures.
  *
diff --git a/sound/soc/codecs/wcd9xxx-mbhc.h b/sound/soc/codecs/wcd9xxx-mbhc.h
index 02ecced..1f6502f 100644
--- a/sound/soc/codecs/wcd9xxx-mbhc.h
+++ b/sound/soc/codecs/wcd9xxx-mbhc.h
@@ -291,6 +291,9 @@
 	bool micbias_enable;
 	int (*micbias_enable_cb) (struct snd_soc_codec*,  bool);
 
+	/* impedance of hphl and hphr */
+	uint32_t zl, zr;
+
 	u32 rco_clk_rate;
 
 #ifdef CONFIG_DEBUG_FS
@@ -365,4 +368,6 @@
 void *wcd9xxx_mbhc_cal_btn_det_mp(
 			    const struct wcd9xxx_mbhc_btn_detect_cfg *btn_det,
 			    const enum wcd9xxx_mbhc_btn_det_mem mem);
+int wcd9xxx_mbhc_get_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl,
+			       uint32_t *zr);
 #endif /* __WCD9XXX_MBHC_H__ */