OMAP2/3 system tick GPTIMER: use overflow interrupts to detect missing match interrupts

From: Paul Walmsley <paul@pwsan.com>

GPTIMER1 on some OMAP3 chips occasionally misses match conditions
between the timer counter and the target register value, and does not
interrupt to the MPU.  This patch adds another line of defense by
setting the timer to generate an overflow interrupt 0.5 seconds after the
timer passes the original comparison value.

If interrupts are masked for a long period of time, one would expect
both a match and an overflow interrupt to be logged.  This is considered
a normal condition.  However, if only an overflow interrupt is logged,
this is considered evidence of a hardware bug and the kernel will issue
a warning.

This workaround is unlikely to be 100% effective, since GPTIMER1 has
also been observed to lose overflow interrupts occasionally.  It is
hoped that the probability of losing both will be significantly lower
than the probability of losing either one.
---

 arch/arm/mach-omap2/timer-gp.c |   36 ++++++++++++++++++++++++++++++++----
 1 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/arch/arm/mach-omap2/timer-gp.c b/arch/arm/mach-omap2/timer-gp.c
index 51996ba..ce5c2b4 100644
--- a/arch/arm/mach-omap2/timer-gp.c
+++ b/arch/arm/mach-omap2/timer-gp.c
@@ -36,17 +36,43 @@
 #include <asm/mach/time.h>
 #include <asm/arch/dmtimer.h>
 
-#define GPTIMER_MATCH_VAL      0xffff0000
+/* 
+ * The number of timer ticks to delay will be subtracted from
+ * GPTIMER_MATCH_VAL before loading into the timer.  So GPTIMER_MATCH_VAL
+ * constrains the longest delay that can be generated with the timer.
+ * Since the current code uses overflow interrupts as protection against
+ * missed comparison interrupts, this value should also be sufficiently
+ * large such that there is not an excessively long delay between ticks
+ * if the comparison interrupt fails to arrive.  The 0xfffff800 value 
+ * below results in a half-second delay in such a case when using
+ * the 32kHz timer as source.
+ */
+#define GPTIMER_MATCH_VAL	(0xffffffff - (32768/2))
 
 static struct omap_dm_timer *gptimer;
 static struct clock_event_device clockevent_gpt;
 
+static u32 last_load;
+
 static irqreturn_t omap2_gp_timer_interrupt(int irq, void *dev_id)
 {
 	struct omap_dm_timer *gpt = (struct omap_dm_timer *)dev_id;
 	struct clock_event_device *evt = &clockevent_gpt;
-
-	omap_dm_timer_write_status(gpt, OMAP_TIMER_INT_MATCH);
+	u32 v;
+
+	v = omap_dm_timer_read_status(gpt);
+	if ((v & OMAP_TIMER_INT_OVERFLOW) && !(v & OMAP_TIMER_INT_MATCH)) {
+		/*
+		 * Should never happen.  Current belief is that this is
+		 * due to a hardware bug in the GPTIMER block on some 
+		 * OMAP3 revisions.
+		 */
+		pr_err("*** GPTIMER missed match interrupt! last load: %08x\n",
+			last_load);
+		WARN_ON(1);
+	}
+
+	omap_dm_timer_write_status(gpt, v);
 
 	evt->event_handler(evt);
 	return IRQ_HANDLED;
@@ -61,6 +87,7 @@ static struct irqaction omap2_gp_timer_irq = {
 static int omap2_gp_timer_set_next_event(unsigned long cycles,
 					 struct clock_event_device *evt)
 {
+	last_load = GPTIMER_MATCH_VAL - cycles;
 	omap_dm_timer_set_load_start(gptimer, 0, GPTIMER_MATCH_VAL - cycles);
 
 	return 0;
@@ -99,7 +126,8 @@ static void __init omap2_gp_clockevent_init(void)
 	omap_dm_timer_stop(gptimer);
 	/*	omap_dm_timer_set_load(gptimer, 0, 0);*/
 	omap_dm_timer_set_match(gptimer, 1, GPTIMER_MATCH_VAL);
-	omap_dm_timer_set_int_enable(gptimer, OMAP_TIMER_INT_MATCH);
+	omap_dm_timer_set_int_enable(gptimer, OMAP_TIMER_INT_MATCH | 
+				     OMAP_TIMER_INT_OVERFLOW);
 
 	clockevent_gpt.mult = div_sc(tick_rate, NSEC_PER_SEC,
 				     clockevent_gpt.shift);
