1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
OMAP2/3 GPTIMER: clear TOCR register after timer overflow interrupt and during load
From: Paul Walmsley <paul@pwsan.com>
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);
|