printk_foo -> printk(BIOS_FOO, ...)
[coreboot.git] / src / mainboard / via / epia-m700 / wakeup.c
1 /*
2  * This file is part of the coreboot project.
3  *
4  * Copyright (C) 2008 Rudolf Marek <r.marek@assembler.cz>
5  * Copyright (C) 2009 One Laptop per Child, Association, Inc.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
20  */
21
22 /* Parts of this code is taken from reboot.c from Linux. */
23
24 /*
25  * This file mostly copied from Rudolf's S3 patch, some changes in
26  * acpi_jump_wake().
27  */
28
29 #include <stdint.h>
30 #include <string.h>
31 #include <arch/io.h>
32 #include <console/console.h>
33 #include <delay.h>
34 #include "wakeup.h"
35
36 int enable_a20(void);
37
38 /*
39  * The following code and data reboots the machine by switching to real
40  * mode and jumping to the BIOS reset entry point, as if the CPU has
41  * really been reset. The previous version asked the keyboard
42  * controller to pulse the CPU reset line, which is more thorough, but
43  * doesn't work with at least one type of 486 motherboard. It is easy
44  * to stop this code working; hence the copious comments.
45  */
46
47 static unsigned long long real_mode_gdt_entries[3] = {
48         0x0000000000000000ULL,  /* Null descriptor */
49         0x00009a000000ffffULL,  /* 16-bit real-mode 64k code at 0x00000000 */
50         0x000092000100ffffULL   /* 16-bit real-mode 64k data at 0x00000100 */
51 };
52
53 struct Xgt_desc_struct {
54         unsigned short size;
55         unsigned long address __attribute__ ((packed));
56         unsigned short pad;
57 } __attribute__ ((packed));
58
59 static struct Xgt_desc_struct real_mode_gdt = {
60         sizeof(real_mode_gdt_entries) - 1,
61         (long)real_mode_gdt_entries
62 },
63 real_mode_idt = {0x3ff, 0},
64 no_idt = { 0, 0 };
65
66 /*
67  * This is 16-bit protected mode code to disable paging and the cache,
68  * switch to real mode and jump to the BIOS reset code.
69  *
70  * The instruction that switches to real mode by writing to CR0 must be
71  * followed immediately by a far jump instruction, which set CS to a
72  * valid value for real mode, and flushes the prefetch queue to avoid
73  * running instructions that have already been decoded in protected
74  * mode.
75  *
76  * Clears all the flags except ET, especially PG (paging), PE
77  * (protected-mode enable) and TS (task switch for coprocessor state
78  * save). Flushes the TLB after paging has been disabled. Sets CD and
79  * NW, to disable the cache on a 486, and invalidates the cache. This
80  * is more like the state of a 486 after reset. I don't know if
81  * something else should be done for other chips.
82  *
83  * More could be done here to set up the registers as if a CPU reset had
84  * occurred; hopefully real BIOSs don't assume much.
85  */
86
87 //      0x66, 0x0d, 0x00, 0x00, 0x00, 0x60,     /* orl $0x60000000, %eax */
88
89 static unsigned char real_mode_switch[] = {
90         0x66, 0x0f, 0x20, 0xc0,                 /* movl %cr0,%eax */
91         0x24, 0xfe,                             /* andb $0xfe,al */
92         0x66, 0x0f, 0x22, 0xc0                  /* movl %eax,%cr0 */
93 };
94
95 static unsigned char jump_to_wakeup[] = {
96         0xea, 0x00, 0x00, 0x00, 0xe0            /* ljmp $0xffff, $0x0000 */
97 };
98
99 /*
100  * Switch to real mode and then execute the code
101  * specified by the code and length parameters.
102  * We assume that length will aways be less that 100!
103  */
104 static unsigned char show31[6] = {
105         0xb0, 0x31, 0xe6, 0x80, 0xeb, 0xFA      /* ljmp $0xffff, $0x0000 */
106 };
107
108 static unsigned char show32[6] = {
109         0xb0, 0x32, 0xe6, 0x80, 0xeb, 0xFA      /* ljmp $0xffff, $0x0000 */
110 };
111
112 void acpi_jump_wake(u32 vector)
113 {
114         u32 tmp, dwEip;
115         u16 tmpvector;
116         u8 Data;
117         struct Xgt_desc_struct *wake_thunk16_Xgt_desc;
118
119         printk(BIOS_DEBUG, "IN ACPI JUMP WAKE TO %x\n", vector);
120         if (enable_a20())
121                 die("failed to enable A20\n");
122         printk(BIOS_DEBUG, "IN ACPI JUMP WAKE TO 3 %x\n", vector);
123
124         *((u16 *) (jump_to_wakeup + 3)) = (u16) (vector >> 4);
125         printk(BIOS_DEBUG, "%x %x %x %x %x\n", jump_to_wakeup[0], jump_to_wakeup[1],
126                      jump_to_wakeup[2], jump_to_wakeup[3], jump_to_wakeup[4]);
127
128         memcpy((void *)(WAKE_THUNK16_ADDR - sizeof(real_mode_switch) - 100),
129                real_mode_switch, sizeof(real_mode_switch));
130         memcpy((void *)(WAKE_THUNK16_ADDR - 100), jump_to_wakeup,
131                sizeof(jump_to_wakeup));
132
133         //jason_tsc_count();
134         printk(BIOS_EMERG, "file '%s', line %d\n\n", __FILE__, __LINE__);
135         //jason_tsc_count_end();
136
137         unsigned long long *real_mode_gdt_entries_at_eseg;
138         real_mode_gdt_entries_at_eseg = WAKE_THUNK16_GDT;               /* Copy from real_mode_gdt_entries and change limition to 1M and data base to 0; */
139         real_mode_gdt_entries_at_eseg[0] = 0x0000000000000000ULL;       /* Null descriptor */
140         real_mode_gdt_entries_at_eseg[1] = 0x000f9a000000ffffULL;       /* 16-bit real-mode 1M code at 0x00000000 */
141         real_mode_gdt_entries_at_eseg[2] = 0x000f93000000ffffULL;       /* 16-bit real-mode 1M data at 0x00000000 */
142
143         wake_thunk16_Xgt_desc = WAKE_THUNK16_XDTR;
144         wake_thunk16_Xgt_desc[0].size = sizeof(real_mode_gdt_entries) - 1;
145         wake_thunk16_Xgt_desc[0].address = (long)real_mode_gdt_entries_at_eseg;
146         wake_thunk16_Xgt_desc[1].size = 0x3ff;
147         wake_thunk16_Xgt_desc[1].address = 0;
148         wake_thunk16_Xgt_desc[2].size = 0;
149         wake_thunk16_Xgt_desc[2].address = 0;
150
151         /* Added this code to get current value of EIP. */
152         __asm__ volatile (
153                 "calll geip\n\t"
154                 "geip: \n\t"
155                 "popl %0\n\t"
156                 : "=a" (dwEip)
157         );
158
159         unsigned char *dest, *src;
160         src = (unsigned char *)dwEip;
161         dest = WAKE_RECOVER1M_CODE;
162         u32 i;
163         for (i = 0; i < 0x200; i++)
164                 dest[i] = src[i];
165
166         __asm__ __volatile__("ljmp $0x0010,%0"  /* 08 error */
167                              ::"i"((void *)(WAKE_RECOVER1M_CODE + 0x20)));
168
169         /* Added 0x20 "nop" to make sure the ljmp will not jump then halt. */
170         asm volatile ("nop");
171         asm volatile ("nop");
172         asm volatile ("nop");
173         asm volatile ("nop");
174         asm volatile ("nop");
175         asm volatile ("nop");
176         asm volatile ("nop");
177         asm volatile ("nop");
178         asm volatile ("nop");
179         asm volatile ("nop");
180
181         asm volatile ("nop");
182         asm volatile ("nop");
183         asm volatile ("nop");
184         asm volatile ("nop");
185         asm volatile ("nop");
186         asm volatile ("nop");
187         asm volatile ("nop");
188         asm volatile ("nop");
189         asm volatile ("nop");
190         asm volatile ("nop");
191
192         asm volatile ("nop");
193         asm volatile ("nop");
194         asm volatile ("nop");
195         asm volatile ("nop");
196         asm volatile ("nop");
197         asm volatile ("nop");
198         asm volatile ("nop");
199         asm volatile ("nop");
200         asm volatile ("nop");
201         asm volatile ("nop");
202
203         __asm__ volatile (
204                 /*
205                  * Set new esp, maybe ebp should not equal to esp?, due to the
206                  * variable in acpi_jump_wake?, anyway, this may be not a big
207                  * problem. and I didn't clear the area (ef000+-0x200) to zero.
208                  */
209                 "movl %0, %%ebp\n\t"
210                 "movl %0, %%esp\n\t"::"a" (WAKE_THUNK16_STACK)
211         );
212
213         /*
214          * Only "src" and "dest" use the new stack, and the esp maybe also
215          * used in resumevector.
216          */
217 #if PAYLOAD_IS_SEABIOS == 1
218         /* WAKE_MEM_INFO inited in get_set_top_available_mem in tables.c. */
219         src =
220             (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024 - 0x100000);
221         dest = 0;
222
223         /*
224          * If recovered 0-e0000, then when resume, before WinXP turn on the
225          * desktop screen, there is gray background which last 1sec.
226          */
227         for (i = 0; i < 0xa0000; i++)
228                 dest[i] = src[i];
229
230 #if 0
231         __asm__ volatile (
232                 "movl %0, %%esi\n\t"
233                 "movl $0, %%edi\n\t"
234                 "movl $0xa0000, %%ecx\n\t"
235                 "shrl $2, %%ecx\n\t"
236                 "rep movsd\n\t"
237                 ::"a"(src)
238         );
239 #endif
240         src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
241                         - 0x100000 + 0xc0000);
242
243 #if 0
244         dest = 0xc0000;
245         for (i = 0; i < 0x20000; i++)
246               dest[i] = src[i];
247
248         __asm__ volatile (
249                 "movl %0, %%esi\n\t"
250                 "movl $0xc0000, %%edi\n\t"
251                 "movl $0x20000, %%ecx\n\t"
252                 "shrl $2, %%ecx\n\t"
253                 "rep movsd\n\t"
254                 ::"a"(src)
255         );
256 #endif
257
258         src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
259                         - 0x100000 + 0xe0000 + WAKE_SPECIAL_SIZE);
260
261         /* dest = 0xf0000; */
262         /* for (i = 0; i < 0x10000; i++) */
263         /*      dest[i] = src[i]; */
264         __asm__ volatile (
265                 "movl %0, %%esi\n\t"
266                 "movl %1, %%edi\n\t"
267                 "movl %2, %%ecx\n\t"
268                 "shrl $2, %%ecx\n\t"
269                 "rep movsd\n\t"::"r" (src),
270                 "r"(0xe0000 + WAKE_SPECIAL_SIZE),
271                 "r"(0x10000 - WAKE_SPECIAL_SIZE)
272         );
273
274         src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
275                         - 0x100000 + 0xf0000);
276         /* dest = 0xf0000; */
277         /* for (i = 0; i < 0x10000; i++) */
278         /*      dest[i] = src[i]; */
279         __asm__ volatile (
280                 "movl %0, %%esi\n\t"
281                 "movl $0xf0000, %%edi\n\t"
282                 "movl $0x10000, %%ecx\n\t"
283                 "shrl $2, %%ecx\n\t" "rep movsd\n\t"::"a" (src)
284         );
285
286         asm volatile ("wbinvd");
287 #endif
288         /* Set up the IDT for real mode. */
289         asm volatile ("lidt %0"::"m" (wake_thunk16_Xgt_desc[1]));
290
291         /*
292          * Set up a GDT from which we can load segment descriptors for real
293          * mode. The GDT is not used in real mode; it is just needed here to
294          * prepare the descriptors.
295          */
296         asm volatile ("lgdt %0"::"m" (wake_thunk16_Xgt_desc[0]));
297
298         /*
299          * Load the data segment registers, and thus the descriptors ready for
300          * real mode.  The base address of each segment is 0x100, 16 times the
301          * selector value being loaded here.  This is so that the segment
302          * registers don't have to be reloaded after switching to real mode:
303          * the values are consistent for real mode operation already.
304          */
305         __asm__ __volatile__(
306                 "movl $0x0010,%%eax\n"
307                 "\tmovl %%eax,%%ds\n"
308                 "\tmovl %%eax,%%es\n"
309                 "\tmovl %%eax,%%fs\n"
310                 "\tmovl %%eax,%%gs\n"
311                 "\tmovl %%eax,%%ss":::"eax"
312         );
313
314         /*
315          * Jump to the 16-bit code that we copied earlier. It disables paging
316          * and the cache, switches to real mode, and jumps to the BIOS reset
317          * entry point.
318          */
319
320         __asm__ __volatile__(
321                 "ljmp $0x0008,%0"::"i"
322                 ((void *)(WAKE_THUNK16_ADDR - sizeof(real_mode_switch) - 100))
323         );
324 }
325
326 /* -*- linux-c -*- ------------------------------------------------------- *
327  *
328  *   Copyright (C) 1991, 1992 Linus Torvalds
329  *   Copyright 2007 rPath, Inc. - All Rights Reserved
330  *
331  *   This file is part of the Linux kernel, and is made available under
332  *   the terms of the GNU General Public License version 2.
333  *
334  * ----------------------------------------------------------------------- */
335
336 /*
337  * arch/i386/boot/a20.c
338  *
339  * Enable A20 gate (return -1 on failure)
340  */
341
342 // #include "boot.h"
343
344 #define MAX_8042_LOOPS  100000
345
346 static int empty_8042(void)
347 {
348         u8 status;
349         int loops = MAX_8042_LOOPS;
350
351         while (loops--) {
352                 udelay(1);
353
354                 status = inb(0x64);
355                 if (status & 1) {
356                         /* Read and discard input data */
357                         udelay(1);
358                         (void)inb(0x60);
359                 } else if (!(status & 2)) {
360                         /* Buffers empty, finished! */
361                         return 0;
362                 }
363         }
364
365         return -1;
366 }
367
368 /* Returns nonzero if the A20 line is enabled.  The memory address
369    used as a test is the int $0x80 vector, which should be safe. */
370
371 #define A20_TEST_ADDR   (4*0x80)
372 #define A20_TEST_SHORT  32
373 #define A20_TEST_LONG   2097152 /* 2^21 */
374
375 static int a20_test(int loops)
376 {
377         int ok = 0;
378         int saved, ctr;
379
380 //      set_fs(0x0000);
381 //      set_gs(0xffff);
382
383         saved = ctr = *((u32 *) A20_TEST_ADDR);
384
385         while (loops--) {
386                 //wrfs32(++ctr, A20_TEST_ADDR);
387
388                 *((u32 *) A20_TEST_ADDR) = ++ctr;
389
390                 udelay(1);      /* Serialize and make delay constant */
391
392                 ok = *((u32 *) A20_TEST_ADDR + 0xffff0 + 0x10) ^ ctr;
393                 if (ok)
394                         break;
395         }
396
397         *((u32 *) A20_TEST_ADDR) = saved;
398         return ok;
399 }
400
401 /* Quick test to see if A20 is already enabled */
402 static int a20_test_short(void)
403 {
404         return a20_test(A20_TEST_SHORT);
405 }
406
407 /* Longer test that actually waits for A20 to come on line; this
408    is useful when dealing with the KBC or other slow external circuitry. */
409 static int a20_test_long(void)
410 {
411         return a20_test(A20_TEST_LONG);
412 }
413
414 static void enable_a20_kbc(void)
415 {
416         empty_8042();
417
418         outb(0xd1, 0x64);       /* Command write */
419         empty_8042();
420
421         outb(0xdf, 0x60);       /* A20 on */
422         empty_8042();
423 }
424
425 static void enable_a20_fast(void)
426 {
427         u8 port_a;
428
429         port_a = inb(0x92);     /* Configuration port A */
430         port_a |= 0x02;         /* Enable A20 */
431         port_a &= ~0x01;        /* Do not reset machine */
432         outb(port_a, 0x92);
433 }
434
435 /*
436  * Actual routine to enable A20; return 0 on ok, -1 on failure
437  */
438
439 #define A20_ENABLE_LOOPS 255    /* Number of times to try */
440
441 int enable_a20(void)
442 {
443         int loops = A20_ENABLE_LOOPS;
444
445         while (loops--) {
446                 /* First, check to see if A20 is already enabled
447                    (legacy free, etc.) */
448                 if (a20_test_short())
449                         return 0;
450
451                 /* Try enabling A20 through the keyboard controller */
452                 empty_8042();
453
454                 // if (a20_test_short())
455                 //      return 0; /* BIOS worked, but with delayed reaction */
456
457                 enable_a20_kbc();
458                 if (a20_test_long())
459                         return 0;
460
461                 /* Finally, try enabling the "fast A20 gate" */
462                 enable_a20_fast();
463                 if (a20_test_long())
464                         return 0;
465         }
466
467         return -1;
468 }