To sleep, perchance to dream (try #3)
Jordan Crouse
jordan.crouse at amd.com
Tue Jan 9 18:27:56 EST 2007
If at first you don't suspend, try, try again.
After several aborted tries, we finally have some happily sleeping code.
It even wakes up - though much like myself after a night in Vegas, it
doesn't remember where it is. Luckily for me, my amnesia usually isn't
permanent.. :)
I won't bore you with the details unless you really want them, but suffice
to say, VSA was setting up the descriptors wrong. Also, we had missed a
GPIO setup to allow us to actually turn off VCORE_CPU.
Attached is the kernel patch, and also a OFW patch for Mitch that plugs
in the correct descriptor and GPIO values. The next step is to get the
resume address written into memory and make LinuxBIOS/OFW do the
right thing to bring us back to the kernel.
Jordan
--
Jordan Crouse
Senior Linux Engineer
Advanced Micro Devices, Inc.
<www.amd.com/embeddedprocessors>
-------------- next part --------------
Index: git/arch/i386/kernel/Makefile
===================================================================
--- git.orig/arch/i386/kernel/Makefile
+++ git/arch/i386/kernel/Makefile
@@ -47,7 +47,7 @@ EXTRA_AFLAGS := -traditional
obj-$(CONFIG_SCx200) += scx200.o
obj-$(CONFIG_OLPC) += olpc.o
-obj-$(CONFIG_OLPC_PM) += olpc-pm.o
+obj-$(CONFIG_OLPC_PM) += olpc-pm.o olpc-wakeup.o
# vsyscall.o contains the vsyscall DSO images as __initdata.
# We must build both images before we can assemble it.
Index: git/arch/i386/kernel/olpc-pm.c
===================================================================
--- git.orig/arch/i386/kernel/olpc-pm.c
+++ git/arch/i386/kernel/olpc-pm.c
@@ -1,5 +1,6 @@
/* olpc-pm.c
* �� 2006 Red Hat, Inc.
+ * Portions also copyright 2006 Advanced Micro Devices, Inc.
* GPLv2
*/
@@ -8,40 +9,180 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/input.h>
+#include <linux/suspend.h>
+#include <linux/bootmem.h>
#include <asm/io.h>
+/* A few words about accessing the ACPI and PM registers. Long story short,
+ byte and word accesses of the ACPI and PM registers is broken. The only
+ way to do it really correctly is to use dword accesses, which we do
+ throughout this code. For more details, please consult Eratta 17 and 18
+ here:
+
+ http://www.amd.com/files/connectivitysolutions/geode/geode_gx/34472D_CS5536_B1_specupdate.pdf
+*/
+
extern int machine_is_olpc;
#define PM_IRQ 3
-
+#define MSR_LBAR_GPIO 0x5140000C
#define MSR_LBAR_ACPI 0x5140000E
#define MSR_LBAR_PMS 0x5140000F
+#define CS5536_PM_PWRBTN (1 << 8)
+#define CS5536_PM_RTC (1 << 10)
+
+unsigned long olpc_wakeup_address = 0;
+extern char wakeup_start, wakeup_end;
+extern void do_olpc_suspend_lowlevel(void);
+extern unsigned long FASTCALL(olpc_copy_wakeup_routine(unsigned long));
+
static unsigned long acpi_base;
static unsigned long pms_base;
static int sci;
static struct input_dev *pm_inputdev;
+static struct pm_ops olpc_pm_ops;
static int olpc_pm_interrupt(int irq, void *id)
{
- uint16_t sts = inw(acpi_base);
- outw(sts, acpi_base);
+ uint32_t sts = inl(acpi_base);
+ outl(sts | 0xFFFF, acpi_base);
+
+ /* only accept the power button as an event */
- /* It has to be 0x100 for now because we don't permit anything else */
- if (sts & 0x100) {
+ if (sts & CS5536_PM_PWRBTN) {
input_report_key(pm_inputdev, KEY_POWER, 1);
input_sync(pm_inputdev);
/* Do we need to delay this (and hence schedule_work)? */
input_report_key(pm_inputdev, KEY_POWER, 0);
input_sync(pm_inputdev);
} else {
- printk(KERN_WARNING "Strange PM1_STS %x\n", sts);
+ printk(KERN_WARNING "olcp-pm: Strange PM1_STS %x\n", sts);
}
return IRQ_HANDLED;
}
+static int olpc_pm_state_valid (suspend_state_t pm_state)
+{
+ if (pm_state == PM_SUSPEND_MEM)
+ return 1;
+
+ return 0;
+}
+
+static int olpc_pm_enter (suspend_state_t pm_state)
+{
+ /* Only STR is supported */
+ if (pm_state != PM_SUSPEND_MEM)
+ return -EINVAL;
+
+ /* Save CPU state */
+ do_olpc_suspend_lowlevel();
+ return 0;
+}
+
+/* Put the memory into self refresh and go to sleep */
+
+static inline void olpc_sleep_asm(void)
+{
+ __asm__ __volatile__( "add $0x08, %%bx\n\t"
+ "movw %%bx, %%dx\n\t"
+ "inl %%dx, %%eax\n\t"
+ "or $0x2000, %%ax\n\t"
+ "movw %%ax, %%di\n\t"
+ "wbinvd\n\t"
+ "movl $0x20000018, %%ecx\n\t"
+ "rdmsr\n\t"
+ "and $0xFF0000FF, %%eax\n\t"
+ "wrmsr\n\t"
+ "movw $0x2004, %%cx\n\t"
+ "xor %%edx, %%edx\n\t"
+ "xor %%eax, %%eax\n\t"
+ "movb $0x04, %%al\n\t"
+ "wrmsr\n\t"
+ "movw %%bx, %%dx\n\t"
+ "movzx %%di, %%eax\n\t"
+ "outl %%eax, %%dx\n\t"
+ : : "b" (acpi_base));
+}
+
+/* [29:0] is the delay */
+#define PM_SCLK_VAL 0x40000e00
+
+/* [29:0] is the delay */
+#define PM_SED_VAL 0x40004601
+
+/* [19:0] is the delay */
+#define PM_WKXD_VAL 0x040000a0
+
+int asmlinkage
+olpc_enter_sleep_state(u8 sleep_state)
+{
+ u32 reg;
+
+ /* Set up the PMC sleep clock */
+ outl(PM_SCLK_VAL, pms_base + 0x10);
+
+ /* Set up the Sleep End Delay */
+ outl(PM_SED_VAL, pms_base + 0x14);
+
+ /* Set up the WORK_AUX delay */
+ outl(PM_WKXD_VAL, pms_base + 0x34);
+
+ /* Clear PM_SSC */
+ outl(0x2FFFF, pms_base + 0x54);
+
+ /* Save ourselves a read by setting the SCI events again here -
+ * we have to write the register anyway */
+
+ /* FIXME: Set any other SCI events that we might want here */
+
+ outl((CS5536_PM_PWRBTN << 16) | 0xFFFF, acpi_base);
+
+ /* FIXME: Set any GPE events that we want here */
+ /* FIXME: Clear pending GPE events if we end up using any */
+
+ /* Actually go to sleep */
+ olpc_sleep_asm();
+
+ return 0;
+}
+
+/* This code will slowly disappear as we fixup the issues in the BIOS */
+
+static void __init olpc_fixup_bios(void)
+{
+ unsigned long hi, lo, base;
+
+ /* The VSA aggressively sets up the ACPI and PM register for
+ * trapping - its not enough to force these values in the BIOS -
+ * they seem to be changed during PCI init as well.
+ */
+
+ /* Change the PM registers to decode to the DD */
+
+ hi = 0x80000001;
+ lo = 0x400fff80;
+ wrmsr(0x510100e2, lo, hi);
+
+ /* Change the ACPI registers to decode to the DD */
+
+ hi = 0x80000001;
+ lo = 0x840ffff0;
+ wrmsr(0x510100e3, lo, hi);
+
+ /* GPIO24 needs to be set to its auxillary function as
+ * the WORK_AUX signal */
+
+ rdmsr(MSR_LBAR_GPIO, lo, hi);
+
+ base = lo & 0x0000ffff;
+ outl(0x100, base + 0x90);
+ outl(0x100, base + 0x84);
+}
+
static int __init olpc_pm_init(void)
{
uint32_t lo, hi;
@@ -51,8 +192,11 @@ static int __init olpc_pm_init(void)
return -ENODEV;
pm_inputdev = input_allocate_device();
+
if (!pm_inputdev)
return -ENOMEM;
+
+ olpc_fixup_bios();
rdmsr(MSR_LBAR_ACPI, lo, hi);
/* Check the mask and whether GPIO is enabled (sanity check) */
@@ -71,7 +215,7 @@ static int __init olpc_pm_init(void)
pms_base = lo & 0x0000ffff;
lo = inl(pms_base + 0x40);
- printk("PM_FSD was %08x\n", lo);
+
/* Lock, enable failsafe, 4 seconds */
outl(0xc001f400, pms_base + 0x40);
@@ -106,8 +250,12 @@ static int __init olpc_pm_init(void)
return ret;
}
- /* Set only power button to generate SCI */
- outw(0x100, acpi_base + 2);
+ /* Here we set up the SCI events we're interested in during
+ * real-time. We have no sleep button, and the RTC doesn't make
+ * sense, so set up the power button
+ */
+
+ outl(inl(acpi_base) | ((CS5536_PM_PWRBTN) << 16), acpi_base);
/* Select level triggered in PIC */
if (sci < 8) {
@@ -120,21 +268,40 @@ static int __init olpc_pm_init(void)
outb(lo, 0x4d1);
}
/* Clear pending interrupt */
- outw(inw(acpi_base), acpi_base);
+ outl(inl(acpi_base) | 0xFFFF, acpi_base);
+
+#if 0
+ olpc_wakeup_address = (unsigned long)alloc_bootmem_low(PAGE_SIZE);
+
+ /* FIXME: stuff this in a register so the firmware can read it
+ at resume time */
+
+ if (olpc_wakeup_address) {
+ memcpy((void *)olpc_wakeup_address, &wakeup_start,
+ &wakeup_end - &wakeup_start);
+ olpc_copy_wakeup_routine(olpc_wakeup_address);
+ }
+#endif
+
+ pm_set_ops(&olpc_pm_ops);
return 0;
}
-
static void olpc_pm_exit(void)
{
- /* Disable all events */
- outw(0, acpi_base+2);
+ /* Clear any pending events, and disable them */
+ outl(0xFFFF, acpi_base+2);
free_irq(sci, &acpi_base);
input_unregister_device(pm_inputdev);
}
+static struct pm_ops olpc_pm_ops = {
+ .valid = olpc_pm_state_valid,
+ .enter = olpc_pm_enter,
+};
+
module_init(olpc_pm_init);
module_exit(olpc_pm_exit);
Index: git/arch/i386/kernel/olpc-wakeup.S
===================================================================
--- /dev/null
+++ git/arch/i386/kernel/olpc-wakeup.S
@@ -0,0 +1,228 @@
+.text
+#include <linux/linkage.h>
+#include <asm/segment.h>
+#include <asm/page.h>
+
+#
+# wakeup_code runs in real mode, and at unknown address (determined at run-time).
+# Therefore it must only use relative jumps/calls.
+#
+# Do we need to deal with A20? It is okay: ACPI specs says A20 must be enabled
+#
+# If physical address of wakeup_code is 0x12345, BIOS should call us with
+# cs = 0x1234, eip = 0x05
+#
+
+ALIGN
+ .align 4096
+ENTRY(wakeup_start)
+wakeup_code:
+ wakeup_code_start = .
+ .code16
+
+ movw $0xb800, %ax
+ movw %ax,%fs
+
+ cli
+ cld
+
+ # setup data segment
+ movw %cs, %ax
+ movw %ax, %ds # Make ds:0 point to wakeup_start
+ movw %ax, %ss
+ mov $(wakeup_stack - wakeup_code), %sp # Private stack is needed for ASUS board
+
+ pushl $0 # Kill any dangerous flags
+ popfl
+
+ movl real_magic - wakeup_code, %eax
+ cmpl $0x12345678, %eax
+ jne bogus_real_magic
+
+ # set up page table
+ movl $swsusp_pg_dir-__PAGE_OFFSET, %eax
+ movl %eax, %cr3
+
+ testl $1, real_efer_save_restore - wakeup_code
+ jz 4f
+ # restore efer setting
+ movl real_save_efer_edx - wakeup_code, %edx
+ movl real_save_efer_eax - wakeup_code, %eax
+ mov $0xc0000080, %ecx
+ wrmsr
+4:
+ # make sure %cr4 is set correctly (features, etc)
+ movl real_save_cr4 - wakeup_code, %eax
+ movl %eax, %cr4
+ movw $0xb800, %ax
+ movw %ax,%fs
+
+ # need a gdt -- use lgdtl to force 32-bit operands, in case
+ # the GDT is located past 16 megabytes.
+ lgdtl real_save_gdt - wakeup_code
+
+ movl real_save_cr0 - wakeup_code, %eax
+ movl %eax, %cr0
+ jmp 1f
+1:
+
+ movl real_magic - wakeup_code, %eax
+ cmpl $0x12345678, %eax
+ jne bogus_real_magic
+
+ ljmpl $__KERNEL_CS,$wakeup_pmode_return
+
+real_save_gdt: .word 0
+ .long 0
+real_save_cr0: .long 0
+real_save_cr3: .long 0
+real_save_cr4: .long 0
+real_magic: .long 0
+real_efer_save_restore: .long 0
+real_save_efer_edx: .long 0
+real_save_efer_eax: .long 0
+
+bogus_real_magic:
+ jmp bogus_real_magic
+
+setbad: clc
+ ret
+
+_setbad: jmp setbad
+
+ .code32
+ ALIGN
+
+.org 0x800
+wakeup_stack_begin: # Stack grows down
+
+.org 0xff0 # Just below end of page
+wakeup_stack:
+ENTRY(wakeup_end)
+
+.org 0x1000
+
+wakeup_pmode_return:
+ movw $__KERNEL_DS, %ax
+ movw %ax, %ss
+ movw %ax, %ds
+ movw %ax, %es
+ movw %ax, %fs
+ movw %ax, %gs
+
+ # reload the gdt, as we need the full 32 bit address
+ lgdt saved_gdt
+ lidt saved_idt
+ lldt saved_ldt
+ ljmp $(__KERNEL_CS),$1f
+1:
+ movl %cr3, %eax
+ movl %eax, %cr3
+ wbinvd
+
+ # and restore the stack ... but you need gdt for this to work
+ movl saved_context_esp, %esp
+
+ movl %cs:saved_magic, %eax
+ cmpl $0x12345678, %eax
+ jne bogus_magic
+
+ # jump to place where we left off
+ movl saved_eip,%eax
+ jmp *%eax
+
+bogus_magic:
+ jmp bogus_magic
+
+
+##
+# olpc_copy_wakeup_routine
+#
+# Copy the above routine to low memory.
+#
+# Parameters:
+# %eax: place to copy wakeup routine to
+#
+# Returned address is location of code in low memory (past data and stack)
+#
+ENTRY(olpc_copy_wakeup_routine)
+
+ sgdt saved_gdt
+ sidt saved_idt
+ sldt saved_ldt
+ str saved_tss
+
+ movl nx_enabled, %edx
+ movl %edx, real_efer_save_restore - wakeup_start (%eax)
+ testl $1, real_efer_save_restore - wakeup_start (%eax)
+ jz 2f
+ # save efer setting
+ pushl %eax
+ movl %eax, %ebx
+ mov $0xc0000080, %ecx
+ rdmsr
+ movl %edx, real_save_efer_edx - wakeup_start (%ebx)
+ movl %eax, real_save_efer_eax - wakeup_start (%ebx)
+ popl %eax
+2:
+
+ movl %cr3, %edx
+ movl %edx, real_save_cr3 - wakeup_start (%eax)
+ movl %cr4, %edx
+ movl %edx, real_save_cr4 - wakeup_start (%eax)
+ movl %cr0, %edx
+ movl %edx, real_save_cr0 - wakeup_start (%eax)
+ sgdt real_save_gdt - wakeup_start (%eax)
+
+ movl $0x12345678, real_magic - wakeup_start (%eax)
+ movl $0x12345678, saved_magic
+ ret
+
+save_registers:
+ leal 4(%esp), %eax
+ movl %eax, saved_context_esp
+ movl %ebx, saved_context_ebx
+ movl %ebp, saved_context_ebp
+ movl %esi, saved_context_esi
+ movl %edi, saved_context_edi
+ pushfl ; popl saved_context_eflags
+
+ movl $ret_point, saved_eip
+ ret
+
+
+restore_registers:
+ movl saved_context_ebp, %ebp
+ movl saved_context_ebx, %ebx
+ movl saved_context_esi, %esi
+ movl saved_context_edi, %edi
+ pushl saved_context_eflags ; popfl
+ ret
+
+ENTRY(do_olpc_suspend_lowlevel)
+ call save_processor_state
+ call save_registers
+ pushl $3
+ call olpc_enter_sleep_state
+ addl $4, %esp
+
+# In case of S3 failure, we'll emerge here. Jump
+# to ret_point to recover
+ jmp ret_point
+ .p2align 4,,7
+ret_point:
+ call restore_registers
+ call restore_processor_state
+ ret
+
+.data
+ALIGN
+ENTRY(saved_magic) .long 0
+ENTRY(saved_eip) .long 0
+
+# saved registers
+saved_gdt: .long 0,0
+saved_idt: .long 0,0
+saved_ldt: .long 0
+saved_tss: .long 0
+
-------------- next part --------------
Index: svn/cpu/x86/pc/olpc/chipinit.fth
===================================================================
--- svn.orig/cpu/x86/pc/olpc/chipinit.fth 2007-01-03 12:58:36.000000000 -0700
+++ svn/cpu/x86/pc/olpc/chipinit.fth 2007-01-09 10:42:45.000000000 -0700
@@ -361,8 +361,8 @@
msr: 5101.0024 400000fe.01bfffff. \ P2D_BMK Descriptor 1 UHCI
msr: 5101.00e0 60000000.1f0ffff8. \ IOD_BM Descriptor 0 ATA IO address
msr: 5101.00e1 a0000001.480fff80. \ IOD_BM Descriptor 1
-msr: 5101.00e2 00000001.400fff80. \ IOD_BM Descriptor 2
-msr: 5101.00e3 00000001.840ffff0. \ IOD_BM Descriptor 3
+msr: 5101.00e2 80000001.400fff80. \ IOD_BM Descriptor 2
+msr: 5101.00e3 80000001.840ffff0. \ IOD_BM Descriptor 3
msr: 5101.00e4 00000001.858ffff8. \ IOD_BM Descriptor 4
msr: 5101.00e5 60000001.8a0ffff0. \ IOD_BM Descriptor 5
msr: 5101.00eb 00000000.f0301850. \ IOD_SC Descriptor 1
@@ -538,10 +538,10 @@
h# 0000 h# 107c pw! \ GPIO_05_EVENT_COUNT
h# 6066 h# 107e pw! \ GPIO_05_EVENTCOMPARE_VALUE
\ h# ffff0000 h# 1080 pl! \ GPIOH_ OUTPUT_VALUE
- h# 660000a6 h# 1084 pl! \ GPIOH_OUTPUT_ENABLE
+ h# 660001a6 h# 1084 pl! \ GPIOH_OUTPUT_ENABLE
h# ea0fb060 h# 1088 pl! \ GPIOH_OUT_OPENDRAIN
h# 0000a6a8 h# 108c pl! \ GPIOH_OUTPUT_INVERT_ENABLE
- h# 10b06066 h# 1090 pl! \ GPIOH_OUT_AUX1_SELECT
+ h# 10b06166 h# 1090 pl! \ GPIOH_OUT_AUX1_SELECT
h# 00a6a8ea h# 1094 pl! \ GPIOH_OUT_AUX2_SELECT
h# b0606600 h# 1098 pl! \ GPIOH_PULLUP_ENABLE
h# a6a8ea11 h# 109c pl! \ GPIOH_PULLDOWN_ENABLE
More information about the Devel
mailing list