Commit | Line | Data |
---|---|---|
5a0015d6 CZ |
1 | /* |
2 | * arch/xtensa/kernel/time.c | |
3 | * | |
4 | * Timer and clock support. | |
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 | * Copyright (C) 2005 Tensilica Inc. | |
11 | * | |
12 | * Chris Zankel <chris@zankel.net> | |
13 | */ | |
14 | ||
15 | #include <linux/config.h> | |
16 | #include <linux/errno.h> | |
17 | #include <linux/time.h> | |
18 | #include <linux/timex.h> | |
19 | #include <linux/interrupt.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/init.h> | |
22 | #include <linux/irq.h> | |
23 | #include <linux/profile.h> | |
24 | #include <linux/delay.h> | |
25 | ||
26 | #include <asm/timex.h> | |
27 | #include <asm/platform.h> | |
28 | ||
29 | ||
30 | extern volatile unsigned long wall_jiffies; | |
31 | ||
5a0015d6 CZ |
32 | spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED; |
33 | EXPORT_SYMBOL(rtc_lock); | |
34 | ||
35 | ||
36 | #ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT | |
37 | unsigned long ccount_per_jiffy; /* per 1/HZ */ | |
38 | unsigned long ccount_nsec; /* nsec per ccount increment */ | |
39 | #endif | |
40 | ||
41 | unsigned int last_ccount_stamp; | |
42 | static long last_rtc_update = 0; | |
43 | ||
44 | /* | |
45 | * Scheduler clock - returns current tim in nanosec units. | |
46 | */ | |
47 | ||
48 | unsigned long long sched_clock(void) | |
49 | { | |
50 | return (unsigned long long)jiffies * (1000000000 / HZ); | |
51 | } | |
52 | ||
53 | static irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs); | |
54 | static struct irqaction timer_irqaction = { | |
55 | .handler = timer_interrupt, | |
56 | .flags = SA_INTERRUPT, | |
57 | .name = "timer", | |
58 | }; | |
59 | ||
60 | void __init time_init(void) | |
61 | { | |
62 | time_t sec_o, sec_n = 0; | |
63 | ||
64 | /* The platform must provide a function to calibrate the processor | |
65 | * speed for the CALIBRATE. | |
66 | */ | |
67 | ||
288a60cf | 68 | #ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT |
5a0015d6 CZ |
69 | printk("Calibrating CPU frequency "); |
70 | platform_calibrate_ccount(); | |
71 | printk("%d.%02d MHz\n", (int)ccount_per_jiffy/(1000000/HZ), | |
72 | (int)(ccount_per_jiffy/(10000/HZ))%100); | |
73 | #endif | |
74 | ||
75 | /* Set time from RTC (if provided) */ | |
76 | ||
77 | if (platform_get_rtc_time(&sec_o) == 0) | |
78 | while (platform_get_rtc_time(&sec_n)) | |
79 | if (sec_o != sec_n) | |
80 | break; | |
81 | ||
82 | xtime.tv_nsec = 0; | |
83 | last_rtc_update = xtime.tv_sec = sec_n; | |
84 | last_ccount_stamp = get_ccount(); | |
85 | ||
86 | set_normalized_timespec(&wall_to_monotonic, | |
87 | -xtime.tv_sec, -xtime.tv_nsec); | |
88 | ||
89 | /* Initialize the linux timer interrupt. */ | |
90 | ||
91 | setup_irq(LINUX_TIMER_INT, &timer_irqaction); | |
92 | set_linux_timer(get_ccount() + CCOUNT_PER_JIFFY); | |
93 | } | |
94 | ||
95 | ||
96 | int do_settimeofday(struct timespec *tv) | |
97 | { | |
98 | time_t wtm_sec, sec = tv->tv_sec; | |
99 | long wtm_nsec, nsec = tv->tv_nsec; | |
100 | unsigned long ccount; | |
101 | ||
102 | if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) | |
103 | return -EINVAL; | |
104 | ||
105 | write_seqlock_irq(&xtime_lock); | |
106 | ||
107 | /* This is revolting. We need to set "xtime" correctly. However, the | |
108 | * value in this location is the value at the most recent update of | |
109 | * wall time. Discover what correction gettimeofday() would have | |
110 | * made, and then undo it! | |
111 | */ | |
112 | ccount = get_ccount(); | |
113 | nsec -= (ccount - last_ccount_stamp) * CCOUNT_NSEC; | |
114 | nsec -= (jiffies - wall_jiffies) * CCOUNT_PER_JIFFY * CCOUNT_NSEC; | |
115 | ||
116 | wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec); | |
117 | wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec); | |
118 | ||
119 | set_normalized_timespec(&xtime, sec, nsec); | |
120 | set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); | |
121 | ||
b149ee22 | 122 | ntp_clear(); |
5a0015d6 CZ |
123 | write_sequnlock_irq(&xtime_lock); |
124 | return 0; | |
125 | } | |
126 | ||
127 | EXPORT_SYMBOL(do_settimeofday); | |
128 | ||
129 | ||
130 | void do_gettimeofday(struct timeval *tv) | |
131 | { | |
132 | unsigned long flags; | |
133 | unsigned long sec, usec, delta, lost, seq; | |
134 | ||
135 | do { | |
136 | seq = read_seqbegin_irqsave(&xtime_lock, flags); | |
137 | ||
138 | delta = get_ccount() - last_ccount_stamp; | |
139 | sec = xtime.tv_sec; | |
140 | usec = (xtime.tv_nsec / NSEC_PER_USEC); | |
141 | ||
142 | lost = jiffies - wall_jiffies; | |
143 | ||
144 | } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); | |
145 | ||
146 | usec += lost * (1000000UL/HZ) + (delta * CCOUNT_NSEC) / NSEC_PER_USEC; | |
147 | for (; usec >= 1000000; sec++, usec -= 1000000) | |
148 | ; | |
149 | ||
150 | tv->tv_sec = sec; | |
151 | tv->tv_usec = usec; | |
152 | } | |
153 | ||
154 | EXPORT_SYMBOL(do_gettimeofday); | |
155 | ||
156 | /* | |
157 | * The timer interrupt is called HZ times per second. | |
158 | */ | |
159 | ||
160 | irqreturn_t timer_interrupt (int irq, void *dev_id, struct pt_regs *regs) | |
161 | { | |
162 | ||
163 | unsigned long next; | |
164 | ||
165 | next = get_linux_timer(); | |
166 | ||
167 | again: | |
168 | while ((signed long)(get_ccount() - next) > 0) { | |
169 | ||
170 | profile_tick(CPU_PROFILING, regs); | |
171 | #ifndef CONFIG_SMP | |
172 | update_process_times(user_mode(regs)); | |
173 | #endif | |
174 | ||
175 | write_seqlock(&xtime_lock); | |
176 | ||
177 | last_ccount_stamp = next; | |
178 | next += CCOUNT_PER_JIFFY; | |
179 | do_timer (regs); /* Linux handler in kernel/timer.c */ | |
180 | ||
b149ee22 | 181 | if (ntp_synced() && |
5a0015d6 CZ |
182 | xtime.tv_sec - last_rtc_update >= 659 && |
183 | abs((xtime.tv_nsec/1000)-(1000000-1000000/HZ))<5000000/HZ && | |
184 | jiffies - wall_jiffies == 1) { | |
185 | ||
186 | if (platform_set_rtc_time(xtime.tv_sec+1) == 0) | |
187 | last_rtc_update = xtime.tv_sec+1; | |
188 | else | |
189 | /* Do it again in 60 s */ | |
190 | last_rtc_update += 60; | |
191 | } | |
192 | write_sequnlock(&xtime_lock); | |
193 | } | |
194 | ||
195 | /* NOTE: writing CCOMPAREn clears the interrupt. */ | |
196 | ||
197 | set_linux_timer (next); | |
198 | ||
199 | /* Make sure we didn't miss any tick... */ | |
200 | ||
201 | if ((signed long)(get_ccount() - next) > 0) | |
202 | goto again; | |
203 | ||
a58a414f | 204 | /* Allow platform to do something useful (Wdog). */ |
5a0015d6 CZ |
205 | |
206 | platform_heartbeat(); | |
207 | ||
208 | return IRQ_HANDLED; | |
209 | } | |
210 | ||
211 | #ifndef CONFIG_GENERIC_CALIBRATE_DELAY | |
212 | void __devinit calibrate_delay(void) | |
213 | { | |
214 | loops_per_jiffy = CCOUNT_PER_JIFFY; | |
215 | printk("Calibrating delay loop (skipped)... " | |
216 | "%lu.%02lu BogoMIPS preset\n", | |
217 | loops_per_jiffy/(1000000/HZ), | |
218 | (loops_per_jiffy/(10000/HZ)) % 100); | |
219 | } | |
220 | #endif | |
221 |