diff options
author | Paul Mundt <lethal@linux-sh.org> | 2007-05-09 04:33:24 -0400 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2007-05-09 04:33:24 -0400 |
commit | 57be2b484a417bffae66359b9b89e7239480b729 (patch) | |
tree | d517f5b449b4f6b629790476082a4a7c478112bb /arch/sh/kernel/timers/timer-tmu.c | |
parent | 1ce7ddd5f4cc754b6afe9eec5cee89ede75348ea (diff) |
sh: clockevent/clocksource/hrtimers/nohz TMU support.
This adds basic support for clockevents and clocksources,
presently only implemented for TMU-based systems (which
are the majority of SH-3 and SH-4 systems).
The old NO_IDLE_HZ implementation is also dropped completely,
the only users of this were on TMU-based systems anyways.
More work needs to be done to generalize the TMU handling,
in that the current implementation is rather tied to the
notion of TMU0 and TMU1 utilization.
Additionally, as more SH timers switch over to this scheme,
we'll be able to gut most of the remaining system timer
infrastructure that existed before.
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'arch/sh/kernel/timers/timer-tmu.c')
-rw-r--r-- | arch/sh/kernel/timers/timer-tmu.c | 182 |
1 files changed, 109 insertions, 73 deletions
diff --git a/arch/sh/kernel/timers/timer-tmu.c b/arch/sh/kernel/timers/timer-tmu.c index d9e3151c891e..2d997e2a5b6c 100644 --- a/arch/sh/kernel/timers/timer-tmu.c +++ b/arch/sh/kernel/timers/timer-tmu.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * arch/sh/kernel/timers/timer-tmu.c - TMU Timer Support | 2 | * arch/sh/kernel/timers/timer-tmu.c - TMU Timer Support |
3 | * | 3 | * |
4 | * Copyright (C) 2005 Paul Mundt | 4 | * Copyright (C) 2005 - 2007 Paul Mundt |
5 | * | 5 | * |
6 | * TMU handling code hacked out of arch/sh/kernel/time.c | 6 | * TMU handling code hacked out of arch/sh/kernel/time.c |
7 | * | 7 | * |
@@ -18,6 +18,7 @@ | |||
18 | #include <linux/kernel.h> | 18 | #include <linux/kernel.h> |
19 | #include <linux/interrupt.h> | 19 | #include <linux/interrupt.h> |
20 | #include <linux/seqlock.h> | 20 | #include <linux/seqlock.h> |
21 | #include <linux/clockchips.h> | ||
21 | #include <asm/timer.h> | 22 | #include <asm/timer.h> |
22 | #include <asm/rtc.h> | 23 | #include <asm/rtc.h> |
23 | #include <asm/io.h> | 24 | #include <asm/io.h> |
@@ -25,56 +26,75 @@ | |||
25 | #include <asm/clock.h> | 26 | #include <asm/clock.h> |
26 | 27 | ||
27 | #define TMU_TOCR_INIT 0x00 | 28 | #define TMU_TOCR_INIT 0x00 |
28 | #define TMU0_TCR_INIT 0x0020 | 29 | #define TMU_TCR_INIT 0x0020 |
29 | #define TMU_TSTR_INIT 1 | ||
30 | 30 | ||
31 | #define TMU0_TCR_CALIB 0x0000 | 31 | static int tmu_timer_start(void) |
32 | { | ||
33 | ctrl_outb(ctrl_inb(TMU_TSTR) | 0x3, TMU_TSTR); | ||
34 | return 0; | ||
35 | } | ||
32 | 36 | ||
33 | static unsigned long tmu_timer_get_offset(void) | 37 | static void tmu0_timer_set_interval(unsigned long interval, unsigned int reload) |
34 | { | 38 | { |
35 | int count; | 39 | ctrl_outl(interval, TMU0_TCNT); |
36 | static int count_p = 0x7fffffff; /* for the first call after boot */ | ||
37 | static unsigned long jiffies_p = 0; | ||
38 | 40 | ||
39 | /* | 41 | /* |
40 | * cache volatile jiffies temporarily; we have IRQs turned off. | 42 | * TCNT reloads from TCOR on underflow, clear it if we don't |
43 | * intend to auto-reload | ||
41 | */ | 44 | */ |
42 | unsigned long jiffies_t; | 45 | if (reload) |
46 | ctrl_outl(interval, TMU0_TCOR); | ||
47 | else | ||
48 | ctrl_outl(0, TMU0_TCOR); | ||
43 | 49 | ||
44 | /* timer count may underflow right here */ | 50 | tmu_timer_start(); |
45 | count = ctrl_inl(TMU0_TCNT); /* read the latched count */ | 51 | } |
46 | 52 | ||
47 | jiffies_t = jiffies; | 53 | static int tmu_timer_stop(void) |
54 | { | ||
55 | ctrl_outb(ctrl_inb(TMU_TSTR) & ~0x3, TMU_TSTR); | ||
56 | return 0; | ||
57 | } | ||
48 | 58 | ||
49 | /* | 59 | static cycle_t tmu_timer_read(void) |
50 | * avoiding timer inconsistencies (they are rare, but they happen)... | 60 | { |
51 | * there is one kind of problem that must be avoided here: | 61 | return ~ctrl_inl(TMU1_TCNT); |
52 | * 1. the timer counter underflows | 62 | } |
53 | */ | 63 | |
64 | static int tmu_set_next_event(unsigned long cycles, | ||
65 | struct clock_event_device *evt) | ||
66 | { | ||
67 | tmu0_timer_set_interval(cycles, 1); | ||
68 | return 0; | ||
69 | } | ||
54 | 70 | ||
55 | if (jiffies_t == jiffies_p) { | 71 | static void tmu_set_mode(enum clock_event_mode mode, |
56 | if (count > count_p) { | 72 | struct clock_event_device *evt) |
57 | /* the nutcase */ | 73 | { |
58 | if (ctrl_inw(TMU0_TCR) & 0x100) { /* Check UNF bit */ | 74 | switch (mode) { |
59 | count -= LATCH; | 75 | case CLOCK_EVT_MODE_PERIODIC: |
60 | } else { | 76 | ctrl_outl(ctrl_inl(TMU0_TCNT), TMU0_TCOR); |
61 | printk("%s (): hardware timer problem?\n", | 77 | break; |
62 | __FUNCTION__); | 78 | case CLOCK_EVT_MODE_ONESHOT: |
63 | } | 79 | ctrl_outl(0, TMU0_TCOR); |
64 | } | 80 | break; |
65 | } else | 81 | case CLOCK_EVT_MODE_UNUSED: |
66 | jiffies_p = jiffies_t; | 82 | case CLOCK_EVT_MODE_SHUTDOWN: |
67 | 83 | break; | |
68 | count_p = count; | 84 | } |
69 | |||
70 | count = ((LATCH-1) - count) * TICK_SIZE; | ||
71 | count = (count + LATCH/2) / LATCH; | ||
72 | |||
73 | return count; | ||
74 | } | 85 | } |
75 | 86 | ||
87 | static struct clock_event_device tmu0_clockevent = { | ||
88 | .name = "tmu0", | ||
89 | .shift = 32, | ||
90 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | ||
91 | .set_mode = tmu_set_mode, | ||
92 | .set_next_event = tmu_set_next_event, | ||
93 | }; | ||
94 | |||
76 | static irqreturn_t tmu_timer_interrupt(int irq, void *dummy) | 95 | static irqreturn_t tmu_timer_interrupt(int irq, void *dummy) |
77 | { | 96 | { |
97 | struct clock_event_device *evt = &tmu0_clockevent; | ||
78 | unsigned long timer_status; | 98 | unsigned long timer_status; |
79 | 99 | ||
80 | /* Clear UNF bit */ | 100 | /* Clear UNF bit */ |
@@ -82,72 +102,76 @@ static irqreturn_t tmu_timer_interrupt(int irq, void *dummy) | |||
82 | timer_status &= ~0x100; | 102 | timer_status &= ~0x100; |
83 | ctrl_outw(timer_status, TMU0_TCR); | 103 | ctrl_outw(timer_status, TMU0_TCR); |
84 | 104 | ||
85 | /* | 105 | evt->event_handler(evt); |
86 | * Here we are in the timer irq handler. We just have irqs locally | ||
87 | * disabled but we don't know if the timer_bh is running on the other | ||
88 | * CPU. We need to avoid to SMP race with it. NOTE: we don' t need | ||
89 | * the irq version of write_lock because as just said we have irq | ||
90 | * locally disabled. -arca | ||
91 | */ | ||
92 | write_seqlock(&xtime_lock); | ||
93 | handle_timer_tick(); | ||
94 | write_sequnlock(&xtime_lock); | ||
95 | 106 | ||
96 | return IRQ_HANDLED; | 107 | return IRQ_HANDLED; |
97 | } | 108 | } |
98 | 109 | ||
99 | static struct irqaction tmu_irq = { | 110 | static struct irqaction tmu0_irq = { |
100 | .name = "timer", | 111 | .name = "periodic timer", |
101 | .handler = tmu_timer_interrupt, | 112 | .handler = tmu_timer_interrupt, |
102 | .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, | 113 | .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, |
103 | .mask = CPU_MASK_NONE, | 114 | .mask = CPU_MASK_NONE, |
104 | }; | 115 | }; |
105 | 116 | ||
106 | static void tmu_clk_init(struct clk *clk) | 117 | static void tmu0_clk_init(struct clk *clk) |
107 | { | 118 | { |
108 | u8 divisor = TMU0_TCR_INIT & 0x7; | 119 | u8 divisor = TMU_TCR_INIT & 0x7; |
109 | ctrl_outw(TMU0_TCR_INIT, TMU0_TCR); | 120 | ctrl_outw(TMU_TCR_INIT, TMU0_TCR); |
110 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); | 121 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); |
111 | } | 122 | } |
112 | 123 | ||
113 | static void tmu_clk_recalc(struct clk *clk) | 124 | static void tmu0_clk_recalc(struct clk *clk) |
114 | { | 125 | { |
115 | u8 divisor = ctrl_inw(TMU0_TCR) & 0x7; | 126 | u8 divisor = ctrl_inw(TMU0_TCR) & 0x7; |
116 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); | 127 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); |
117 | } | 128 | } |
118 | 129 | ||
119 | static struct clk_ops tmu_clk_ops = { | 130 | static struct clk_ops tmu0_clk_ops = { |
120 | .init = tmu_clk_init, | 131 | .init = tmu0_clk_init, |
121 | .recalc = tmu_clk_recalc, | 132 | .recalc = tmu0_clk_recalc, |
122 | }; | 133 | }; |
123 | 134 | ||
124 | static struct clk tmu0_clk = { | 135 | static struct clk tmu0_clk = { |
125 | .name = "tmu0_clk", | 136 | .name = "tmu0_clk", |
126 | .ops = &tmu_clk_ops, | 137 | .ops = &tmu0_clk_ops, |
127 | }; | 138 | }; |
128 | 139 | ||
129 | static int tmu_timer_start(void) | 140 | static void tmu1_clk_init(struct clk *clk) |
130 | { | 141 | { |
131 | ctrl_outb(TMU_TSTR_INIT, TMU_TSTR); | 142 | u8 divisor = TMU_TCR_INIT & 0x7; |
132 | return 0; | 143 | ctrl_outw(divisor, TMU1_TCR); |
144 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); | ||
133 | } | 145 | } |
134 | 146 | ||
135 | static int tmu_timer_stop(void) | 147 | static void tmu1_clk_recalc(struct clk *clk) |
136 | { | 148 | { |
137 | ctrl_outb(0, TMU_TSTR); | 149 | u8 divisor = ctrl_inw(TMU1_TCR) & 0x7; |
138 | return 0; | 150 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); |
139 | } | 151 | } |
140 | 152 | ||
153 | static struct clk_ops tmu1_clk_ops = { | ||
154 | .init = tmu1_clk_init, | ||
155 | .recalc = tmu1_clk_recalc, | ||
156 | }; | ||
157 | |||
158 | static struct clk tmu1_clk = { | ||
159 | .name = "tmu1_clk", | ||
160 | .ops = &tmu1_clk_ops, | ||
161 | }; | ||
162 | |||
141 | static int tmu_timer_init(void) | 163 | static int tmu_timer_init(void) |
142 | { | 164 | { |
143 | unsigned long interval; | 165 | unsigned long interval; |
166 | unsigned long frequency; | ||
144 | 167 | ||
145 | setup_irq(CONFIG_SH_TIMER_IRQ, &tmu_irq); | 168 | setup_irq(CONFIG_SH_TIMER_IRQ, &tmu0_irq); |
146 | 169 | ||
147 | tmu0_clk.parent = clk_get(NULL, "module_clk"); | 170 | tmu0_clk.parent = clk_get(NULL, "module_clk"); |
171 | tmu1_clk.parent = clk_get(NULL, "module_clk"); | ||
148 | 172 | ||
149 | /* Start TMU0 */ | ||
150 | tmu_timer_stop(); | 173 | tmu_timer_stop(); |
174 | |||
151 | #if !defined(CONFIG_CPU_SUBTYPE_SH7300) && \ | 175 | #if !defined(CONFIG_CPU_SUBTYPE_SH7300) && \ |
152 | !defined(CONFIG_CPU_SUBTYPE_SH7760) && \ | 176 | !defined(CONFIG_CPU_SUBTYPE_SH7760) && \ |
153 | !defined(CONFIG_CPU_SUBTYPE_SH7785) | 177 | !defined(CONFIG_CPU_SUBTYPE_SH7785) |
@@ -155,15 +179,29 @@ static int tmu_timer_init(void) | |||
155 | #endif | 179 | #endif |
156 | 180 | ||
157 | clk_register(&tmu0_clk); | 181 | clk_register(&tmu0_clk); |
182 | clk_register(&tmu1_clk); | ||
158 | clk_enable(&tmu0_clk); | 183 | clk_enable(&tmu0_clk); |
184 | clk_enable(&tmu1_clk); | ||
159 | 185 | ||
160 | interval = (clk_get_rate(&tmu0_clk) + HZ / 2) / HZ; | 186 | frequency = clk_get_rate(&tmu0_clk); |
161 | printk(KERN_INFO "Interval = %ld\n", interval); | 187 | interval = (frequency + HZ / 2) / HZ; |
162 | 188 | ||
163 | ctrl_outl(interval, TMU0_TCOR); | 189 | sh_hpt_frequency = clk_get_rate(&tmu1_clk); |
164 | ctrl_outl(interval, TMU0_TCNT); | 190 | ctrl_outl(~0, TMU1_TCNT); |
191 | ctrl_outl(~0, TMU1_TCOR); | ||
165 | 192 | ||
166 | tmu_timer_start(); | 193 | tmu0_timer_set_interval(interval, 1); |
194 | |||
195 | tmu0_clockevent.mult = div_sc(frequency, NSEC_PER_SEC, | ||
196 | tmu0_clockevent.shift); | ||
197 | tmu0_clockevent.max_delta_ns = | ||
198 | clockevent_delta2ns(-1, &tmu0_clockevent); | ||
199 | tmu0_clockevent.min_delta_ns = | ||
200 | clockevent_delta2ns(1, &tmu0_clockevent); | ||
201 | |||
202 | tmu0_clockevent.cpumask = cpumask_of_cpu(0); | ||
203 | |||
204 | clockevents_register_device(&tmu0_clockevent); | ||
167 | 205 | ||
168 | return 0; | 206 | return 0; |
169 | } | 207 | } |
@@ -172,9 +210,7 @@ struct sys_timer_ops tmu_timer_ops = { | |||
172 | .init = tmu_timer_init, | 210 | .init = tmu_timer_init, |
173 | .start = tmu_timer_start, | 211 | .start = tmu_timer_start, |
174 | .stop = tmu_timer_stop, | 212 | .stop = tmu_timer_stop, |
175 | #ifndef CONFIG_GENERIC_TIME | 213 | .read = tmu_timer_read, |
176 | .get_offset = tmu_timer_get_offset, | ||
177 | #endif | ||
178 | }; | 214 | }; |
179 | 215 | ||
180 | struct sys_timer tmu_timer = { | 216 | struct sys_timer tmu_timer = { |