Merge changes Iccd3819c,If5856ead into msm-3.0

* changes:
  ASoC: msm8960: add headphone over current protection type
  ASoC: wcd9310: add headphone over current protection feature
diff --git a/include/linux/mfd/wcd9310/pdata.h b/include/linux/mfd/wcd9310/pdata.h
index 9ca6a8c..af801f0 100644
--- a/include/linux/mfd/wcd9310/pdata.h
+++ b/include/linux/mfd/wcd9310/pdata.h
@@ -27,6 +27,30 @@
 
 #define MAX_AMIC_CHANNEL 7
 
+#define TABLA_OCP_300_MA 0x0
+#define TABLA_OCP_350_MA 0x2
+#define TABLA_OCP_365_MA 0x3
+#define TABLA_OCP_150_MA 0x4
+#define TABLA_OCP_190_MA 0x6
+#define TABLA_OCP_220_MA 0x7
+
+#define TABLA_DCYCLE_255  0x0
+#define TABLA_DCYCLE_511  0x1
+#define TABLA_DCYCLE_767  0x2
+#define TABLA_DCYCLE_1023 0x3
+#define TABLA_DCYCLE_1279 0x4
+#define TABLA_DCYCLE_1535 0x5
+#define TABLA_DCYCLE_1791 0x6
+#define TABLA_DCYCLE_2047 0x7
+#define TABLA_DCYCLE_2303 0x8
+#define TABLA_DCYCLE_2559 0x9
+#define TABLA_DCYCLE_2815 0xA
+#define TABLA_DCYCLE_3071 0xB
+#define TABLA_DCYCLE_3327 0xC
+#define TABLA_DCYCLE_3583 0xD
+#define TABLA_DCYCLE_3839 0xE
+#define TABLA_DCYCLE_4095 0xF
+
 struct tabla_amic {
 	/*legacy mode, txfe_enable and txfe_buff take 7 input
 	 * each bit represent the channel / TXFE number
@@ -62,6 +86,14 @@
 	u8 bias4_cfilt_sel;
 };
 
+struct tabla_ocp_setting {
+	unsigned int	use_pdata:1; /* 0 - use sys default as recommended */
+	unsigned int	num_attempts:4; /* up to 15 attempts */
+	unsigned int	run_time:4; /* in duty cycle */
+	unsigned int	wait_time:4; /* in duty cycle */
+	unsigned int	hph_ocp_limit:3; /* Headphone OCP current limit */
+};
+
 struct tabla_pdata {
 	int irq;
 	int irq_base;
@@ -70,6 +102,7 @@
 	struct tabla_amic amic_settings;
 	struct slim_device slimbus_slave_device;
 	struct tabla_micbias_setting micbias;
+	struct tabla_ocp_setting ocp;
 };
 
 #endif
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index ad8190c..7f7bdc9 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -37,6 +37,8 @@
 #define TABLA_RX_DAI_ID 1
 #define TABLA_TX_DAI_ID 2
 
+#define TABLA_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR)
+
 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);
@@ -84,6 +86,14 @@
 	struct mbhc_micbias_regs mbhc_bias_regs;
 	u8 cfilt_k_value;
 	bool mbhc_micbias_switched;
+
+	u32 hph_status; /* track headhpone status */
+	/* define separate work for left and right headphone OCP to avoid
+	 * additional checking on which OCP event to report so no locking
+	 * to ensure synchronization is required
+	 */
+	struct work_struct hphlocp_work; /* reporting left hph ocp off */
+	struct work_struct hphrocp_work; /* reporting right hph ocp off */
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1285,6 +1295,42 @@
 	return 0;
 }
 
+static void hphocp_off_report(struct tabla_priv *tabla,
+	u32 jack_status, int irq)
+{
+	struct snd_soc_codec *codec;
+
+	if (tabla) {
+		pr_info("%s: clear ocp status %x\n", __func__, jack_status);
+		codec = tabla->codec;
+		tabla->hph_status &= ~jack_status;
+		if (tabla->headset_jack)
+			snd_soc_jack_report(tabla->headset_jack,
+			tabla->hph_status, TABLA_JACK_MASK);
+		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
+		0x00);
+		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
+		0x10);
+		tabla_enable_irq(codec->control_data, irq);
+	} else {
+		pr_err("%s: Bad tabla private data\n", __func__);
+	}
+}
+
+static void hphlocp_off_report(struct work_struct *work)
+{
+	struct tabla_priv *tabla = container_of(work, struct tabla_priv,
+		hphlocp_work);
+	hphocp_off_report(tabla, SND_JACK_OC_HPHL, TABLA_IRQ_HPH_PA_OCPL_FAULT);
+}
+
+static void hphrocp_off_report(struct work_struct *work)
+{
+	struct tabla_priv *tabla = container_of(work, struct tabla_priv,
+		hphrocp_work);
+	hphocp_off_report(tabla, SND_JACK_OC_HPHR, TABLA_IRQ_HPH_PA_OCPR_FAULT);
+}
+
 static int tabla_hph_pa_event(struct snd_soc_dapm_widget *w,
 	struct snd_kcontrol *kcontrol, int event)
 {
@@ -1305,7 +1351,17 @@
 		break;
 
 	case SND_SOC_DAPM_POST_PMD:
-
+		/* schedule work is required because at the time HPH PA DAPM
+		 * event callback is called by DAPM framework, CODEC dapm mutex
+		 * would have been locked while snd_soc_jack_report also
+		 * attempts to acquire same lock.
+		 */
+		if ((tabla->hph_status & SND_JACK_OC_HPHL) &&
+			strnstr(w->name, "HPHL", 4))
+			schedule_work(&tabla->hphlocp_work);
+		else if ((tabla->hph_status & SND_JACK_OC_HPHR) &&
+			strnstr(w->name, "HPHR", 4))
+			schedule_work(&tabla->hphrocp_work);
 		if (tabla->mbhc_micbias_switched)
 			tabla_codec_switch_micbias(codec, 0);
 
@@ -2495,6 +2551,8 @@
 	struct tabla_mbhc_calibration *calibration)
 {
 	struct tabla_priv *tabla;
+	int rc;
+
 	if (!codec || !calibration) {
 		pr_err("Error: no codec or calibration\n");
 		return -EINVAL;
@@ -2506,7 +2564,20 @@
 	tabla_get_mbhc_micbias_regs(codec, &tabla->mbhc_bias_regs);
 
 	INIT_DELAYED_WORK(&tabla->btn0_dwork, btn0_lpress_fn);
-	return tabla_codec_enable_hs_detect(codec, 1);
+	INIT_WORK(&tabla->hphlocp_work, hphlocp_off_report);
+	INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report);
+	rc =  tabla_codec_enable_hs_detect(codec, 1);
+
+	if (!IS_ERR_VALUE(rc)) {
+		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
+			0x10);
+		tabla_enable_irq(codec->control_data,
+			TABLA_IRQ_HPH_PA_OCPL_FAULT);
+		tabla_enable_irq(codec->control_data,
+			TABLA_IRQ_HPH_PA_OCPR_FAULT);
+	}
+
+	return rc;
 }
 EXPORT_SYMBOL_GPL(tabla_hs_detect);
 
@@ -2636,6 +2707,52 @@
 	tabla->mbhc_polling_active = false;
 }
 
+static irqreturn_t tabla_hphl_ocp_irq(int irq, void *data)
+{
+	struct tabla_priv *tabla = data;
+	struct snd_soc_codec *codec;
+
+	pr_info("%s: received HPHL OCP irq\n", __func__);
+
+	if (tabla) {
+		codec = tabla->codec;
+		tabla_disable_irq(codec->control_data,
+			TABLA_IRQ_HPH_PA_OCPL_FAULT);
+		tabla->hph_status |= SND_JACK_OC_HPHL;
+		if (tabla->headset_jack) {
+			snd_soc_jack_report(tabla->headset_jack,
+				tabla->hph_status, TABLA_JACK_MASK);
+		}
+	} else {
+		pr_err("%s: Bad tabla private data\n", __func__);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t tabla_hphr_ocp_irq(int irq, void *data)
+{
+	struct tabla_priv *tabla = data;
+	struct snd_soc_codec *codec;
+
+	pr_info("%s: received HPHR OCP irq\n", __func__);
+
+	if (tabla) {
+		codec = tabla->codec;
+		tabla_disable_irq(codec->control_data,
+			TABLA_IRQ_HPH_PA_OCPR_FAULT);
+		tabla->hph_status |= SND_JACK_OC_HPHR;
+		if (tabla->headset_jack) {
+			snd_soc_jack_report(tabla->headset_jack,
+				tabla->hph_status, TABLA_JACK_MASK);
+		}
+	} else {
+		pr_err("%s: Bad tabla private data\n", __func__);
+	}
+
+	return IRQ_HANDLED;
+}
+
 static irqreturn_t tabla_hs_insert_irq(int irq, void *data)
 {
 	struct tabla_priv *priv = data;
@@ -2683,10 +2800,11 @@
 		 */
 		if (priv->mbhc_micbias_switched)
 			tabla_codec_switch_micbias(codec, 0);
+		priv->hph_status &= ~SND_JACK_HEADSET;
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting removal\n", __func__);
-			snd_soc_jack_report(priv->headset_jack, 0,
-				SND_JACK_HEADSET);
+			snd_soc_jack_report(priv->headset_jack,
+				priv->hph_status, TABLA_JACK_MASK);
 		}
 		tabla_codec_shutdown_hs_removal_detect(codec);
 		tabla_codec_enable_hs_detect(codec, 1);
@@ -2702,12 +2820,12 @@
 	} else if (mic_voltage < threshold_no_mic) {
 		pr_debug("%s: Headphone Detected, mic_voltage = %x\n",
 			__func__, mic_voltage);
-
+		priv->hph_status |= SND_JACK_HEADPHONE;
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting insertion %d\n", __func__,
 				SND_JACK_HEADPHONE);
 			snd_soc_jack_report(priv->headset_jack,
-				SND_JACK_HEADPHONE, SND_JACK_HEADSET);
+				priv->hph_status, TABLA_JACK_MASK);
 		}
 		tabla_codec_shutdown_hs_polling(codec);
 		tabla_codec_enable_hs_detect(codec, 0);
@@ -2715,11 +2833,12 @@
 	} else {
 		pr_debug("%s: Headset detected, mic_voltage = %x\n",
 			__func__, mic_voltage);
+		priv->hph_status |= SND_JACK_HEADSET;
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting insertion %d\n", __func__,
 				SND_JACK_HEADSET);
 			snd_soc_jack_report(priv->headset_jack,
-				SND_JACK_HEADSET, SND_JACK_HEADSET);
+				priv->hph_status,  TABLA_JACK_MASK);
 		}
 		tabla_codec_start_hs_polling(codec);
 	}
@@ -2754,10 +2873,11 @@
 		 */
 		if (priv->mbhc_micbias_switched)
 			tabla_codec_switch_micbias(codec, 0);
+		priv->hph_status &= ~SND_JACK_HEADSET;
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting removal\n", __func__);
 			snd_soc_jack_report(priv->headset_jack, 0,
-				SND_JACK_HEADSET);
+				 TABLA_JACK_MASK);
 		}
 		tabla_codec_shutdown_hs_polling(codec);
 
@@ -2888,6 +3008,21 @@
 		snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN,
 			0x13, value);
 	}
+
+	if (pdata->ocp.use_pdata) {
+		/* not defined in CODEC specification */
+		if (pdata->ocp.hph_ocp_limit == 1 ||
+			pdata->ocp.hph_ocp_limit == 5) {
+			rc = -EINVAL;
+			goto done;
+		}
+		snd_soc_update_bits(codec, TABLA_A_RX_COM_OCP_CTL,
+			0x0F, pdata->ocp.num_attempts);
+		snd_soc_write(codec, TABLA_A_RX_COM_OCP_COUNT,
+			((pdata->ocp.run_time << 4) | pdata->ocp.wait_time));
+		snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL,
+			0xE0, (pdata->ocp.hph_ocp_limit << 5));
+	}
 done:
 	return rc;
 }
@@ -2956,6 +3091,8 @@
 }
 
 static const struct tabla_reg_mask_val tabla_codec_reg_init_val[] = {
+	/* Initialize current threshold to 350MA */
+	{TABLA_A_RX_HPH_OCP_CTL, 0xE0, 0x60},
 
 	{TABLA_A_QFUSE_CTL, 0xFF, 0x03},
 
@@ -3118,12 +3255,34 @@
 		tabla_interface_reg_write(codec->control_data,
 			TABLA_SLIM_PGD_PORT_INT_EN0 + i, 0xFF);
 
+	ret = tabla_request_irq(codec->control_data,
+		TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla_hphl_ocp_irq,
+		"HPH_L OCP detect", tabla);
+	if (ret) {
+		pr_err("%s: Failed to request irq %d\n", __func__,
+			TABLA_IRQ_HPH_PA_OCPL_FAULT);
+		goto err_hphl_ocp_irq;
+	}
+
+	ret = tabla_request_irq(codec->control_data,
+		TABLA_IRQ_HPH_PA_OCPR_FAULT, tabla_hphr_ocp_irq,
+		"HPH_R OCP detect", tabla);
+	if (ret) {
+		pr_err("%s: Failed to request irq %d\n", __func__,
+			TABLA_IRQ_HPH_PA_OCPR_FAULT);
+		goto err_hphr_ocp_irq;
+	}
+
 #ifdef CONFIG_DEBUG_FS
 	debug_tabla_priv = tabla;
 #endif
 
 	return ret;
 
+err_hphr_ocp_irq:
+	tabla_free_irq(codec->control_data, TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla);
+err_hphl_ocp_irq:
+	tabla_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla);
 err_slimbus_irq:
 	tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, tabla);
 err_release_irq:
diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c
index f5f5893..d129fb2 100644
--- a/sound/soc/msm/msm8960.c
+++ b/sound/soc/msm/msm8960.c
@@ -598,7 +598,8 @@
 	snd_soc_dapm_sync(dapm);
 
 	err = snd_soc_jack_new(codec, "Headset Jack",
-				SND_JACK_HEADSET, &hs_jack);
+		(SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR),
+		&hs_jack);
 	if (err) {
 		pr_err("failed to create new jack\n");
 		return err;