OMAP2/3 GPTIMER: clear TOCR register after timer overflow interrupt and during load From: Paul Walmsley There appears to be a hardware bug in the 1-ms tick generation section of the GPTIMER blocks on some OMAP3530 chips. TOCR is sometimes incremented when a a timer overflow event occurs and TPIR = TPNR = TOWR = TOCR = 0, in contradiction of 34xx TRM 16.2.4.2.1. When TOCR is incremented under these conditions, the timer will not generate any further overflow interrupts. (The kernel currently relies on overflow interrupts to generate ticks and drive the scheduler.) This patch works around the bug by clearing TOCR in the GPTIMER overflow ISR and in the timer load functions. The precise sequence of hardware events needed to reproduce this bug is still unknown. Without this patch, the bug is consistently observable on several BeagleBoards (including mine and Koen's) within a few minutes of boot. It's not clear whether this bug is present on all OMAP3 revisions, or whether it is simply specific to certain OMAP3530ES2.2 lots. This patch fixes the "serial hangs" reported by some BeagleBoard users. During these hangs, characters are still received from the serial port, so magic SysRq will still work; but characters are never delivered on to the underlying line discipline. This since tty_flip_buffer_push() uses schedule_delayed_work() to defer passing the input buffer to the line discipline, but the delayed work function is never called since no timer tick ever arrives. The patch should also fix some other sporadic boot hangs reported by BeagleBoard users that are due to timer interrupt non-delivery. --- arch/arm/mach-omap2/timer-gp.c | 1 + arch/arm/plat-omap/dmtimer.c | 8 ++++++++ include/asm-arm/arch-omap/dmtimer.h | 2 ++ 3 files changed, 11 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-omap2/timer-gp.c b/arch/arm/mach-omap2/timer-gp.c index edc0c9e..869fe14 100644 --- a/arch/arm/mach-omap2/timer-gp.c +++ b/arch/arm/mach-omap2/timer-gp.c @@ -44,6 +44,7 @@ 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_clear_ovf_cnt(gpt); omap_dm_timer_write_status(gpt, OMAP_TIMER_INT_OVERFLOW); evt->event_handler(evt); diff --git a/arch/arm/plat-omap/dmtimer.c b/arch/arm/plat-omap/dmtimer.c index f22506a..2cbb4cc 100644 --- a/arch/arm/plat-omap/dmtimer.c +++ b/arch/arm/plat-omap/dmtimer.c @@ -543,6 +543,8 @@ void omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload, while (readl(timer->io_base + (OMAP_TIMER_WRITE_PEND_REG & 0xff))) cpu_relax(); + omap_dm_timer_write_reg(timer, OMAP_TIMER_TICK_INT_MASK_SET_REG, 0); + omap_dm_timer_write_reg(timer, OMAP_TIMER_TRIGGER_REG, 0); } @@ -561,6 +563,7 @@ void omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload, omap_dm_timer_write_reg(timer, OMAP_TIMER_COUNTER_REG, load); omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG, load); + omap_dm_timer_write_reg(timer, OMAP_TIMER_TICK_INT_MASK_SET_REG, 0); omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l); } @@ -614,6 +617,11 @@ void omap_dm_timer_set_int_enable(struct omap_dm_timer *timer, omap_dm_timer_write_reg(timer, OMAP_TIMER_WAKEUP_EN_REG, value); } +void omap_dm_timer_clear_ovf_cnt(struct omap_dm_timer *timer) +{ + omap_dm_timer_write_reg(timer, OMAP_TIMER_TICK_INT_MASK_SET_REG, 0); +} + unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer) { unsigned int l; diff --git a/include/asm-arm/arch-omap/dmtimer.h b/include/asm-arm/arch-omap/dmtimer.h index 02b29e8..7b1138b 100644 --- a/include/asm-arm/arch-omap/dmtimer.h +++ b/include/asm-arm/arch-omap/dmtimer.h @@ -73,6 +73,8 @@ void omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, int prescaler); void omap_dm_timer_set_int_enable(struct omap_dm_timer *timer, unsigned int value); +void omap_dm_timer_clear_ovf_cnt(struct omap_dm_timer *timer); + unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer); void omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value); unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer);