ASoC: wcd9310: Ensure system is awake while accessing CODEC registers

If Tabla ISRs are called when system is in suspended state,
there are chances that Tabla and underlying bus are not awake.
Fix ISRs to wait until devices are awaken and prevent system from
suspending while accessing bus is completed.

CRs-Fixed: 318992
Change-Id: Idc358dc351c16c79420d7bee15a4a6fde9f1602a
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index c087823..75864702 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -122,6 +122,11 @@
 	 */
 	struct work_struct hphlocp_work; /* reporting left hph ocp off */
 	struct work_struct hphrocp_work; /* reporting right hph ocp off */
+
+	/* pm_cnt holds number of sleep lock holders + 1
+	 * so if pm_cnt is 1 system is sleep-able. */
+	atomic_t pm_cnt;
+	wait_queue_head_t pm_wq;
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1603,6 +1608,14 @@
 	return 0;
 }
 
+static void tabla_snd_soc_jack_report(struct tabla_priv *tabla,
+				      struct snd_soc_jack *jack, int status,
+				      int mask)
+{
+	/* XXX: wake_lock_timeout()? */
+	snd_soc_jack_report(jack, status, mask);
+}
+
 static void hphocp_off_report(struct tabla_priv *tabla,
 	u32 jack_status, int irq)
 {
@@ -1613,8 +1626,9 @@
 		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);
+			tabla_snd_soc_jack_report(tabla, 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,
@@ -3070,6 +3084,24 @@
 	return 0;
 }
 
+static void tabla_lock_sleep(struct tabla_priv *tabla)
+{
+	int ret;
+	while (!(ret = wait_event_timeout(tabla->pm_wq,
+					  atomic_inc_not_zero(&tabla->pm_cnt),
+					  2 * HZ))) {
+		pr_err("%s: didn't wake up for 2000ms (%d), pm_cnt %d\n",
+		       __func__, ret, atomic_read(&tabla->pm_cnt));
+		WARN_ON_ONCE(1);
+	}
+}
+
+static void tabla_unlock_sleep(struct tabla_priv *tabla)
+{
+	atomic_dec(&tabla->pm_cnt);
+	wake_up(&tabla->pm_wq);
+}
+
 static void btn0_lpress_fn(struct work_struct *work)
 {
 	struct delayed_work *delayed_work;
@@ -3084,13 +3116,15 @@
 		if (tabla->button_jack) {
 			pr_debug("%s: Reporting long button press event\n",
 					__func__);
-			snd_soc_jack_report(tabla->button_jack, SND_JACK_BTN_0,
-					SND_JACK_BTN_0);
+			tabla_snd_soc_jack_report(tabla, tabla->button_jack,
+						  SND_JACK_BTN_0,
+						  SND_JACK_BTN_0);
 		}
 	} else {
 		pr_err("%s: Bad tabla private data\n", __func__);
 	}
 
+	tabla_unlock_sleep(tabla);
 }
 
 int tabla_hs_detect(struct snd_soc_codec *codec,
@@ -3140,6 +3174,7 @@
 
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL);
+	tabla_lock_sleep(priv);
 
 	bias_value = tabla_codec_read_dce_result(codec);
 	pr_debug("%s: button press interrupt, bias value(DCE Read)=%d\n",
@@ -3156,7 +3191,11 @@
 
 	msleep(100);
 
-	schedule_delayed_work(&priv->btn0_dwork, msecs_to_jiffies(400));
+	if (schedule_delayed_work(&priv->btn0_dwork,
+				  msecs_to_jiffies(400)) == 0) {
+		WARN(1, "Button pressed twice without release event\n");
+		tabla_unlock_sleep(priv);
+	}
 
 	return IRQ_HANDLED;
 }
@@ -3169,6 +3208,7 @@
 
 	pr_debug("%s\n", __func__);
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE);
+	tabla_lock_sleep(priv);
 
 	mic_voltage = tabla_codec_read_dce_result(codec);
 	pr_debug("%s: Microphone Voltage on release(DCE Read) = %d\n",
@@ -3182,12 +3222,15 @@
 			pr_debug("%s: Reporting long button release event\n",
 					__func__);
 			if (priv->button_jack) {
-				snd_soc_jack_report(priv->button_jack, 0,
-					SND_JACK_BTN_0);
+				tabla_snd_soc_jack_report(priv,
+							  priv->button_jack, 0,
+							  SND_JACK_BTN_0);
 			}
 
 		} else {
-
+			/* if scheduled btn0_dwork is canceled from here,
+			 * we have to unlock from here instead btn0_work */
+			tabla_unlock_sleep(priv);
 			mic_voltage =
 				tabla_codec_measure_micbias_voltage(codec, 0);
 			pr_debug("%s: Mic Voltage on release(new STA) = %d\n",
@@ -3202,10 +3245,12 @@
 					pr_debug("%s:reporting short button press and release\n",
 							__func__);
 
-					snd_soc_jack_report(priv->button_jack,
+					tabla_snd_soc_jack_report(priv,
+							      priv->button_jack,
 						SND_JACK_BTN_0, SND_JACK_BTN_0);
-					snd_soc_jack_report(priv->button_jack,
-						0, SND_JACK_BTN_0);
+					tabla_snd_soc_jack_report(priv,
+							     priv->button_jack,
+							     0, SND_JACK_BTN_0);
 				}
 			}
 		}
@@ -3214,7 +3259,7 @@
 	}
 
 	tabla_codec_start_hs_polling(codec);
-
+	tabla_unlock_sleep(priv);
 	return IRQ_HANDLED;
 }
 
@@ -3269,8 +3314,9 @@
 			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);
+			tabla_snd_soc_jack_report(tabla, tabla->headset_jack,
+						  tabla->hph_status,
+						  TABLA_JACK_MASK);
 		}
 	} else {
 		pr_err("%s: Bad tabla private data\n", __func__);
@@ -3292,8 +3338,9 @@
 			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);
+			tabla_snd_soc_jack_report(tabla, tabla->headset_jack,
+						  tabla->hph_status,
+						  TABLA_JACK_MASK);
 		}
 	} else {
 		pr_err("%s: Bad tabla private data\n", __func__);
@@ -3312,9 +3359,10 @@
 	short threshold_fake_insert = 0xFD30;
 	u8 is_removal;
 
-
 	pr_debug("%s\n", __func__);
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION);
+	tabla_lock_sleep(priv);
+
 	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);
 
@@ -3362,11 +3410,13 @@
 		priv->hph_status &= ~SND_JACK_HEADPHONE;
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting removal\n", __func__);
-			snd_soc_jack_report(priv->headset_jack,
-				priv->hph_status, TABLA_JACK_MASK);
+			tabla_snd_soc_jack_report(priv, priv->headset_jack,
+						  priv->hph_status,
+						  TABLA_JACK_MASK);
 		}
 		tabla_codec_shutdown_hs_removal_detect(codec);
 		tabla_codec_enable_hs_detect(codec, 1);
+		tabla_unlock_sleep(priv);
 		return IRQ_HANDLED;
 	}
 
@@ -3401,8 +3451,9 @@
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting insertion %d\n", __func__,
 				SND_JACK_HEADPHONE);
-			snd_soc_jack_report(priv->headset_jack,
-				priv->hph_status, TABLA_JACK_MASK);
+			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);
@@ -3414,12 +3465,14 @@
 		if (priv->headset_jack) {
 			pr_debug("%s: Reporting insertion %d\n", __func__,
 				SND_JACK_HEADSET);
-			snd_soc_jack_report(priv->headset_jack,
-				priv->hph_status,  TABLA_JACK_MASK);
+			tabla_snd_soc_jack_report(priv, priv->headset_jack,
+						  priv->hph_status,
+						  TABLA_JACK_MASK);
 		}
 		tabla_codec_start_hs_polling(codec);
 	}
 
+	tabla_unlock_sleep(priv);
 	return IRQ_HANDLED;
 }
 
@@ -3432,6 +3485,7 @@
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL);
 	tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE);
+	tabla_lock_sleep(priv);
 
 	usleep_range(priv->calibration->shutdown_plug_removal,
 		priv->calibration->shutdown_plug_removal);
@@ -3453,13 +3507,15 @@
 		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,
-				 TABLA_JACK_MASK);
+			tabla_snd_soc_jack_report(priv, priv->headset_jack, 0,
+						  TABLA_JACK_MASK);
 		}
 		tabla_codec_shutdown_hs_polling(codec);
 
 		tabla_codec_enable_hs_detect(codec, 1);
 	}
+
+	tabla_unlock_sleep(priv);
 	return IRQ_HANDLED;
 }
 
@@ -3472,6 +3528,8 @@
 	int i, j;
 	u8 val;
 
+	tabla_lock_sleep(priv);
+
 	for (i = 0; i < TABLA_SLIM_NUM_PORT_REG; i++) {
 		slimbus_value = tabla_interface_reg_read(codec->control_data,
 			TABLA_SLIM_PGD_PORT_INT_STATUS0 + i);
@@ -3489,6 +3547,7 @@
 			TABLA_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF);
 	}
 
+	tabla_unlock_sleep(priv);
 	return IRQ_HANDLED;
 }
 
@@ -3766,6 +3825,8 @@
 	tabla->codec = codec;
 	tabla->pdata = dev_get_platdata(codec->dev->parent);
 	tabla->intf_type = tabla_get_intf_type();
+	atomic_set(&tabla->pm_cnt, 1);
+	init_waitqueue_head(&tabla->pm_wq);
 
 	tabla_update_reg_defaults(codec);
 	tabla_codec_init_reg(codec);
@@ -3969,6 +4030,64 @@
 };
 #endif
 
+#ifdef CONFIG_PM
+static int tabla_suspend(struct device *dev)
+{
+	int ret = 0, cnt;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct tabla_priv *tabla = platform_get_drvdata(pdev);
+
+	cnt = atomic_read(&tabla->pm_cnt);
+	if (cnt > 0) {
+		if (wait_event_timeout(tabla->pm_wq,
+				       (atomic_cmpxchg(&tabla->pm_cnt, 1, 0)
+						== 1), 5 * HZ)) {
+			dev_dbg(dev, "system suspend pm_cnt %d\n",
+				atomic_read(&tabla->pm_cnt));
+		} else {
+			dev_err(dev, "%s timed out pm_cnt = %d\n",
+				__func__, atomic_read(&tabla->pm_cnt));
+			WARN_ON_ONCE(1);
+			ret = -EBUSY;
+		}
+	} else if (cnt == 0)
+		dev_warn(dev, "system is already in suspend, pm_cnt %d\n",
+			 atomic_read(&tabla->pm_cnt));
+	else {
+		WARN(1, "unexpected pm_cnt %d\n", cnt);
+		ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+static int tabla_resume(struct device *dev)
+{
+	int ret = 0, cnt;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct tabla_priv *tabla = platform_get_drvdata(pdev);
+
+	cnt = atomic_cmpxchg(&tabla->pm_cnt, 0, 1);
+	if (cnt == 0) {
+		dev_dbg(dev, "system resume, pm_cnt %d\n",
+			atomic_read(&tabla->pm_cnt));
+		wake_up_all(&tabla->pm_wq);
+	} else if (cnt > 0)
+		dev_warn(dev, "system is already awake, pm_cnt %d\n", cnt);
+	else {
+		WARN(1, "unexpected pm_cnt %d\n", cnt);
+		ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+static const struct dev_pm_ops tabla_pm_ops = {
+	.suspend	= tabla_suspend,
+	.resume		= tabla_resume,
+};
+#endif
+
 static int __devinit tabla_probe(struct platform_device *pdev)
 {
 	int ret = 0;
@@ -4000,6 +4119,9 @@
 	.driver = {
 		.name = "tabla_codec",
 		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &tabla_pm_ops,
+#endif
 	},
 };