On 04/01/07 18:53 -0700, Jordan Crouse wrote: > > This patch adds code which may just about manage to work in some > > respect. Possibly. I wouldn't really expect it to, though. Assuming I > > haven't done anything stupid, once this patch is applied, > > /sys/power/state should show mem. Echoing mem into it may (or may not) > > cause the machine to sequence into some sort of suspended state. > > And there you go. The rest of the code should be pretty clear - I've > tried to comment it as best I can. I'm sure future versions of this patch > will follow, but please comment if you can, or catch me on IRC.
And I wasn't lying. I've attached another version of the patch which toss in two additional clock delays I had missed before. This version actually does something - and that something is that it immediately reboots the machine. I'm not sure if we're actually sleeping, and a resume event in the CS5536 is immediately firing, or if the EC sees MAIN_ON go down and thinks that an emergency is happening, or some third alternative we haven't thought of. Please look over the code and let me know if I missed something obvious. 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 @@ -8,25 +8,47 @@ #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 +*/ + +/* These are the wake sources that we will use to wake the system back up + For now, just enable the power button - later we may do the RTC as well. + We can also set up any number of the GPE0 bits too - though thats a different register +*/ + +#define OLPC_PM1_WAKE_SOURCES 0x100 + extern int machine_is_olpc; #define PM_IRQ 3 - #define MSR_LBAR_ACPI 0x5140000E #define MSR_LBAR_PMS 0x5140000F +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); /* It has to be 0x100 for now because we don't permit anything else */ if (sts & 0x100) { @@ -42,6 +64,78 @@ static int olpc_pm_interrupt(int irq, vo 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; +} + +#define PM_WKXD_VAL 0x400000a0 + +/* 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" + "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)); +} + +int asmlinkage +olpc_enter_sleep_state(u8 sleep_state) +{ + u32 reg; + + /* Set up the PMC sleep clock */ + outl(0x40000e00, pms_base + 0x10); + + /* Set up the Sleep End Delay */ + outl(0x40004601, pms_base + 0x14); + + /* Set up the WORK_AUX delay */ + outl(0x400000a0, pms_base + 0x34); + + /* Clear PM_SSC */ + outl(0x2FFFF, pms_base + 0x54); + + /* FIXME: Clear the pending GPE events if we end up using any */ + + /* Clear any pending PM1 events */ + outl(inl(acpi_base) | 0xFFFF, acpi_base); + + /* Acutally go to sleep */ + olpc_sleep_asm(); + + return 0; +} + static int __init olpc_pm_init(void) { uint32_t lo, hi; @@ -106,8 +200,7 @@ static int __init olpc_pm_init(void) return ret; } - /* Set only power button to generate SCI */ - outw(0x100, acpi_base + 2); + outl(inl(acpi_base) | OLPC_PM1_WAKE_SOURCES << 16, acpi_base); /* Select level triggered in PIC */ if (sci < 8) { @@ -120,21 +213,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 +
_______________________________________________ Devel mailing list [email protected] http://mailman.laptop.org/mailman/listinfo/devel
