mfd: wcd9xxx: Fix interrupt request flooding

The wcd9320 and wcd9306 codec have additional interrupt bits which are not
currently masked.  Therefore it's possible those interrupts assert and
never be cleared by driver.  Mask additional interrupt mask bits in order
to prevent irq flooding.

Additionally enhance irq handler to clear unhandled nested irq so even
buggy driver cannot make irq flooding.

CRs-fixed: 478185
Change-Id: I6d7c0fad8baceefd0fb8c2ee0498ca09b56a56a8
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/drivers/mfd/wcd9xxx-irq.c b/drivers/mfd/wcd9xxx-irq.c
index 356aecb..111131a 100644
--- a/drivers/mfd/wcd9xxx-irq.c
+++ b/drivers/mfd/wcd9xxx-irq.c
@@ -25,6 +25,7 @@
 #include <linux/of.h>
 #include <linux/of_irq.h>
 #include <linux/slab.h>
+#include <linux/ratelimit.h>
 #include <mach/cpuidle.h>
 
 #define BYTE_BIT_MASK(nr)		(1UL << ((nr) % BITS_PER_BYTE))
@@ -226,9 +227,11 @@
 {
 	int ret;
 	int i;
+	char linebuf[128];
 	struct wcd9xxx *wcd9xxx = data;
 	int num_irq_regs = wcd9xxx_num_irq_regs(wcd9xxx);
-	u8 status[num_irq_regs];
+	u8 status[num_irq_regs], status1[num_irq_regs];
+	static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 1);
 
 	if (unlikely(wcd9xxx_lock_sleep(wcd9xxx) == false)) {
 		dev_err(wcd9xxx->dev, "Failed to hold suspend\n");
@@ -250,12 +253,17 @@
 	for (i = 0; i < num_irq_regs; i++)
 		status[i] &= ~wcd9xxx->irq_masks_cur[i];
 
+	memcpy(status1, status, sizeof(status1));
+
 	/* Find out which interrupt was triggered and call that interrupt's
 	 * handler function
 	 */
 	if (status[BIT_BYTE(WCD9XXX_IRQ_SLIMBUS)] &
-	    BYTE_BIT_MASK(WCD9XXX_IRQ_SLIMBUS))
+	    BYTE_BIT_MASK(WCD9XXX_IRQ_SLIMBUS)) {
 		wcd9xxx_irq_dispatch(wcd9xxx, WCD9XXX_IRQ_SLIMBUS);
+		status1[BIT_BYTE(WCD9XXX_IRQ_SLIMBUS)] &=
+		    ~BYTE_BIT_MASK(WCD9XXX_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
@@ -264,13 +272,41 @@
 	 * machine's order */
 	for (i = WCD9XXX_IRQ_MBHC_INSERTION;
 	     i >= WCD9XXX_IRQ_MBHC_REMOVAL; i--) {
-		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i))
+		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i)) {
 			wcd9xxx_irq_dispatch(wcd9xxx, i);
+			status1[BIT_BYTE(i)] &= ~BYTE_BIT_MASK(i);
+		}
 	}
 	for (i = WCD9XXX_IRQ_BG_PRECHARGE; i < wcd9xxx->codec_type->num_irqs;
 	     i++) {
-		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i))
+		if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i)) {
 			wcd9xxx_irq_dispatch(wcd9xxx, i);
+			status1[BIT_BYTE(i)] &= ~BYTE_BIT_MASK(i);
+		}
+	}
+
+	/*
+	 * As a failsafe if unhandled irq is found, clear it to prevent
+	 * interrupt storm.
+	 * Note that we can say there was an unhandled irq only when no irq
+	 * handled by nested irq handler since Taiko supports qdsp as irqs'
+	 * destination for few irqs.  Therefore driver shouldn't clear pending
+	 * irqs when few handled while few others not.
+	 */
+	if (unlikely(!memcmp(status, status1, sizeof(status)))) {
+		if (__ratelimit(&ratelimit)) {
+			pr_warn("%s: Unhandled irq found\n", __func__);
+			hex_dump_to_buffer(status, sizeof(status), 16, 1,
+					   linebuf, sizeof(linebuf), false);
+			pr_warn("%s: status0 : %s\n", __func__, linebuf);
+			hex_dump_to_buffer(status1, sizeof(status1), 16, 1,
+					   linebuf, sizeof(linebuf), false);
+			pr_warn("%s: status1 : %s\n", __func__, linebuf);
+		}
+
+		memset(status, 0, num_irq_regs);
+		wcd9xxx_bulk_write(wcd9xxx, WCD9XXX_A_INTR_STATUS0,
+				   num_irq_regs, status);
 	}
 	wcd9xxx_unlock_sleep(wcd9xxx);
 
diff --git a/include/linux/mfd/wcd9xxx/core.h b/include/linux/mfd/wcd9xxx/core.h
index 3a9b1b9..0d1f49f 100644
--- a/include/linux/mfd/wcd9xxx/core.h
+++ b/include/linux/mfd/wcd9xxx/core.h
@@ -77,7 +77,8 @@
 	WCD9XXX_IRQ_HPH_L_PA_STARTUP,
 	WCD9XXX_IRQ_HPH_R_PA_STARTUP,
 	WCD9XXX_IRQ_EAR_PA_STARTUP,
-	WCD9XXX_IRQ_RESERVED_0,
+	WCD9310_NUM_IRQS,
+	WCD9XXX_IRQ_RESERVED_0 = WCD9310_NUM_IRQS,
 	WCD9XXX_IRQ_RESERVED_1,
 	/* INTR_REG 3 */
 	WCD9XXX_IRQ_MAD_AUDIO,
@@ -85,12 +86,14 @@
 	WCD9XXX_IRQ_MAD_ULTRASOUND,
 	WCD9XXX_IRQ_SPEAKER_CLIPPING,
 	WCD9XXX_IRQ_MBHC_JACK_SWITCH,
+	WCD9XXX_IRQ_VBAT_MONITOR_ATTACK,
+	WCD9XXX_IRQ_VBAT_MONITOR_RELEASE,
 	WCD9XXX_NUM_IRQS,
 };
 
 enum {
-	TABLA_NUM_IRQS = WCD9XXX_NUM_IRQS,
-	SITAR_NUM_IRQS = WCD9XXX_NUM_IRQS,
+	TABLA_NUM_IRQS = WCD9310_NUM_IRQS,
+	SITAR_NUM_IRQS = WCD9310_NUM_IRQS,
 	TAIKO_NUM_IRQS = WCD9XXX_NUM_IRQS,
 	TAPAN_NUM_IRQS = WCD9XXX_NUM_IRQS,
 };