Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* Copyright (C) 1999,2001 |
2 | * | |
3 | * Author: J.E.J.Bottomley@HansenPartnership.com | |
4 | * | |
1da177e4 LT |
5 | * This file contains all the voyager specific routines for getting |
6 | * initialisation of the architecture to function. For additional | |
7 | * features see: | |
8 | * | |
9 | * voyager_cat.c - Voyager CAT bus interface | |
10 | * voyager_smp.c - Voyager SMP hal (emulates linux smp.c) | |
11 | */ | |
12 | ||
1da177e4 LT |
13 | #include <linux/module.h> |
14 | #include <linux/types.h> | |
15 | #include <linux/sched.h> | |
16 | #include <linux/ptrace.h> | |
17 | #include <linux/ioport.h> | |
18 | #include <linux/interrupt.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/delay.h> | |
21 | #include <linux/reboot.h> | |
22 | #include <linux/sysrq.h> | |
8d5c822b JB |
23 | #include <linux/smp.h> |
24 | #include <linux/nodemask.h> | |
1da177e4 LT |
25 | #include <asm/io.h> |
26 | #include <asm/voyager.h> | |
27 | #include <asm/vic.h> | |
28 | #include <linux/pm.h> | |
1da177e4 LT |
29 | #include <asm/tlbflush.h> |
30 | #include <asm/arch_hooks.h> | |
306e440d | 31 | #include <asm/i8253.h> |
1da177e4 LT |
32 | |
33 | /* | |
34 | * Power off function, if any | |
35 | */ | |
a4ec1eff | 36 | void (*pm_power_off) (void); |
153f8057 | 37 | EXPORT_SYMBOL(pm_power_off); |
1da177e4 LT |
38 | |
39 | int voyager_level = 0; | |
40 | ||
41 | struct voyager_SUS *voyager_SUS = NULL; | |
42 | ||
43 | #ifdef CONFIG_SMP | |
a4ec1eff | 44 | static void voyager_dump(int dummy1, struct tty_struct *dummy3) |
1da177e4 LT |
45 | { |
46 | /* get here via a sysrq */ | |
47 | voyager_smp_dump(); | |
48 | } | |
49 | ||
50 | static struct sysrq_key_op sysrq_voyager_dump_op = { | |
a4ec1eff IM |
51 | .handler = voyager_dump, |
52 | .help_msg = "Voyager", | |
53 | .action_msg = "Dump Voyager Status", | |
1da177e4 LT |
54 | }; |
55 | #endif | |
56 | ||
a4ec1eff | 57 | void voyager_detect(struct voyager_bios_info *bios) |
1da177e4 | 58 | { |
a4ec1eff IM |
59 | if (bios->len != 0xff) { |
60 | int class = (bios->class_1 << 8) | |
61 | | (bios->class_2 & 0xff); | |
1da177e4 LT |
62 | |
63 | printk("Voyager System detected.\n" | |
64 | " Class %x, Revision %d.%d\n", | |
65 | class, bios->major, bios->minor); | |
a4ec1eff | 66 | if (class == VOYAGER_LEVEL4) |
1da177e4 | 67 | voyager_level = 4; |
a4ec1eff | 68 | else if (class < VOYAGER_LEVEL5_AND_ABOVE) |
1da177e4 LT |
69 | voyager_level = 3; |
70 | else | |
71 | voyager_level = 5; | |
72 | printk(" Architecture Level %d\n", voyager_level); | |
a4ec1eff IM |
73 | if (voyager_level < 4) |
74 | printk | |
75 | ("\n**WARNING**: Voyager HAL only supports Levels 4 and 5 Architectures at the moment\n\n"); | |
1da177e4 LT |
76 | /* install the power off handler */ |
77 | pm_power_off = voyager_power_off; | |
78 | #ifdef CONFIG_SMP | |
79 | register_sysrq_key('v', &sysrq_voyager_dump_op); | |
80 | #endif | |
81 | } else { | |
82 | printk("\n\n**WARNING**: No Voyager Subsystem Found\n"); | |
83 | } | |
84 | } | |
85 | ||
a4ec1eff | 86 | void voyager_system_interrupt(int cpl, void *dev_id) |
1da177e4 LT |
87 | { |
88 | printk("Voyager: detected system interrupt\n"); | |
89 | } | |
90 | ||
91 | /* Routine to read information from the extended CMOS area */ | |
a4ec1eff | 92 | __u8 voyager_extended_cmos_read(__u16 addr) |
1da177e4 LT |
93 | { |
94 | outb(addr & 0xff, 0x74); | |
95 | outb((addr >> 8) & 0xff, 0x75); | |
96 | return inb(0x76); | |
97 | } | |
98 | ||
99 | /* internal definitions for the SUS Click Map of memory */ | |
100 | ||
101 | #define CLICK_ENTRIES 16 | |
102 | #define CLICK_SIZE 4096 /* click to byte conversion for Length */ | |
103 | ||
104 | typedef struct ClickMap { | |
105 | struct Entry { | |
a4ec1eff IM |
106 | __u32 Address; |
107 | __u32 Length; | |
1da177e4 LT |
108 | } Entry[CLICK_ENTRIES]; |
109 | } ClickMap_t; | |
110 | ||
1da177e4 LT |
111 | /* This routine is pretty much an awful hack to read the bios clickmap by |
112 | * mapping it into page 0. There are usually three regions in the map: | |
113 | * Base Memory | |
114 | * Extended Memory | |
115 | * zero length marker for end of map | |
116 | * | |
117 | * Returns are 0 for failure and 1 for success on extracting region. | |
118 | */ | |
a4ec1eff | 119 | int __init voyager_memory_detect(int region, __u32 * start, __u32 * length) |
1da177e4 LT |
120 | { |
121 | int i; | |
122 | int retval = 0; | |
123 | __u8 cmos[4]; | |
124 | ClickMap_t *map; | |
125 | unsigned long map_addr; | |
126 | unsigned long old; | |
127 | ||
a4ec1eff | 128 | if (region >= CLICK_ENTRIES) { |
1da177e4 LT |
129 | printk("Voyager: Illegal ClickMap region %d\n", region); |
130 | return 0; | |
131 | } | |
132 | ||
a4ec1eff IM |
133 | for (i = 0; i < sizeof(cmos); i++) |
134 | cmos[i] = | |
135 | voyager_extended_cmos_read(VOYAGER_MEMORY_CLICKMAP + i); | |
1da177e4 LT |
136 | |
137 | map_addr = *(unsigned long *)cmos; | |
138 | ||
139 | /* steal page 0 for this */ | |
140 | old = pg0[0]; | |
141 | pg0[0] = ((map_addr & PAGE_MASK) | _PAGE_RW | _PAGE_PRESENT); | |
142 | local_flush_tlb(); | |
143 | /* now clear everything out but page 0 */ | |
a4ec1eff | 144 | map = (ClickMap_t *) (map_addr & (~PAGE_MASK)); |
1da177e4 LT |
145 | |
146 | /* zero length is the end of the clickmap */ | |
a4ec1eff | 147 | if (map->Entry[region].Length != 0) { |
1da177e4 LT |
148 | *length = map->Entry[region].Length * CLICK_SIZE; |
149 | *start = map->Entry[region].Address; | |
150 | retval = 1; | |
151 | } | |
152 | ||
153 | /* replace the mapping */ | |
154 | pg0[0] = old; | |
155 | local_flush_tlb(); | |
156 | return retval; | |
157 | } | |
158 | ||
159 | /* voyager specific handling code for timer interrupts. Used to hand | |
160 | * off the timer tick to the SMP code, since the VIC doesn't have an | |
161 | * internal timer (The QIC does, but that's another story). */ | |
a4ec1eff | 162 | void voyager_timer_interrupt(void) |
1da177e4 | 163 | { |
a4ec1eff | 164 | if ((jiffies & 0x3ff) == 0) { |
1da177e4 LT |
165 | |
166 | /* There seems to be something flaky in either | |
167 | * hardware or software that is resetting the timer 0 | |
168 | * count to something much higher than it should be | |
169 | * This seems to occur in the boot sequence, just | |
170 | * before root is mounted. Therefore, every 10 | |
171 | * seconds or so, we sanity check the timer zero count | |
172 | * and kick it back to where it should be. | |
173 | * | |
174 | * FIXME: This is the most awful hack yet seen. I | |
175 | * should work out exactly what is interfering with | |
176 | * the timer count settings early in the boot sequence | |
177 | * and swiftly introduce it to something sharp and | |
178 | * pointy. */ | |
179 | __u16 val; | |
1da177e4 LT |
180 | |
181 | spin_lock(&i8253_lock); | |
a4ec1eff | 182 | |
1da177e4 LT |
183 | outb_p(0x00, 0x43); |
184 | val = inb_p(0x40); | |
185 | val |= inb(0x40) << 8; | |
186 | spin_unlock(&i8253_lock); | |
187 | ||
a4ec1eff IM |
188 | if (val > LATCH) { |
189 | printk | |
190 | ("\nVOYAGER: countdown timer value too high (%d), resetting\n\n", | |
191 | val); | |
1da177e4 | 192 | spin_lock(&i8253_lock); |
a4ec1eff IM |
193 | outb(0x34, 0x43); |
194 | outb_p(LATCH & 0xff, 0x40); /* LSB */ | |
195 | outb(LATCH >> 8, 0x40); /* MSB */ | |
1da177e4 LT |
196 | spin_unlock(&i8253_lock); |
197 | } | |
198 | } | |
199 | #ifdef CONFIG_SMP | |
81c06b10 | 200 | smp_vic_timer_interrupt(); |
1da177e4 LT |
201 | #endif |
202 | } | |
203 | ||
a4ec1eff | 204 | void voyager_power_off(void) |
1da177e4 LT |
205 | { |
206 | printk("VOYAGER Power Off\n"); | |
207 | ||
a4ec1eff | 208 | if (voyager_level == 5) { |
1da177e4 | 209 | voyager_cat_power_off(); |
a4ec1eff | 210 | } else if (voyager_level == 4) { |
1da177e4 LT |
211 | /* This doesn't apparently work on most L4 machines, |
212 | * but the specs say to do this to get automatic power | |
213 | * off. Unfortunately, if it doesn't power off the | |
214 | * machine, it ends up doing a cold restart, which | |
215 | * isn't really intended, so comment out the code */ | |
216 | #if 0 | |
217 | int port; | |
218 | ||
1da177e4 | 219 | /* enable the voyager Configuration Space */ |
a4ec1eff | 220 | outb((inb(VOYAGER_MC_SETUP) & 0xf0) | 0x8, VOYAGER_MC_SETUP); |
1da177e4 LT |
221 | /* the port for the power off flag is an offset from the |
222 | floating base */ | |
223 | port = (inb(VOYAGER_SSPB_RELOCATION_PORT) << 8) + 0x21; | |
224 | /* set the power off flag */ | |
225 | outb(inb(port) | 0x1, port); | |
226 | #endif | |
227 | } | |
228 | /* and wait for it to happen */ | |
f2ab4461 | 229 | local_irq_disable(); |
a4ec1eff | 230 | for (;;) |
f2ab4461 | 231 | halt(); |
1da177e4 LT |
232 | } |
233 | ||
234 | /* copied from process.c */ | |
a4ec1eff | 235 | static inline void kb_wait(void) |
1da177e4 LT |
236 | { |
237 | int i; | |
238 | ||
a4ec1eff | 239 | for (i = 0; i < 0x10000; i++) |
1da177e4 LT |
240 | if ((inb_p(0x64) & 0x02) == 0) |
241 | break; | |
242 | } | |
243 | ||
a4ec1eff | 244 | void machine_shutdown(void) |
094528a7 EB |
245 | { |
246 | /* Architecture specific shutdown needed before a kexec */ | |
247 | } | |
248 | ||
a4ec1eff | 249 | void machine_restart(char *cmd) |
1da177e4 LT |
250 | { |
251 | printk("Voyager Warm Restart\n"); | |
252 | kb_wait(); | |
253 | ||
a4ec1eff | 254 | if (voyager_level == 5) { |
1da177e4 LT |
255 | /* write magic values to the RTC to inform system that |
256 | * shutdown is beginning */ | |
257 | outb(0x8f, 0x70); | |
a4ec1eff IM |
258 | outb(0x5, 0x71); |
259 | ||
1da177e4 | 260 | udelay(50); |
a4ec1eff IM |
261 | outb(0xfe, 0x64); /* pull reset low */ |
262 | } else if (voyager_level == 4) { | |
263 | __u16 catbase = inb(VOYAGER_SSPB_RELOCATION_PORT) << 8; | |
1da177e4 | 264 | __u8 basebd = inb(VOYAGER_MC_SETUP); |
a4ec1eff | 265 | |
1da177e4 LT |
266 | outb(basebd | 0x08, VOYAGER_MC_SETUP); |
267 | outb(0x02, catbase + 0x21); | |
268 | } | |
f2ab4461 | 269 | local_irq_disable(); |
a4ec1eff | 270 | for (;;) |
f2ab4461 | 271 | halt(); |
1da177e4 LT |
272 | } |
273 | ||
a4ec1eff | 274 | void machine_emergency_restart(void) |
e6cb9941 JB |
275 | { |
276 | /*for now, just hook this to a warm restart */ | |
277 | machine_restart(NULL); | |
278 | } | |
279 | ||
a4ec1eff | 280 | void mca_nmi_hook(void) |
1da177e4 | 281 | { |
affd872e DR |
282 | __u8 dumpval __maybe_unused = inb(0xf823); |
283 | __u8 swnmi __maybe_unused = inb(0xf813); | |
1da177e4 LT |
284 | |
285 | /* FIXME: assume dump switch pressed */ | |
286 | /* check to see if the dump switch was pressed */ | |
287 | VDEBUG(("VOYAGER: dumpval = 0x%x, swnmi = 0x%x\n", dumpval, swnmi)); | |
288 | /* clear swnmi */ | |
289 | outb(0xff, 0xf813); | |
290 | /* tell SUS to ignore dump */ | |
a4ec1eff IM |
291 | if (voyager_level == 5 && voyager_SUS != NULL) { |
292 | if (voyager_SUS->SUS_mbox == VOYAGER_DUMP_BUTTON_NMI) { | |
1da177e4 LT |
293 | voyager_SUS->kernel_mbox = VOYAGER_NO_COMMAND; |
294 | voyager_SUS->kernel_flags |= VOYAGER_OS_IN_PROGRESS; | |
295 | udelay(1000); | |
296 | voyager_SUS->kernel_mbox = VOYAGER_IGNORE_DUMP; | |
297 | voyager_SUS->kernel_flags &= ~VOYAGER_OS_IN_PROGRESS; | |
298 | } | |
299 | } | |
a4ec1eff IM |
300 | printk(KERN_ERR |
301 | "VOYAGER: Dump switch pressed, printing CPU%d tracebacks\n", | |
302 | smp_processor_id()); | |
1da177e4 LT |
303 | show_stack(NULL, NULL); |
304 | show_state(); | |
305 | } | |
306 | ||
a4ec1eff | 307 | void machine_halt(void) |
1da177e4 LT |
308 | { |
309 | /* treat a halt like a power off */ | |
310 | machine_power_off(); | |
311 | } | |
312 | ||
1da177e4 LT |
313 | void machine_power_off(void) |
314 | { | |
315 | if (pm_power_off) | |
316 | pm_power_off(); | |
317 | } |