diff options
Diffstat (limited to 'arch/powerpc/platforms/powermac/time.c')
-rw-r--r-- | arch/powerpc/platforms/powermac/time.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/powermac/time.c b/arch/powerpc/platforms/powermac/time.c new file mode 100644 index 000000000000..5947b21a8588 --- /dev/null +++ b/arch/powerpc/platforms/powermac/time.c | |||
@@ -0,0 +1,360 @@ | |||
1 | /* | ||
2 | * Support for periodic interrupts (100 per second) and for getting | ||
3 | * the current time from the RTC on Power Macintoshes. | ||
4 | * | ||
5 | * We use the decrementer register for our periodic interrupts. | ||
6 | * | ||
7 | * Paul Mackerras August 1996. | ||
8 | * Copyright (C) 1996 Paul Mackerras. | ||
9 | * Copyright (C) 2003-2005 Benjamin Herrenschmidt. | ||
10 | * | ||
11 | */ | ||
12 | #include <linux/config.h> | ||
13 | #include <linux/errno.h> | ||
14 | #include <linux/sched.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/param.h> | ||
17 | #include <linux/string.h> | ||
18 | #include <linux/mm.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/time.h> | ||
21 | #include <linux/adb.h> | ||
22 | #include <linux/cuda.h> | ||
23 | #include <linux/pmu.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/hardirq.h> | ||
26 | #include <linux/rtc.h> | ||
27 | |||
28 | #include <asm/sections.h> | ||
29 | #include <asm/prom.h> | ||
30 | #include <asm/system.h> | ||
31 | #include <asm/io.h> | ||
32 | #include <asm/pgtable.h> | ||
33 | #include <asm/machdep.h> | ||
34 | #include <asm/time.h> | ||
35 | #include <asm/nvram.h> | ||
36 | #include <asm/smu.h> | ||
37 | |||
38 | #undef DEBUG | ||
39 | |||
40 | #ifdef DEBUG | ||
41 | #define DBG(x...) printk(x) | ||
42 | #else | ||
43 | #define DBG(x...) | ||
44 | #endif | ||
45 | |||
46 | /* Apparently the RTC stores seconds since 1 Jan 1904 */ | ||
47 | #define RTC_OFFSET 2082844800 | ||
48 | |||
49 | /* | ||
50 | * Calibrate the decrementer frequency with the VIA timer 1. | ||
51 | */ | ||
52 | #define VIA_TIMER_FREQ_6 4700000 /* time 1 frequency * 6 */ | ||
53 | |||
54 | /* VIA registers */ | ||
55 | #define RS 0x200 /* skip between registers */ | ||
56 | #define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */ | ||
57 | #define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */ | ||
58 | #define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */ | ||
59 | #define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */ | ||
60 | #define ACR (11*RS) /* Auxiliary control register */ | ||
61 | #define IFR (13*RS) /* Interrupt flag register */ | ||
62 | |||
63 | /* Bits in ACR */ | ||
64 | #define T1MODE 0xc0 /* Timer 1 mode */ | ||
65 | #define T1MODE_CONT 0x40 /* continuous interrupts */ | ||
66 | |||
67 | /* Bits in IFR and IER */ | ||
68 | #define T1_INT 0x40 /* Timer 1 interrupt */ | ||
69 | |||
70 | long __init pmac_time_init(void) | ||
71 | { | ||
72 | s32 delta = 0; | ||
73 | #ifdef CONFIG_NVRAM | ||
74 | int dst; | ||
75 | |||
76 | delta = ((s32)pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0x9)) << 16; | ||
77 | delta |= ((s32)pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0xa)) << 8; | ||
78 | delta |= pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0xb); | ||
79 | if (delta & 0x00800000UL) | ||
80 | delta |= 0xFF000000UL; | ||
81 | dst = ((pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0x8) & 0x80) != 0); | ||
82 | printk("GMT Delta read from XPRAM: %d minutes, DST: %s\n", delta/60, | ||
83 | dst ? "on" : "off"); | ||
84 | #endif | ||
85 | return delta; | ||
86 | } | ||
87 | |||
88 | static void to_rtc_time(unsigned long now, struct rtc_time *tm) | ||
89 | { | ||
90 | to_tm(now, tm); | ||
91 | tm->tm_year -= 1900; | ||
92 | tm->tm_mon -= 1; | ||
93 | } | ||
94 | |||
95 | static unsigned long from_rtc_time(struct rtc_time *tm) | ||
96 | { | ||
97 | return mktime(tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, | ||
98 | tm->tm_hour, tm->tm_min, tm->tm_sec); | ||
99 | } | ||
100 | |||
101 | #ifdef CONFIG_ADB_CUDA | ||
102 | static unsigned long cuda_get_time(void) | ||
103 | { | ||
104 | struct adb_request req; | ||
105 | unsigned long now; | ||
106 | |||
107 | if (cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_GET_TIME) < 0) | ||
108 | return 0; | ||
109 | while (!req.complete) | ||
110 | cuda_poll(); | ||
111 | if (req.reply_len != 7) | ||
112 | printk(KERN_ERR "cuda_get_time: got %d byte reply\n", | ||
113 | req.reply_len); | ||
114 | now = (req.reply[3] << 24) + (req.reply[4] << 16) | ||
115 | + (req.reply[5] << 8) + req.reply[6]; | ||
116 | return now - RTC_OFFSET; | ||
117 | } | ||
118 | |||
119 | #define cuda_get_rtc_time(tm) to_rtc_time(cuda_get_time(), (tm)) | ||
120 | |||
121 | static int cuda_set_rtc_time(struct rtc_time *tm) | ||
122 | { | ||
123 | unsigned int nowtime; | ||
124 | struct adb_request req; | ||
125 | |||
126 | nowtime = from_rtc_time(tm) + RTC_OFFSET; | ||
127 | if (cuda_request(&req, NULL, 6, CUDA_PACKET, CUDA_SET_TIME, | ||
128 | nowtime >> 24, nowtime >> 16, nowtime >> 8, | ||
129 | nowtime) < 0) | ||
130 | return -ENXIO; | ||
131 | while (!req.complete) | ||
132 | cuda_poll(); | ||
133 | if ((req.reply_len != 3) && (req.reply_len != 7)) | ||
134 | printk(KERN_ERR "cuda_set_rtc_time: got %d byte reply\n", | ||
135 | req.reply_len); | ||
136 | return 0; | ||
137 | } | ||
138 | |||
139 | #else | ||
140 | #define cuda_get_time() 0 | ||
141 | #define cuda_get_rtc_time(tm) | ||
142 | #define cuda_set_rtc_time(tm) 0 | ||
143 | #endif | ||
144 | |||
145 | #ifdef CONFIG_ADB_PMU | ||
146 | static unsigned long pmu_get_time(void) | ||
147 | { | ||
148 | struct adb_request req; | ||
149 | unsigned long now; | ||
150 | |||
151 | if (pmu_request(&req, NULL, 1, PMU_READ_RTC) < 0) | ||
152 | return 0; | ||
153 | pmu_wait_complete(&req); | ||
154 | if (req.reply_len != 4) | ||
155 | printk(KERN_ERR "pmu_get_time: got %d byte reply from PMU\n", | ||
156 | req.reply_len); | ||
157 | now = (req.reply[0] << 24) + (req.reply[1] << 16) | ||
158 | + (req.reply[2] << 8) + req.reply[3]; | ||
159 | return now - RTC_OFFSET; | ||
160 | } | ||
161 | |||
162 | #define pmu_get_rtc_time(tm) to_rtc_time(pmu_get_time(), (tm)) | ||
163 | |||
164 | static int pmu_set_rtc_time(struct rtc_time *tm) | ||
165 | { | ||
166 | unsigned int nowtime; | ||
167 | struct adb_request req; | ||
168 | |||
169 | nowtime = from_rtc_time(tm) + RTC_OFFSET; | ||
170 | if (pmu_request(&req, NULL, 5, PMU_SET_RTC, nowtime >> 24, | ||
171 | nowtime >> 16, nowtime >> 8, nowtime) < 0) | ||
172 | return -ENXIO; | ||
173 | pmu_wait_complete(&req); | ||
174 | if (req.reply_len != 0) | ||
175 | printk(KERN_ERR "pmu_set_rtc_time: %d byte reply from PMU\n", | ||
176 | req.reply_len); | ||
177 | return 0; | ||
178 | } | ||
179 | |||
180 | #else | ||
181 | #define pmu_get_time() 0 | ||
182 | #define pmu_get_rtc_time(tm) | ||
183 | #define pmu_set_rtc_time(tm) 0 | ||
184 | #endif | ||
185 | |||
186 | #ifdef CONFIG_PMAC_SMU | ||
187 | static unsigned long smu_get_time(void) | ||
188 | { | ||
189 | struct rtc_time tm; | ||
190 | |||
191 | if (smu_get_rtc_time(&tm, 1)) | ||
192 | return 0; | ||
193 | return from_rtc_time(&tm); | ||
194 | } | ||
195 | |||
196 | #else | ||
197 | #define smu_get_time() 0 | ||
198 | #define smu_get_rtc_time(tm, spin) | ||
199 | #define smu_set_rtc_time(tm, spin) 0 | ||
200 | #endif | ||
201 | |||
202 | unsigned long pmac_get_boot_time(void) | ||
203 | { | ||
204 | /* Get the time from the RTC, used only at boot time */ | ||
205 | switch (sys_ctrler) { | ||
206 | case SYS_CTRLER_CUDA: | ||
207 | return cuda_get_time(); | ||
208 | case SYS_CTRLER_PMU: | ||
209 | return pmu_get_time(); | ||
210 | case SYS_CTRLER_SMU: | ||
211 | return smu_get_time(); | ||
212 | default: | ||
213 | return 0; | ||
214 | } | ||
215 | } | ||
216 | |||
217 | void pmac_get_rtc_time(struct rtc_time *tm) | ||
218 | { | ||
219 | /* Get the time from the RTC, used only at boot time */ | ||
220 | switch (sys_ctrler) { | ||
221 | case SYS_CTRLER_CUDA: | ||
222 | cuda_get_rtc_time(tm); | ||
223 | break; | ||
224 | case SYS_CTRLER_PMU: | ||
225 | pmu_get_rtc_time(tm); | ||
226 | break; | ||
227 | case SYS_CTRLER_SMU: | ||
228 | smu_get_rtc_time(tm, 1); | ||
229 | break; | ||
230 | default: | ||
231 | ; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | int pmac_set_rtc_time(struct rtc_time *tm) | ||
236 | { | ||
237 | switch (sys_ctrler) { | ||
238 | case SYS_CTRLER_CUDA: | ||
239 | return cuda_set_rtc_time(tm); | ||
240 | case SYS_CTRLER_PMU: | ||
241 | return pmu_set_rtc_time(tm); | ||
242 | case SYS_CTRLER_SMU: | ||
243 | return smu_set_rtc_time(tm, 1); | ||
244 | default: | ||
245 | return -ENODEV; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | #ifdef CONFIG_PPC32 | ||
250 | /* | ||
251 | * Calibrate the decrementer register using VIA timer 1. | ||
252 | * This is used both on powermacs and CHRP machines. | ||
253 | */ | ||
254 | int __init via_calibrate_decr(void) | ||
255 | { | ||
256 | struct device_node *vias; | ||
257 | volatile unsigned char __iomem *via; | ||
258 | int count = VIA_TIMER_FREQ_6 / 100; | ||
259 | unsigned int dstart, dend; | ||
260 | |||
261 | vias = find_devices("via-cuda"); | ||
262 | if (vias == 0) | ||
263 | vias = find_devices("via-pmu"); | ||
264 | if (vias == 0) | ||
265 | vias = find_devices("via"); | ||
266 | if (vias == 0 || vias->n_addrs == 0) | ||
267 | return 0; | ||
268 | via = ioremap(vias->addrs[0].address, vias->addrs[0].size); | ||
269 | |||
270 | /* set timer 1 for continuous interrupts */ | ||
271 | out_8(&via[ACR], (via[ACR] & ~T1MODE) | T1MODE_CONT); | ||
272 | /* set the counter to a small value */ | ||
273 | out_8(&via[T1CH], 2); | ||
274 | /* set the latch to `count' */ | ||
275 | out_8(&via[T1LL], count); | ||
276 | out_8(&via[T1LH], count >> 8); | ||
277 | /* wait until it hits 0 */ | ||
278 | while ((in_8(&via[IFR]) & T1_INT) == 0) | ||
279 | ; | ||
280 | dstart = get_dec(); | ||
281 | /* clear the interrupt & wait until it hits 0 again */ | ||
282 | in_8(&via[T1CL]); | ||
283 | while ((in_8(&via[IFR]) & T1_INT) == 0) | ||
284 | ; | ||
285 | dend = get_dec(); | ||
286 | |||
287 | ppc_tb_freq = (dstart - dend) * 100 / 6; | ||
288 | |||
289 | iounmap(via); | ||
290 | |||
291 | return 1; | ||
292 | } | ||
293 | #endif | ||
294 | |||
295 | #ifdef CONFIG_PM | ||
296 | /* | ||
297 | * Reset the time after a sleep. | ||
298 | */ | ||
299 | static int | ||
300 | time_sleep_notify(struct pmu_sleep_notifier *self, int when) | ||
301 | { | ||
302 | static unsigned long time_diff; | ||
303 | unsigned long flags; | ||
304 | unsigned long seq; | ||
305 | struct timespec tv; | ||
306 | |||
307 | switch (when) { | ||
308 | case PBOOK_SLEEP_NOW: | ||
309 | do { | ||
310 | seq = read_seqbegin_irqsave(&xtime_lock, flags); | ||
311 | time_diff = xtime.tv_sec - pmac_get_boot_time(); | ||
312 | } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); | ||
313 | break; | ||
314 | case PBOOK_WAKE: | ||
315 | tv.tv_sec = pmac_get_boot_time() + time_diff; | ||
316 | tv.tv_nsec = 0; | ||
317 | do_settimeofday(&tv); | ||
318 | break; | ||
319 | } | ||
320 | return PBOOK_SLEEP_OK; | ||
321 | } | ||
322 | |||
323 | static struct pmu_sleep_notifier time_sleep_notifier = { | ||
324 | time_sleep_notify, SLEEP_LEVEL_MISC, | ||
325 | }; | ||
326 | #endif /* CONFIG_PM */ | ||
327 | |||
328 | /* | ||
329 | * Query the OF and get the decr frequency. | ||
330 | */ | ||
331 | void __init pmac_calibrate_decr(void) | ||
332 | { | ||
333 | #ifdef CONFIG_PM | ||
334 | /* XXX why here? */ | ||
335 | pmu_register_sleep_notifier(&time_sleep_notifier); | ||
336 | #endif /* CONFIG_PM */ | ||
337 | |||
338 | generic_calibrate_decr(); | ||
339 | |||
340 | #ifdef CONFIG_PPC32 | ||
341 | /* We assume MacRISC2 machines have correct device-tree | ||
342 | * calibration. That's better since the VIA itself seems | ||
343 | * to be slightly off. --BenH | ||
344 | */ | ||
345 | if (!machine_is_compatible("MacRISC2") && | ||
346 | !machine_is_compatible("MacRISC3") && | ||
347 | !machine_is_compatible("MacRISC4")) | ||
348 | if (via_calibrate_decr()) | ||
349 | return; | ||
350 | |||
351 | /* Special case: QuickSilver G4s seem to have a badly calibrated | ||
352 | * timebase-frequency in OF, VIA is much better on these. We should | ||
353 | * probably implement calibration based on the KL timer on these | ||
354 | * machines anyway... -BenH | ||
355 | */ | ||
356 | if (machine_is_compatible("PowerMac3,5")) | ||
357 | if (via_calibrate_decr()) | ||
358 | return; | ||
359 | #endif | ||
360 | } | ||