diff options
author | Ley Foon Tan <lftan@altera.com> | 2014-11-06 02:20:04 -0500 |
---|---|---|
committer | Ley Foon Tan <lftan@altera.com> | 2014-12-07 23:56:01 -0500 |
commit | 4182de9e6356c0ac567c072241f014476ffd9ce0 (patch) | |
tree | ed47fa1b1953b98d0130b5a7ac7d19add9d34ab1 /arch/nios2 | |
parent | 95acd4c7b69c9b250d901d154390ec4c8b7b51c1 (diff) |
nios2: Time keeping
Add time keeping code for nios2.
Signed-off-by: Ley Foon Tan <lftan@altera.com>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'arch/nios2')
-rw-r--r-- | arch/nios2/include/asm/delay.h | 21 | ||||
-rw-r--r-- | arch/nios2/include/asm/timex.h | 24 | ||||
-rw-r--r-- | arch/nios2/kernel/time.c | 310 | ||||
-rw-r--r-- | arch/nios2/lib/delay.c | 52 |
4 files changed, 407 insertions, 0 deletions
diff --git a/arch/nios2/include/asm/delay.h b/arch/nios2/include/asm/delay.h new file mode 100644 index 000000000000..098e49bf3aa3 --- /dev/null +++ b/arch/nios2/include/asm/delay.h | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Altera Corporation | ||
3 | * Copyright (C) 2004 Microtronix Datacom Ltd | ||
4 | * | ||
5 | * This file is subject to the terms and conditions of the GNU General Public | ||
6 | * License. See the file "COPYING" in the main directory of this archive | ||
7 | * for more details. | ||
8 | */ | ||
9 | |||
10 | #ifndef _ASM_NIOS2_DELAY_H | ||
11 | #define _ASM_NIOS2_DELAY_H | ||
12 | |||
13 | #include <asm-generic/delay.h> | ||
14 | |||
15 | /* Undefined functions to get compile-time errors */ | ||
16 | extern void __bad_udelay(void); | ||
17 | extern void __bad_ndelay(void); | ||
18 | |||
19 | extern unsigned long loops_per_jiffy; | ||
20 | |||
21 | #endif /* _ASM_NIOS2_DELAY_H */ | ||
diff --git a/arch/nios2/include/asm/timex.h b/arch/nios2/include/asm/timex.h new file mode 100644 index 000000000000..2f2abb28ec2f --- /dev/null +++ b/arch/nios2/include/asm/timex.h | |||
@@ -0,0 +1,24 @@ | |||
1 | /* Copyright Altera Corporation (C) 2014. All rights reserved. | ||
2 | * | ||
3 | * This program is free software; you can redistribute it and/or modify | ||
4 | * it under the terms of the GNU General Public License, version 2, | ||
5 | * as published by the Free Software Foundation. | ||
6 | * | ||
7 | * This program is distributed in the hope that it will be useful, | ||
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
10 | * GNU General Public License for more details. | ||
11 | * | ||
12 | * You should have received a copy of the GNU General Public License | ||
13 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
14 | * | ||
15 | */ | ||
16 | |||
17 | #ifndef _ASM_NIOS2_TIMEX_H | ||
18 | #define _ASM_NIOS2_TIMEX_H | ||
19 | |||
20 | typedef unsigned long cycles_t; | ||
21 | |||
22 | extern cycles_t get_cycles(void); | ||
23 | |||
24 | #endif | ||
diff --git a/arch/nios2/kernel/time.c b/arch/nios2/kernel/time.c new file mode 100644 index 000000000000..d90ca1b854e0 --- /dev/null +++ b/arch/nios2/kernel/time.c | |||
@@ -0,0 +1,310 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013-2014 Altera Corporation | ||
3 | * Copyright (C) 2010 Tobias Klauser <tklauser@distanz.ch> | ||
4 | * Copyright (C) 2004 Microtronix Datacom Ltd. | ||
5 | * | ||
6 | * This file is subject to the terms and conditions of the GNU General Public | ||
7 | * License. See the file "COPYING" in the main directory of this archive | ||
8 | * for more details. | ||
9 | */ | ||
10 | |||
11 | #include <linux/interrupt.h> | ||
12 | #include <linux/clockchips.h> | ||
13 | #include <linux/clocksource.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/of_address.h> | ||
17 | #include <linux/of_irq.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/slab.h> | ||
20 | |||
21 | #define ALTERA_TIMER_STATUS_REG 0 | ||
22 | #define ALTERA_TIMER_CONTROL_REG 4 | ||
23 | #define ALTERA_TIMER_PERIODL_REG 8 | ||
24 | #define ALTERA_TIMER_PERIODH_REG 12 | ||
25 | #define ALTERA_TIMER_SNAPL_REG 16 | ||
26 | #define ALTERA_TIMER_SNAPH_REG 20 | ||
27 | |||
28 | #define ALTERA_TIMER_CONTROL_ITO_MSK (0x1) | ||
29 | #define ALTERA_TIMER_CONTROL_CONT_MSK (0x2) | ||
30 | #define ALTERA_TIMER_CONTROL_START_MSK (0x4) | ||
31 | #define ALTERA_TIMER_CONTROL_STOP_MSK (0x8) | ||
32 | |||
33 | struct nios2_timer { | ||
34 | void __iomem *base; | ||
35 | unsigned long freq; | ||
36 | int irq; | ||
37 | }; | ||
38 | |||
39 | struct nios2_clockevent_dev { | ||
40 | struct nios2_timer timer; | ||
41 | struct clock_event_device ced; | ||
42 | }; | ||
43 | |||
44 | struct nios2_clocksource { | ||
45 | struct nios2_timer timer; | ||
46 | struct clocksource cs; | ||
47 | }; | ||
48 | |||
49 | static inline struct nios2_clockevent_dev * | ||
50 | to_nios2_clkevent(struct clock_event_device *evt) | ||
51 | { | ||
52 | return container_of(evt, struct nios2_clockevent_dev, ced); | ||
53 | } | ||
54 | |||
55 | static inline struct nios2_clocksource * | ||
56 | to_nios2_clksource(struct clocksource *cs) | ||
57 | { | ||
58 | return container_of(cs, struct nios2_clocksource, cs); | ||
59 | } | ||
60 | |||
61 | static u16 timer_readw(struct nios2_timer *timer, u32 offs) | ||
62 | { | ||
63 | return readw(timer->base + offs); | ||
64 | } | ||
65 | |||
66 | static void timer_writew(struct nios2_timer *timer, u16 val, u32 offs) | ||
67 | { | ||
68 | writew(val, timer->base + offs); | ||
69 | } | ||
70 | |||
71 | static inline unsigned long read_timersnapshot(struct nios2_timer *timer) | ||
72 | { | ||
73 | unsigned long count; | ||
74 | |||
75 | timer_writew(timer, 0, ALTERA_TIMER_SNAPL_REG); | ||
76 | count = timer_readw(timer, ALTERA_TIMER_SNAPH_REG) << 16 | | ||
77 | timer_readw(timer, ALTERA_TIMER_SNAPL_REG); | ||
78 | |||
79 | return count; | ||
80 | } | ||
81 | |||
82 | static cycle_t nios2_timer_read(struct clocksource *cs) | ||
83 | { | ||
84 | struct nios2_clocksource *nios2_cs = to_nios2_clksource(cs); | ||
85 | unsigned long flags; | ||
86 | u32 count; | ||
87 | |||
88 | local_irq_save(flags); | ||
89 | count = read_timersnapshot(&nios2_cs->timer); | ||
90 | local_irq_restore(flags); | ||
91 | |||
92 | /* Counter is counting down */ | ||
93 | return ~count; | ||
94 | } | ||
95 | |||
96 | static struct nios2_clocksource nios2_cs = { | ||
97 | .cs = { | ||
98 | .name = "nios2-clksrc", | ||
99 | .rating = 250, | ||
100 | .read = nios2_timer_read, | ||
101 | .mask = CLOCKSOURCE_MASK(32), | ||
102 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | ||
103 | }, | ||
104 | }; | ||
105 | |||
106 | cycles_t get_cycles(void) | ||
107 | { | ||
108 | return nios2_timer_read(&nios2_cs.cs); | ||
109 | } | ||
110 | |||
111 | static void nios2_timer_start(struct nios2_timer *timer) | ||
112 | { | ||
113 | u16 ctrl; | ||
114 | |||
115 | ctrl = timer_readw(timer, ALTERA_TIMER_CONTROL_REG); | ||
116 | ctrl |= ALTERA_TIMER_CONTROL_START_MSK; | ||
117 | timer_writew(timer, ctrl, ALTERA_TIMER_CONTROL_REG); | ||
118 | } | ||
119 | |||
120 | static void nios2_timer_stop(struct nios2_timer *timer) | ||
121 | { | ||
122 | u16 ctrl; | ||
123 | |||
124 | ctrl = timer_readw(timer, ALTERA_TIMER_CONTROL_REG); | ||
125 | ctrl |= ALTERA_TIMER_CONTROL_STOP_MSK; | ||
126 | timer_writew(timer, ctrl, ALTERA_TIMER_CONTROL_REG); | ||
127 | } | ||
128 | |||
129 | static void nios2_timer_config(struct nios2_timer *timer, unsigned long period, | ||
130 | enum clock_event_mode mode) | ||
131 | { | ||
132 | u16 ctrl; | ||
133 | |||
134 | /* The timer's actual period is one cycle greater than the value | ||
135 | * stored in the period register. */ | ||
136 | period--; | ||
137 | |||
138 | ctrl = timer_readw(timer, ALTERA_TIMER_CONTROL_REG); | ||
139 | /* stop counter */ | ||
140 | timer_writew(timer, ctrl | ALTERA_TIMER_CONTROL_STOP_MSK, | ||
141 | ALTERA_TIMER_CONTROL_REG); | ||
142 | |||
143 | /* write new count */ | ||
144 | timer_writew(timer, period, ALTERA_TIMER_PERIODL_REG); | ||
145 | timer_writew(timer, period >> 16, ALTERA_TIMER_PERIODH_REG); | ||
146 | |||
147 | ctrl |= ALTERA_TIMER_CONTROL_START_MSK | ALTERA_TIMER_CONTROL_ITO_MSK; | ||
148 | if (mode == CLOCK_EVT_MODE_PERIODIC) | ||
149 | ctrl |= ALTERA_TIMER_CONTROL_CONT_MSK; | ||
150 | else | ||
151 | ctrl &= ~ALTERA_TIMER_CONTROL_CONT_MSK; | ||
152 | timer_writew(timer, ctrl, ALTERA_TIMER_CONTROL_REG); | ||
153 | } | ||
154 | |||
155 | static int nios2_timer_set_next_event(unsigned long delta, | ||
156 | struct clock_event_device *evt) | ||
157 | { | ||
158 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); | ||
159 | |||
160 | nios2_timer_config(&nios2_ced->timer, delta, evt->mode); | ||
161 | |||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static void nios2_timer_set_mode(enum clock_event_mode mode, | ||
166 | struct clock_event_device *evt) | ||
167 | { | ||
168 | unsigned long period; | ||
169 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); | ||
170 | struct nios2_timer *timer = &nios2_ced->timer; | ||
171 | |||
172 | switch (mode) { | ||
173 | case CLOCK_EVT_MODE_PERIODIC: | ||
174 | period = DIV_ROUND_UP(timer->freq, HZ); | ||
175 | nios2_timer_config(timer, period, CLOCK_EVT_MODE_PERIODIC); | ||
176 | break; | ||
177 | case CLOCK_EVT_MODE_ONESHOT: | ||
178 | case CLOCK_EVT_MODE_UNUSED: | ||
179 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
180 | nios2_timer_stop(timer); | ||
181 | break; | ||
182 | case CLOCK_EVT_MODE_RESUME: | ||
183 | nios2_timer_start(timer); | ||
184 | break; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | irqreturn_t timer_interrupt(int irq, void *dev_id) | ||
189 | { | ||
190 | struct clock_event_device *evt = (struct clock_event_device *) dev_id; | ||
191 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); | ||
192 | |||
193 | /* Clear the interrupt condition */ | ||
194 | timer_writew(&nios2_ced->timer, 0, ALTERA_TIMER_STATUS_REG); | ||
195 | evt->event_handler(evt); | ||
196 | |||
197 | return IRQ_HANDLED; | ||
198 | } | ||
199 | |||
200 | static void __init nios2_timer_get_base_and_freq(struct device_node *np, | ||
201 | void __iomem **base, u32 *freq) | ||
202 | { | ||
203 | *base = of_iomap(np, 0); | ||
204 | if (!*base) | ||
205 | panic("Unable to map reg for %s\n", np->name); | ||
206 | |||
207 | if (of_property_read_u32(np, "clock-frequency", freq)) | ||
208 | panic("Unable to get %s clock frequency\n", np->name); | ||
209 | } | ||
210 | |||
211 | static struct nios2_clockevent_dev nios2_ce = { | ||
212 | .ced = { | ||
213 | .name = "nios2-clkevent", | ||
214 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | ||
215 | .rating = 250, | ||
216 | .shift = 32, | ||
217 | .set_next_event = nios2_timer_set_next_event, | ||
218 | .set_mode = nios2_timer_set_mode, | ||
219 | }, | ||
220 | }; | ||
221 | |||
222 | static __init void nios2_clockevent_init(struct device_node *timer) | ||
223 | { | ||
224 | void __iomem *iobase; | ||
225 | u32 freq; | ||
226 | int irq; | ||
227 | |||
228 | nios2_timer_get_base_and_freq(timer, &iobase, &freq); | ||
229 | |||
230 | irq = irq_of_parse_and_map(timer, 0); | ||
231 | if (irq < 0) | ||
232 | panic("Unable to parse timer irq\n"); | ||
233 | |||
234 | nios2_ce.timer.base = iobase; | ||
235 | nios2_ce.timer.irq = irq; | ||
236 | nios2_ce.timer.freq = freq; | ||
237 | |||
238 | nios2_ce.ced.cpumask = cpumask_of(0); | ||
239 | nios2_ce.ced.irq = irq; | ||
240 | |||
241 | nios2_timer_stop(&nios2_ce.timer); | ||
242 | /* clear pending interrupt */ | ||
243 | timer_writew(&nios2_ce.timer, 0, ALTERA_TIMER_STATUS_REG); | ||
244 | |||
245 | if (request_irq(irq, timer_interrupt, IRQF_TIMER, timer->name, | ||
246 | &nios2_ce.ced)) | ||
247 | panic("Unable to setup timer irq\n"); | ||
248 | |||
249 | clockevents_config_and_register(&nios2_ce.ced, freq, 1, ULONG_MAX); | ||
250 | } | ||
251 | |||
252 | static __init void nios2_clocksource_init(struct device_node *timer) | ||
253 | { | ||
254 | unsigned int ctrl; | ||
255 | void __iomem *iobase; | ||
256 | u32 freq; | ||
257 | |||
258 | nios2_timer_get_base_and_freq(timer, &iobase, &freq); | ||
259 | |||
260 | nios2_cs.timer.base = iobase; | ||
261 | nios2_cs.timer.freq = freq; | ||
262 | |||
263 | clocksource_register_hz(&nios2_cs.cs, freq); | ||
264 | |||
265 | timer_writew(&nios2_cs.timer, USHRT_MAX, ALTERA_TIMER_PERIODL_REG); | ||
266 | timer_writew(&nios2_cs.timer, USHRT_MAX, ALTERA_TIMER_PERIODH_REG); | ||
267 | |||
268 | /* interrupt disable + continuous + start */ | ||
269 | ctrl = ALTERA_TIMER_CONTROL_CONT_MSK | ALTERA_TIMER_CONTROL_START_MSK; | ||
270 | timer_writew(&nios2_cs.timer, ctrl, ALTERA_TIMER_CONTROL_REG); | ||
271 | |||
272 | /* Calibrate the delay loop directly */ | ||
273 | lpj_fine = freq / HZ; | ||
274 | } | ||
275 | |||
276 | /* | ||
277 | * The first timer instance will use as a clockevent. If there are two or | ||
278 | * more instances, the second one gets used as clocksource and all | ||
279 | * others are unused. | ||
280 | */ | ||
281 | static void __init nios2_time_init(struct device_node *timer) | ||
282 | { | ||
283 | static int num_called; | ||
284 | |||
285 | switch (num_called) { | ||
286 | case 0: | ||
287 | nios2_clockevent_init(timer); | ||
288 | break; | ||
289 | case 1: | ||
290 | nios2_clocksource_init(timer); | ||
291 | break; | ||
292 | default: | ||
293 | break; | ||
294 | } | ||
295 | |||
296 | num_called++; | ||
297 | } | ||
298 | |||
299 | void read_persistent_clock(struct timespec *ts) | ||
300 | { | ||
301 | ts->tv_sec = mktime(2007, 1, 1, 0, 0, 0); | ||
302 | ts->tv_nsec = 0; | ||
303 | } | ||
304 | |||
305 | void __init time_init(void) | ||
306 | { | ||
307 | clocksource_of_init(); | ||
308 | } | ||
309 | |||
310 | CLOCKSOURCE_OF_DECLARE(nios2_timer, "altr,timer-1.0", nios2_time_init); | ||
diff --git a/arch/nios2/lib/delay.c b/arch/nios2/lib/delay.c new file mode 100644 index 000000000000..088119cd0cc5 --- /dev/null +++ b/arch/nios2/lib/delay.c | |||
@@ -0,0 +1,52 @@ | |||
1 | /* Copyright Altera Corporation (C) 2014. All rights reserved. | ||
2 | * | ||
3 | * This program is free software; you can redistribute it and/or modify | ||
4 | * it under the terms of the GNU General Public License, version 2, | ||
5 | * as published by the Free Software Foundation. | ||
6 | * | ||
7 | * This program is distributed in the hope that it will be useful, | ||
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
10 | * GNU General Public License for more details. | ||
11 | * | ||
12 | * You should have received a copy of the GNU General Public License | ||
13 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
14 | * | ||
15 | */ | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <asm/delay.h> | ||
19 | #include <asm/param.h> | ||
20 | #include <asm/processor.h> | ||
21 | #include <asm/timex.h> | ||
22 | |||
23 | void __delay(unsigned long cycles) | ||
24 | { | ||
25 | cycles_t start = get_cycles(); | ||
26 | |||
27 | while ((get_cycles() - start) < cycles) | ||
28 | cpu_relax(); | ||
29 | } | ||
30 | EXPORT_SYMBOL(__delay); | ||
31 | |||
32 | void __const_udelay(unsigned long xloops) | ||
33 | { | ||
34 | u64 loops; | ||
35 | |||
36 | loops = (u64)xloops * loops_per_jiffy * HZ; | ||
37 | |||
38 | __delay(loops >> 32); | ||
39 | } | ||
40 | EXPORT_SYMBOL(__const_udelay); | ||
41 | |||
42 | void __udelay(unsigned long usecs) | ||
43 | { | ||
44 | __const_udelay(usecs * 0x10C7UL); /* 2**32 / 1000000 (rounded up) */ | ||
45 | } | ||
46 | EXPORT_SYMBOL(__udelay); | ||
47 | |||
48 | void __ndelay(unsigned long nsecs) | ||
49 | { | ||
50 | __const_udelay(nsecs * 0x5UL); /* 2**32 / 1000000000 (rounded up) */ | ||
51 | } | ||
52 | EXPORT_SYMBOL(__ndelay); | ||