[PATCH] add suspend/resume for timer
[deliverable/linux.git] / arch / i386 / kernel / timers / timer_hpet.c
CommitLineData
1da177e4
LT
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
6c036527 21static unsigned long __read_mostly hpet_usec_quotient; /* convert hpet clks to usec */
1da177e4
LT
22static unsigned long tsc_hpet_quotient; /* convert tsc to hpet clks */
23static unsigned long hpet_last; /* hpet counter value at last tick*/
24static unsigned long last_tsc_low; /* lsb 32 bits of Time Stamp Counter */
25static unsigned long last_tsc_high; /* msb 32 bits of Time Stamp Counter */
26static unsigned long long monotonic_base;
27static 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_mhz * 10^6))
34 * ns = cycles * (10^3 / cpu_mhz)
35 *
36 * Then we use scaling math (suggested by george@mvista.com) to get:
37 * ns = cycles * (10^3 * SC / cpu_mhz) / 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 * -johnstul@us.ibm.com "math is hard, lets go shopping!"
43 */
44static unsigned long cyc2ns_scale;
45#define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */
46
47static inline void set_cyc2ns_scale(unsigned long cpu_mhz)
48{
49 cyc2ns_scale = (1000 << CYC2NS_SCALE_FACTOR)/cpu_mhz;
50}
51
52static inline unsigned long long cycles_2_ns(unsigned long long cyc)
53{
54 return (cyc * cyc2ns_scale) >> CYC2NS_SCALE_FACTOR;
55}
56
57static unsigned long long monotonic_clock_hpet(void)
58{
59 unsigned long long last_offset, this_offset, base;
60 unsigned seq;
61
62 /* atomically read monotonic base & last_offset */
63 do {
64 seq = read_seqbegin(&monotonic_lock);
65 last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
66 base = monotonic_base;
67 } while (read_seqretry(&monotonic_lock, seq));
68
69 /* Read the Time Stamp Counter */
70 rdtscll(this_offset);
71
72 /* return the value in ns */
73 return base + cycles_2_ns(this_offset - last_offset);
74}
75
76static unsigned long get_offset_hpet(void)
77{
78 register unsigned long eax, edx;
79
80 eax = hpet_readl(HPET_COUNTER);
81 eax -= hpet_last; /* hpet delta */
35492df5 82 eax = min(hpet_tick, eax);
1da177e4
LT
83 /*
84 * Time offset = (hpet delta) * ( usecs per HPET clock )
85 * = (hpet delta) * ( usecs per tick / HPET clocks per tick)
86 * = (hpet delta) * ( hpet_usec_quotient ) / (2^32)
87 *
88 * Where,
89 * hpet_usec_quotient = (2^32 * usecs per tick)/HPET clocks per tick
90 *
91 * Using a mull instead of a divl saves some cycles in critical path.
92 */
93 ASM_MUL64_REG(eax, edx, hpet_usec_quotient, eax);
94
95 /* our adjusted time offset in microseconds */
96 return edx;
97}
98
99static void mark_offset_hpet(void)
100{
101 unsigned long long this_offset, last_offset;
102 unsigned long offset;
103
104 write_seqlock(&monotonic_lock);
105 last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
106 rdtsc(last_tsc_low, last_tsc_high);
107
35492df5 108 if (hpet_use_timer)
109 offset = hpet_readl(HPET_T0_CMP) - hpet_tick;
110 else
111 offset = hpet_readl(HPET_COUNTER);
112 if (unlikely(((offset - hpet_last) >= (2*hpet_tick)) && (hpet_last != 0))) {
113 int lost_ticks = ((offset - hpet_last) / hpet_tick) - 1;
1da177e4
LT
114 jiffies_64 += lost_ticks;
115 }
116 hpet_last = offset;
117
118 /* update the monotonic base value */
119 this_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
120 monotonic_base += cycles_2_ns(this_offset - last_offset);
121 write_sequnlock(&monotonic_lock);
122}
123
124static void delay_hpet(unsigned long loops)
125{
126 unsigned long hpet_start, hpet_end;
127 unsigned long eax;
128
129 /* loops is the number of cpu cycles. Convert it to hpet clocks */
130 ASM_MUL64_REG(eax, loops, tsc_hpet_quotient, loops);
131
132 hpet_start = hpet_readl(HPET_COUNTER);
133 do {
134 rep_nop();
135 hpet_end = hpet_readl(HPET_COUNTER);
136 } while ((hpet_end - hpet_start) < (loops));
137}
138
4116c527
VP
139static struct timer_opts timer_hpet;
140
1da177e4
LT
141static int __init init_hpet(char* override)
142{
143 unsigned long result, remain;
144
145 /* check clock override */
146 if (override[0] && strncmp(override,"hpet",4))
147 return -ENODEV;
148
149 if (!is_hpet_enabled())
150 return -ENODEV;
151
152 printk("Using HPET for gettimeofday\n");
153 if (cpu_has_tsc) {
154 unsigned long tsc_quotient = calibrate_tsc_hpet(&tsc_hpet_quotient);
155 if (tsc_quotient) {
156 /* report CPU clock rate in Hz.
157 * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
158 * clock/second. Our precision is about 100 ppm.
159 */
160 { unsigned long eax=0, edx=1000;
161 ASM_DIV64_REG(cpu_khz, edx, tsc_quotient,
162 eax, edx);
a3a255e7 163 printk("Detected %u.%03u MHz processor.\n",
1da177e4
LT
164 cpu_khz / 1000, cpu_khz % 1000);
165 }
166 set_cyc2ns_scale(cpu_khz/1000);
167 }
4116c527
VP
168 /* set this only when cpu_has_tsc */
169 timer_hpet.read_timer = read_timer_tsc;
1da177e4
LT
170 }
171
172 /*
173 * Math to calculate hpet to usec multiplier
174 * Look for the comments at get_offset_hpet()
175 */
176 ASM_DIV64_REG(result, remain, hpet_tick, 0, KERNEL_TICK_USEC);
177 if (remain > (hpet_tick >> 1))
178 result++; /* rounding the result */
179 hpet_usec_quotient = result;
180
181 return 0;
182}
183
c3c433e4
SL
184static int hpet_resume(void)
185{
186 write_seqlock(&monotonic_lock);
187 /* Assume this is the last mark offset time */
188 rdtsc(last_tsc_low, last_tsc_high);
189
190 if (hpet_use_timer)
191 hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick;
192 else
193 hpet_last = hpet_readl(HPET_COUNTER);
194 write_sequnlock(&monotonic_lock);
195 return 0;
196}
1da177e4
LT
197/************************************************************/
198
199/* tsc timer_opts struct */
6c036527 200static struct timer_opts timer_hpet __read_mostly = {
1da177e4
LT
201 .name = "hpet",
202 .mark_offset = mark_offset_hpet,
203 .get_offset = get_offset_hpet,
204 .monotonic_clock = monotonic_clock_hpet,
205 .delay = delay_hpet,
c3c433e4 206 .resume = hpet_resume,
1da177e4
LT
207};
208
209struct init_timer_opts __initdata timer_hpet_init = {
210 .init = init_hpet,
211 .opts = &timer_hpet,
212};
This page took 0.068708 seconds and 5 git commands to generate.