Merge master.kernel.org:/home/rmk/linux-2.6-serial
[linux-2.6] / arch / i386 / kernel / timers / timer_hpet.c
1 /*
2  * This code largely moved from arch/i386/kernel/time.c.
3  * See comments there for proper credits.
4  */
5
6 #include <linux/spinlock.h>
7 #include <linux/init.h>
8 #include <linux/timex.h>
9 #include <linux/errno.h>
10 #include <linux/string.h>
11 #include <linux/jiffies.h>
12
13 #include <asm/timer.h>
14 #include <asm/io.h>
15 #include <asm/processor.h>
16
17 #include "io_ports.h"
18 #include "mach_timer.h"
19 #include <asm/hpet.h>
20
21 static unsigned long hpet_usec_quotient __read_mostly;  /* convert hpet clks to usec */
22 static unsigned long tsc_hpet_quotient __read_mostly;   /* convert tsc to hpet clks */
23 static unsigned long hpet_last;         /* hpet counter value at last tick*/
24 static unsigned long last_tsc_low;      /* lsb 32 bits of Time Stamp Counter */
25 static unsigned long last_tsc_high;     /* msb 32 bits of Time Stamp Counter */
26 static unsigned long long monotonic_base;
27 static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED;
28
29 /* convert from cycles(64bits) => nanoseconds (64bits)
30  *  basic equation:
31  *              ns = cycles / (freq / ns_per_sec)
32  *              ns = cycles * (ns_per_sec / freq)
33  *              ns = cycles * (10^9 / (cpu_khz * 10^3))
34  *              ns = cycles * (10^6 / cpu_khz)
35  *
36  *      Then we use scaling math (suggested by george@mvista.com) to get:
37  *              ns = cycles * (10^6 * SC / cpu_khz) / SC
38  *              ns = cycles * cyc2ns_scale / SC
39  *
40  *      And since SC is a constant power of two, we can convert the div
41  *  into a shift.
42  *
43  *  We can use khz divisor instead of mhz to keep a better percision, since
44  *  cyc2ns_scale is limited to 10^6 * 2^10, which fits in 32 bits.
45  *  (mathieu.desnoyers@polymtl.ca)
46  *
47  *                      -johnstul@us.ibm.com "math is hard, lets go shopping!"
48  */
49 static unsigned long cyc2ns_scale;
50 #define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */
51
52 static inline void set_cyc2ns_scale(unsigned long cpu_khz)
53 {
54         cyc2ns_scale = (1000000 << CYC2NS_SCALE_FACTOR)/cpu_khz;
55 }
56
57 static inline unsigned long long cycles_2_ns(unsigned long long cyc)
58 {
59         return (cyc * cyc2ns_scale) >> CYC2NS_SCALE_FACTOR;
60 }
61
62 static unsigned long long monotonic_clock_hpet(void)
63 {
64         unsigned long long last_offset, this_offset, base;
65         unsigned seq;
66
67         /* atomically read monotonic base & last_offset */
68         do {
69                 seq = read_seqbegin(&monotonic_lock);
70                 last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
71                 base = monotonic_base;
72         } while (read_seqretry(&monotonic_lock, seq));
73
74         /* Read the Time Stamp Counter */
75         rdtscll(this_offset);
76
77         /* return the value in ns */
78         return base + cycles_2_ns(this_offset - last_offset);
79 }
80
81 static unsigned long get_offset_hpet(void)
82 {
83         register unsigned long eax, edx;
84
85         eax = hpet_readl(HPET_COUNTER);
86         eax -= hpet_last;       /* hpet delta */
87         eax = min(hpet_tick, eax);
88         /*
89          * Time offset = (hpet delta) * ( usecs per HPET clock )
90          *             = (hpet delta) * ( usecs per tick / HPET clocks per tick)
91          *             = (hpet delta) * ( hpet_usec_quotient ) / (2^32)
92          *
93          * Where,
94          * hpet_usec_quotient = (2^32 * usecs per tick)/HPET clocks per tick
95          *
96          * Using a mull instead of a divl saves some cycles in critical path.
97          */
98         ASM_MUL64_REG(eax, edx, hpet_usec_quotient, eax);
99
100         /* our adjusted time offset in microseconds */
101         return edx;
102 }
103
104 static void mark_offset_hpet(void)
105 {
106         unsigned long long this_offset, last_offset;
107         unsigned long offset;
108
109         write_seqlock(&monotonic_lock);
110         last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
111         rdtsc(last_tsc_low, last_tsc_high);
112
113         if (hpet_use_timer)
114                 offset = hpet_readl(HPET_T0_CMP) - hpet_tick;
115         else
116                 offset = hpet_readl(HPET_COUNTER);
117         if (unlikely(((offset - hpet_last) >= (2*hpet_tick)) && (hpet_last != 0))) {
118                 int lost_ticks = ((offset - hpet_last) / hpet_tick) - 1;
119                 jiffies_64 += lost_ticks;
120         }
121         hpet_last = offset;
122
123         /* update the monotonic base value */
124         this_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
125         monotonic_base += cycles_2_ns(this_offset - last_offset);
126         write_sequnlock(&monotonic_lock);
127 }
128
129 static void delay_hpet(unsigned long loops)
130 {
131         unsigned long hpet_start, hpet_end;
132         unsigned long eax;
133
134         /* loops is the number of cpu cycles. Convert it to hpet clocks */
135         ASM_MUL64_REG(eax, loops, tsc_hpet_quotient, loops);
136
137         hpet_start = hpet_readl(HPET_COUNTER);
138         do {
139                 rep_nop();
140                 hpet_end = hpet_readl(HPET_COUNTER);
141         } while ((hpet_end - hpet_start) < (loops));
142 }
143
144 static struct timer_opts timer_hpet;
145
146 static int __init init_hpet(char* override)
147 {
148         unsigned long result, remain;
149
150         /* check clock override */
151         if (override[0] && strncmp(override,"hpet",4))
152                 return -ENODEV;
153
154         if (!is_hpet_enabled())
155                 return -ENODEV;
156
157         printk("Using HPET for gettimeofday\n");
158         if (cpu_has_tsc) {
159                 unsigned long tsc_quotient = calibrate_tsc_hpet(&tsc_hpet_quotient);
160                 if (tsc_quotient) {
161                         /* report CPU clock rate in Hz.
162                          * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
163                          * clock/second. Our precision is about 100 ppm.
164                          */
165                         {       unsigned long eax=0, edx=1000;
166                                 ASM_DIV64_REG(cpu_khz, edx, tsc_quotient,
167                                                 eax, edx);
168                                 printk("Detected %u.%03u MHz processor.\n",
169                                         cpu_khz / 1000, cpu_khz % 1000);
170                         }
171                         set_cyc2ns_scale(cpu_khz);
172                 }
173                 /* set this only when cpu_has_tsc */
174                 timer_hpet.read_timer = read_timer_tsc;
175         }
176
177         /*
178          * Math to calculate hpet to usec multiplier
179          * Look for the comments at get_offset_hpet()
180          */
181         ASM_DIV64_REG(result, remain, hpet_tick, 0, KERNEL_TICK_USEC);
182         if (remain > (hpet_tick >> 1))
183                 result++; /* rounding the result */
184         hpet_usec_quotient = result;
185
186         return 0;
187 }
188
189 static int hpet_resume(void)
190 {
191         write_seqlock(&monotonic_lock);
192         /* Assume this is the last mark offset time */
193         rdtsc(last_tsc_low, last_tsc_high);
194
195         if (hpet_use_timer)
196                 hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick;
197         else
198                 hpet_last = hpet_readl(HPET_COUNTER);
199         write_sequnlock(&monotonic_lock);
200         return 0;
201 }
202 /************************************************************/
203
204 /* tsc timer_opts struct */
205 static struct timer_opts timer_hpet __read_mostly = {
206         .name =                 "hpet",
207         .mark_offset =          mark_offset_hpet,
208         .get_offset =           get_offset_hpet,
209         .monotonic_clock =      monotonic_clock_hpet,
210         .delay =                delay_hpet,
211         .resume =               hpet_resume,
212 };
213
214 struct init_timer_opts __initdata timer_hpet_init = {
215         .init = init_hpet,
216         .opts = &timer_hpet,
217 };