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