Commit | Line | Data |
---|---|---|
aa01666d PM |
1 | /* |
2 | * arch/sh/kernel/timers/timer-tmu.c - TMU Timer Support | |
3 | * | |
4 | * Copyright (C) 2005 Paul Mundt | |
5 | * | |
6 | * TMU handling code hacked out of arch/sh/kernel/time.c | |
7 | * | |
8 | * Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka | |
9 | * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org> | |
10 | * Copyright (C) 2002, 2003, 2004 Paul Mundt | |
11 | * Copyright (C) 2002 M. R. Brown <mrbrown@linux-sh.org> | |
12 | * | |
13 | * This file is subject to the terms and conditions of the GNU General Public | |
14 | * License. See the file "COPYING" in the main directory of this archive | |
15 | * for more details. | |
16 | */ | |
17 | #include <linux/init.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/interrupt.h> | |
aa01666d PM |
20 | #include <linux/seqlock.h> |
21 | #include <asm/timer.h> | |
22 | #include <asm/rtc.h> | |
23 | #include <asm/io.h> | |
24 | #include <asm/irq.h> | |
25 | #include <asm/clock.h> | |
26 | ||
27 | #define TMU_TOCR_INIT 0x00 | |
28 | #define TMU0_TCR_INIT 0x0020 | |
29 | #define TMU_TSTR_INIT 1 | |
30 | ||
31 | #define TMU0_TCR_CALIB 0x0000 | |
32 | ||
aa01666d PM |
33 | static unsigned long tmu_timer_get_offset(void) |
34 | { | |
35 | int count; | |
aa01666d PM |
36 | static int count_p = 0x7fffffff; /* for the first call after boot */ |
37 | static unsigned long jiffies_p = 0; | |
38 | ||
39 | /* | |
40 | * cache volatile jiffies temporarily; we have IRQs turned off. | |
41 | */ | |
42 | unsigned long jiffies_t; | |
43 | ||
aa01666d PM |
44 | /* timer count may underflow right here */ |
45 | count = ctrl_inl(TMU0_TCNT); /* read the latched count */ | |
46 | ||
47 | jiffies_t = jiffies; | |
48 | ||
49 | /* | |
50 | * avoiding timer inconsistencies (they are rare, but they happen)... | |
51 | * there is one kind of problem that must be avoided here: | |
52 | * 1. the timer counter underflows | |
53 | */ | |
54 | ||
55 | if (jiffies_t == jiffies_p) { | |
56 | if (count > count_p) { | |
57 | /* the nutcase */ | |
58 | if (ctrl_inw(TMU0_TCR) & 0x100) { /* Check UNF bit */ | |
59 | count -= LATCH; | |
60 | } else { | |
61 | printk("%s (): hardware timer problem?\n", | |
62 | __FUNCTION__); | |
63 | } | |
64 | } | |
65 | } else | |
66 | jiffies_p = jiffies_t; | |
67 | ||
68 | count_p = count; | |
aa01666d PM |
69 | |
70 | count = ((LATCH-1) - count) * TICK_SIZE; | |
71 | count = (count + LATCH/2) / LATCH; | |
72 | ||
73 | return count; | |
74 | } | |
75 | ||
35f3c518 | 76 | static irqreturn_t tmu_timer_interrupt(int irq, void *dummy) |
aa01666d PM |
77 | { |
78 | unsigned long timer_status; | |
79 | ||
80 | /* Clear UNF bit */ | |
81 | timer_status = ctrl_inw(TMU0_TCR); | |
82 | timer_status &= ~0x100; | |
83 | ctrl_outw(timer_status, TMU0_TCR); | |
84 | ||
85 | /* | |
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); | |
35f3c518 | 93 | handle_timer_tick(); |
aa01666d PM |
94 | write_sequnlock(&xtime_lock); |
95 | ||
96 | return IRQ_HANDLED; | |
97 | } | |
98 | ||
99 | static struct irqaction tmu_irq = { | |
100 | .name = "timer", | |
101 | .handler = tmu_timer_interrupt, | |
e74b5680 | 102 | .flags = IRQF_DISABLED | IRQF_TIMER, |
aa01666d PM |
103 | .mask = CPU_MASK_NONE, |
104 | }; | |
105 | ||
aa01666d PM |
106 | static void tmu_clk_init(struct clk *clk) |
107 | { | |
108 | u8 divisor = TMU0_TCR_INIT & 0x7; | |
109 | ctrl_outw(TMU0_TCR_INIT, TMU0_TCR); | |
110 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); | |
111 | } | |
112 | ||
113 | static void tmu_clk_recalc(struct clk *clk) | |
114 | { | |
115 | u8 divisor = ctrl_inw(TMU0_TCR) & 0x7; | |
116 | clk->rate = clk->parent->rate / (4 << (divisor << 1)); | |
117 | } | |
118 | ||
119 | static struct clk_ops tmu_clk_ops = { | |
120 | .init = tmu_clk_init, | |
121 | .recalc = tmu_clk_recalc, | |
122 | }; | |
123 | ||
124 | static struct clk tmu0_clk = { | |
125 | .name = "tmu0_clk", | |
126 | .ops = &tmu_clk_ops, | |
127 | }; | |
128 | ||
3aa770e7 AS |
129 | static int tmu_timer_start(void) |
130 | { | |
131 | ctrl_outb(TMU_TSTR_INIT, TMU_TSTR); | |
132 | return 0; | |
133 | } | |
134 | ||
135 | static int tmu_timer_stop(void) | |
136 | { | |
137 | ctrl_outb(0, TMU_TSTR); | |
138 | return 0; | |
139 | } | |
140 | ||
aa01666d PM |
141 | static int tmu_timer_init(void) |
142 | { | |
143 | unsigned long interval; | |
144 | ||
417528a2 | 145 | setup_irq(CONFIG_SH_TIMER_IRQ, &tmu_irq); |
aa01666d PM |
146 | |
147 | tmu0_clk.parent = clk_get("module_clk"); | |
148 | ||
149 | /* Start TMU0 */ | |
3aa770e7 | 150 | tmu_timer_stop(); |
aa01666d PM |
151 | #if !defined(CONFIG_CPU_SUBTYPE_SH7300) && !defined(CONFIG_CPU_SUBTYPE_SH7760) |
152 | ctrl_outb(TMU_TOCR_INIT, TMU_TOCR); | |
153 | #endif | |
154 | ||
155 | clk_register(&tmu0_clk); | |
156 | clk_enable(&tmu0_clk); | |
157 | ||
158 | interval = (clk_get_rate(&tmu0_clk) + HZ / 2) / HZ; | |
159 | printk(KERN_INFO "Interval = %ld\n", interval); | |
160 | ||
161 | ctrl_outl(interval, TMU0_TCOR); | |
162 | ctrl_outl(interval, TMU0_TCNT); | |
163 | ||
3aa770e7 | 164 | tmu_timer_start(); |
aa01666d PM |
165 | |
166 | return 0; | |
167 | } | |
168 | ||
169 | struct sys_timer_ops tmu_timer_ops = { | |
170 | .init = tmu_timer_init, | |
3aa770e7 AS |
171 | .start = tmu_timer_start, |
172 | .stop = tmu_timer_stop, | |
a700f359 | 173 | #ifndef CONFIG_GENERIC_TIME |
aa01666d | 174 | .get_offset = tmu_timer_get_offset, |
a700f359 | 175 | #endif |
aa01666d PM |
176 | }; |
177 | ||
178 | struct sys_timer tmu_timer = { | |
179 | .name = "tmu", | |
180 | .ops = &tmu_timer_ops, | |
181 | }; |