diff options
Diffstat (limited to 'arch/arm/plat-mxc/time.c')
-rw-r--r-- | arch/arm/plat-mxc/time.c | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/arch/arm/plat-mxc/time.c b/arch/arm/plat-mxc/time.c new file mode 100644 index 00000000000..4b0fe285e83 --- /dev/null +++ b/arch/arm/plat-mxc/time.c | |||
@@ -0,0 +1,319 @@ | |||
1 | /* | ||
2 | * linux/arch/arm/plat-mxc/time.c | ||
3 | * | ||
4 | * Copyright (C) 2000-2001 Deep Blue Solutions | ||
5 | * Copyright (C) 2002 Shane Nay (shane@minirl.com) | ||
6 | * Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.com) | ||
7 | * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License | ||
11 | * as published by the Free Software Foundation; either version 2 | ||
12 | * of the License, or (at your option) any later version. | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||
21 | * MA 02110-1301, USA. | ||
22 | */ | ||
23 | |||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/irq.h> | ||
26 | #include <linux/clockchips.h> | ||
27 | #include <linux/clk.h> | ||
28 | |||
29 | #include <mach/hardware.h> | ||
30 | #include <asm/sched_clock.h> | ||
31 | #include <asm/mach/time.h> | ||
32 | #include <mach/common.h> | ||
33 | |||
34 | /* | ||
35 | * There are 2 versions of the timer hardware on Freescale MXC hardware. | ||
36 | * Version 1: MX1/MXL, MX21, MX27. | ||
37 | * Version 2: MX25, MX31, MX35, MX37, MX51 | ||
38 | */ | ||
39 | |||
40 | /* defines common for all i.MX */ | ||
41 | #define MXC_TCTL 0x00 | ||
42 | #define MXC_TCTL_TEN (1 << 0) /* Enable module */ | ||
43 | #define MXC_TPRER 0x04 | ||
44 | |||
45 | /* MX1, MX21, MX27 */ | ||
46 | #define MX1_2_TCTL_CLK_PCLK1 (1 << 1) | ||
47 | #define MX1_2_TCTL_IRQEN (1 << 4) | ||
48 | #define MX1_2_TCTL_FRR (1 << 8) | ||
49 | #define MX1_2_TCMP 0x08 | ||
50 | #define MX1_2_TCN 0x10 | ||
51 | #define MX1_2_TSTAT 0x14 | ||
52 | |||
53 | /* MX21, MX27 */ | ||
54 | #define MX2_TSTAT_CAPT (1 << 1) | ||
55 | #define MX2_TSTAT_COMP (1 << 0) | ||
56 | |||
57 | /* MX31, MX35, MX25, MX5 */ | ||
58 | #define V2_TCTL_WAITEN (1 << 3) /* Wait enable mode */ | ||
59 | #define V2_TCTL_CLK_IPG (1 << 6) | ||
60 | #define V2_TCTL_FRR (1 << 9) | ||
61 | #define V2_IR 0x0c | ||
62 | #define V2_TSTAT 0x08 | ||
63 | #define V2_TSTAT_OF1 (1 << 0) | ||
64 | #define V2_TCN 0x24 | ||
65 | #define V2_TCMP 0x10 | ||
66 | |||
67 | #define timer_is_v1() (cpu_is_mx1() || cpu_is_mx21() || cpu_is_mx27()) | ||
68 | #define timer_is_v2() (!timer_is_v1()) | ||
69 | |||
70 | static struct clock_event_device clockevent_mxc; | ||
71 | static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED; | ||
72 | |||
73 | static void __iomem *timer_base; | ||
74 | |||
75 | static inline void gpt_irq_disable(void) | ||
76 | { | ||
77 | unsigned int tmp; | ||
78 | |||
79 | if (timer_is_v2()) | ||
80 | __raw_writel(0, timer_base + V2_IR); | ||
81 | else { | ||
82 | tmp = __raw_readl(timer_base + MXC_TCTL); | ||
83 | __raw_writel(tmp & ~MX1_2_TCTL_IRQEN, timer_base + MXC_TCTL); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | static inline void gpt_irq_enable(void) | ||
88 | { | ||
89 | if (timer_is_v2()) | ||
90 | __raw_writel(1<<0, timer_base + V2_IR); | ||
91 | else { | ||
92 | __raw_writel(__raw_readl(timer_base + MXC_TCTL) | MX1_2_TCTL_IRQEN, | ||
93 | timer_base + MXC_TCTL); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | static void gpt_irq_acknowledge(void) | ||
98 | { | ||
99 | if (timer_is_v1()) { | ||
100 | if (cpu_is_mx1()) | ||
101 | __raw_writel(0, timer_base + MX1_2_TSTAT); | ||
102 | else | ||
103 | __raw_writel(MX2_TSTAT_CAPT | MX2_TSTAT_COMP, | ||
104 | timer_base + MX1_2_TSTAT); | ||
105 | } else if (timer_is_v2()) | ||
106 | __raw_writel(V2_TSTAT_OF1, timer_base + V2_TSTAT); | ||
107 | } | ||
108 | |||
109 | static void __iomem *sched_clock_reg; | ||
110 | |||
111 | static DEFINE_CLOCK_DATA(cd); | ||
112 | unsigned long long notrace sched_clock(void) | ||
113 | { | ||
114 | cycle_t cyc = sched_clock_reg ? __raw_readl(sched_clock_reg) : 0; | ||
115 | |||
116 | return cyc_to_sched_clock(&cd, cyc, (u32)~0); | ||
117 | } | ||
118 | |||
119 | static void notrace mxc_update_sched_clock(void) | ||
120 | { | ||
121 | cycle_t cyc = sched_clock_reg ? __raw_readl(sched_clock_reg) : 0; | ||
122 | update_sched_clock(&cd, cyc, (u32)~0); | ||
123 | } | ||
124 | |||
125 | static int __init mxc_clocksource_init(struct clk *timer_clk) | ||
126 | { | ||
127 | unsigned int c = clk_get_rate(timer_clk); | ||
128 | void __iomem *reg = timer_base + (timer_is_v2() ? V2_TCN : MX1_2_TCN); | ||
129 | |||
130 | sched_clock_reg = reg; | ||
131 | |||
132 | init_sched_clock(&cd, mxc_update_sched_clock, 32, c); | ||
133 | return clocksource_mmio_init(reg, "mxc_timer1", c, 200, 32, | ||
134 | clocksource_mmio_readl_up); | ||
135 | } | ||
136 | |||
137 | /* clock event */ | ||
138 | |||
139 | static int mx1_2_set_next_event(unsigned long evt, | ||
140 | struct clock_event_device *unused) | ||
141 | { | ||
142 | unsigned long tcmp; | ||
143 | |||
144 | tcmp = __raw_readl(timer_base + MX1_2_TCN) + evt; | ||
145 | |||
146 | __raw_writel(tcmp, timer_base + MX1_2_TCMP); | ||
147 | |||
148 | return (int)(tcmp - __raw_readl(timer_base + MX1_2_TCN)) < 0 ? | ||
149 | -ETIME : 0; | ||
150 | } | ||
151 | |||
152 | static int v2_set_next_event(unsigned long evt, | ||
153 | struct clock_event_device *unused) | ||
154 | { | ||
155 | unsigned long tcmp; | ||
156 | |||
157 | tcmp = __raw_readl(timer_base + V2_TCN) + evt; | ||
158 | |||
159 | __raw_writel(tcmp, timer_base + V2_TCMP); | ||
160 | |||
161 | return (int)(tcmp - __raw_readl(timer_base + V2_TCN)) < 0 ? | ||
162 | -ETIME : 0; | ||
163 | } | ||
164 | |||
165 | #ifdef DEBUG | ||
166 | static const char *clock_event_mode_label[] = { | ||
167 | [CLOCK_EVT_MODE_PERIODIC] = "CLOCK_EVT_MODE_PERIODIC", | ||
168 | [CLOCK_EVT_MODE_ONESHOT] = "CLOCK_EVT_MODE_ONESHOT", | ||
169 | [CLOCK_EVT_MODE_SHUTDOWN] = "CLOCK_EVT_MODE_SHUTDOWN", | ||
170 | [CLOCK_EVT_MODE_UNUSED] = "CLOCK_EVT_MODE_UNUSED" | ||
171 | }; | ||
172 | #endif /* DEBUG */ | ||
173 | |||
174 | static void mxc_set_mode(enum clock_event_mode mode, | ||
175 | struct clock_event_device *evt) | ||
176 | { | ||
177 | unsigned long flags; | ||
178 | |||
179 | /* | ||
180 | * The timer interrupt generation is disabled at least | ||
181 | * for enough time to call mxc_set_next_event() | ||
182 | */ | ||
183 | local_irq_save(flags); | ||
184 | |||
185 | /* Disable interrupt in GPT module */ | ||
186 | gpt_irq_disable(); | ||
187 | |||
188 | if (mode != clockevent_mode) { | ||
189 | /* Set event time into far-far future */ | ||
190 | if (timer_is_v2()) | ||
191 | __raw_writel(__raw_readl(timer_base + V2_TCN) - 3, | ||
192 | timer_base + V2_TCMP); | ||
193 | else | ||
194 | __raw_writel(__raw_readl(timer_base + MX1_2_TCN) - 3, | ||
195 | timer_base + MX1_2_TCMP); | ||
196 | |||
197 | /* Clear pending interrupt */ | ||
198 | gpt_irq_acknowledge(); | ||
199 | } | ||
200 | |||
201 | #ifdef DEBUG | ||
202 | printk(KERN_INFO "mxc_set_mode: changing mode from %s to %s\n", | ||
203 | clock_event_mode_label[clockevent_mode], | ||
204 | clock_event_mode_label[mode]); | ||
205 | #endif /* DEBUG */ | ||
206 | |||
207 | /* Remember timer mode */ | ||
208 | clockevent_mode = mode; | ||
209 | local_irq_restore(flags); | ||
210 | |||
211 | switch (mode) { | ||
212 | case CLOCK_EVT_MODE_PERIODIC: | ||
213 | printk(KERN_ERR"mxc_set_mode: Periodic mode is not " | ||
214 | "supported for i.MX\n"); | ||
215 | break; | ||
216 | case CLOCK_EVT_MODE_ONESHOT: | ||
217 | /* | ||
218 | * Do not put overhead of interrupt enable/disable into | ||
219 | * mxc_set_next_event(), the core has about 4 minutes | ||
220 | * to call mxc_set_next_event() or shutdown clock after | ||
221 | * mode switching | ||
222 | */ | ||
223 | local_irq_save(flags); | ||
224 | gpt_irq_enable(); | ||
225 | local_irq_restore(flags); | ||
226 | break; | ||
227 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
228 | case CLOCK_EVT_MODE_UNUSED: | ||
229 | case CLOCK_EVT_MODE_RESUME: | ||
230 | /* Left event sources disabled, no more interrupts appear */ | ||
231 | break; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | /* | ||
236 | * IRQ handler for the timer | ||
237 | */ | ||
238 | static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) | ||
239 | { | ||
240 | struct clock_event_device *evt = &clockevent_mxc; | ||
241 | uint32_t tstat; | ||
242 | |||
243 | if (timer_is_v2()) | ||
244 | tstat = __raw_readl(timer_base + V2_TSTAT); | ||
245 | else | ||
246 | tstat = __raw_readl(timer_base + MX1_2_TSTAT); | ||
247 | |||
248 | gpt_irq_acknowledge(); | ||
249 | |||
250 | evt->event_handler(evt); | ||
251 | |||
252 | return IRQ_HANDLED; | ||
253 | } | ||
254 | |||
255 | static struct irqaction mxc_timer_irq = { | ||
256 | .name = "i.MX Timer Tick", | ||
257 | .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, | ||
258 | .handler = mxc_timer_interrupt, | ||
259 | }; | ||
260 | |||
261 | static struct clock_event_device clockevent_mxc = { | ||
262 | .name = "mxc_timer1", | ||
263 | .features = CLOCK_EVT_FEAT_ONESHOT, | ||
264 | .shift = 32, | ||
265 | .set_mode = mxc_set_mode, | ||
266 | .set_next_event = mx1_2_set_next_event, | ||
267 | .rating = 200, | ||
268 | }; | ||
269 | |||
270 | static int __init mxc_clockevent_init(struct clk *timer_clk) | ||
271 | { | ||
272 | unsigned int c = clk_get_rate(timer_clk); | ||
273 | |||
274 | if (timer_is_v2()) | ||
275 | clockevent_mxc.set_next_event = v2_set_next_event; | ||
276 | |||
277 | clockevent_mxc.mult = div_sc(c, NSEC_PER_SEC, | ||
278 | clockevent_mxc.shift); | ||
279 | clockevent_mxc.max_delta_ns = | ||
280 | clockevent_delta2ns(0xfffffffe, &clockevent_mxc); | ||
281 | clockevent_mxc.min_delta_ns = | ||
282 | clockevent_delta2ns(0xff, &clockevent_mxc); | ||
283 | |||
284 | clockevent_mxc.cpumask = cpumask_of(0); | ||
285 | |||
286 | clockevents_register_device(&clockevent_mxc); | ||
287 | |||
288 | return 0; | ||
289 | } | ||
290 | |||
291 | void __init mxc_timer_init(struct clk *timer_clk, void __iomem *base, int irq) | ||
292 | { | ||
293 | uint32_t tctl_val; | ||
294 | |||
295 | clk_enable(timer_clk); | ||
296 | |||
297 | timer_base = base; | ||
298 | |||
299 | /* | ||
300 | * Initialise to a known state (all timers off, and timing reset) | ||
301 | */ | ||
302 | |||
303 | __raw_writel(0, timer_base + MXC_TCTL); | ||
304 | __raw_writel(0, timer_base + MXC_TPRER); /* see datasheet note */ | ||
305 | |||
306 | if (timer_is_v2()) | ||
307 | tctl_val = V2_TCTL_CLK_IPG | V2_TCTL_FRR | V2_TCTL_WAITEN | MXC_TCTL_TEN; | ||
308 | else | ||
309 | tctl_val = MX1_2_TCTL_FRR | MX1_2_TCTL_CLK_PCLK1 | MXC_TCTL_TEN; | ||
310 | |||
311 | __raw_writel(tctl_val, timer_base + MXC_TCTL); | ||
312 | |||
313 | /* init and register the timer to the framework */ | ||
314 | mxc_clocksource_init(timer_clk); | ||
315 | mxc_clockevent_init(timer_clk); | ||
316 | |||
317 | /* Make irqs happen */ | ||
318 | setup_irq(irq, &mxc_timer_irq); | ||
319 | } | ||