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>
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
+
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
_______________________________________________
Devel mailing list
[email protected]
http://mailman.laptop.org/mailman/listinfo/devel

Reply via email to