OLPC PM - the early years

Jordan Crouse jordan.crouse at amd.com
Wed Feb 28 19:32:15 EST 2007


Now that Mitch has his fast path resume working, it is time for the kernel
guys to earn their keep too.   OFW now handles the actual suspend and
resume of the core - we call into the subroutine at 0xf0000,  the system
suspends, and upon resume, control comes back to us.  OFW even nicely 
restores us back to protected mode, which simplifies things greatly.

So, replacing what we had previously, we just set up the SCI wakeup bits
(and soon, GPE0 bits, right EC hackers?), save our current processor state,
call into the routine, and the do it all backwards.

I also added suspend/resume functionality for the MFGPT timers, so that
the MFGPT clock event gets correctly restored for NOHZ mode.  The patch is
attached - I'll push it into unstable if nobody cries too hard.

Next up, KFB suspend / resume support and I2C suspend/resume.

Jordan
-- 
Jordan Crouse
Senior Linux Engineer
Advanced Micro Devices, Inc.
<www.amd.com/embeddedprocessors>
-------------- next part --------------
diff --git a/arch/i386/kernel/geode.c b/arch/i386/kernel/geode.c
index 9ee54b6..2e6ec95 100644
--- a/arch/i386/kernel/geode.c
+++ b/arch/i386/kernel/geode.c
@@ -1,5 +1,5 @@
 /* AMD Geode southbridge support code
- * Copyright (C) 2006, Advanced Micro Devices, Inc.
+ * Copyright (C) 2006-2007, Advanced Micro Devices, Inc.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -14,6 +14,8 @@ #include <asm/io.h>
 #include <asm/msr.h>
 #include <asm/geode.h>
 
+#define MFGPT_NAME "geode-mfgpt"
+
 static struct {
 	char *name;
 	u32 msr;
@@ -23,11 +25,10 @@ static struct {
 	{ "geode-pms",   MSR_LBAR_PMS, LBAR_PMS_SIZE, 0 },
 	{ "geode-acpi",  MSR_LBAR_ACPI, LBAR_ACPI_SIZE, 0 },
 	{ "geode-gpio",  MSR_LBAR_GPIO, LBAR_GPIO_SIZE, 0 },
-	{ "geode-mfgpt", MSR_LBAR_MFGPT, LBAR_MFGPT_SIZE, 0 }
+	{ MFGPT_NAME, MSR_LBAR_MFGPT, LBAR_MFGPT_SIZE, 0 }
 };
 
-int
-geode_get_dev_base(unsigned int dev) {
+int geode_get_dev_base(unsigned int dev) {
 	if (dev < ARRAY_SIZE(lbars))
 		return lbars[dev].base;
 	return 0;
@@ -37,8 +38,7 @@ EXPORT_SYMBOL_GPL(geode_get_dev_base);
 
 /* === GPIO API === */
 
-void
-geode_gpio_set(unsigned int gpio, unsigned int reg)
+void geode_gpio_set(unsigned int gpio, unsigned int reg)
 {
 	u32 base = geode_get_dev_base(GEODE_DEV_GPIO);
 
@@ -53,8 +53,7 @@ geode_gpio_set(unsigned int gpio, unsign
 
 EXPORT_SYMBOL_GPL(geode_gpio_set);
 
-void
-geode_gpio_clear(unsigned int gpio, unsigned int reg)
+void geode_gpio_clear(unsigned int gpio, unsigned int reg)
 {
 	u32 base = geode_get_dev_base(GEODE_DEV_GPIO);
 
@@ -69,8 +68,7 @@ geode_gpio_clear(unsigned int gpio, unsi
 
 EXPORT_SYMBOL_GPL(geode_gpio_clear);
 
-int
-geode_gpio_isset(unsigned int gpio, unsigned int reg)
+int geode_gpio_isset(unsigned int gpio, unsigned int reg)
 {
 	u32 base = geode_get_dev_base(GEODE_DEV_GPIO);
 
@@ -85,8 +83,7 @@ geode_gpio_isset(unsigned int gpio, unsi
 
 EXPORT_SYMBOL_GPL(geode_gpio_isset);
 
-void
-geode_gpio_set_pme(unsigned int gpio, int pair) {
+void geode_gpio_set_pme(unsigned int gpio, int pair) {
 	u32 base = geode_get_dev_base(GEODE_DEV_GPIO);
 	u32 offset, shift, val;
 
@@ -119,10 +116,29 @@ static struct mfgpt_timer_t {
 	int index;
 	int flags;
 	struct module *owner;
+	void (*suspend)(int);
+	void (*resume)(int);
 } mfgpt_timers[MFGPT_MAX_TIMERS];
 
-void
-geode_mfgpt_toggle_event(int timer, int cmp, int event, int setup)
+static int geode_mfgpt_suspend(struct sys_device *dev, pm_message_t state)
+{
+	int i;
+	for(i = 0; i < MFGPT_MAX_TIMERS; i++)
+		if (mfgpt_timers[i].owner && mfgpt_timers[i].suspend)
+			mfgpt_timers[i].suspend(i);
+	return 0;
+}
+
+static int geode_mfgpt_resume(struct sys_device *dev)
+{
+	int i;
+	for(i = 0; i < MFGPT_MAX_TIMERS; i++)
+		if (mfgpt_timers[i].owner && mfgpt_timers[i].resume)
+			mfgpt_timers[i].resume(i);
+	return 0;
+}
+
+void geode_mfgpt_toggle_event(int timer, int cmp, int event, int setup)
 {
 	u32 msr, mask, value, dummy;
 	int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
@@ -159,8 +175,7 @@ geode_mfgpt_toggle_event(int timer, int 
 
 EXPORT_SYMBOL(geode_mfgpt_toggle_event);
 
-void
-geode_mfgpt_set_irq(int timer, int cmp, int irq, int setup)
+void geode_mfgpt_set_irq(int timer, int cmp, int irq, int setup)
 {
 	u32 val, dummy;
 	int offset;
@@ -181,26 +196,33 @@ geode_mfgpt_set_irq(int timer, int cmp, 
 	wrmsr(0x51400022, val, dummy);
 }
 
-int
-geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner)
+static inline void geode_mfgpt_init_timer(int timer,
+					  struct geode_mfgpt_timer_t *req)
+{
+	mfgpt_timers[timer].flags &= ~F_AVAIL;
+	mfgpt_timers[timer].owner = req->owner;
+	mfgpt_timers[timer].suspend = req->suspend;
+	mfgpt_timers[timer].resume = req->resume;
+}
+
+int geode_mfgpt_alloc_timer(struct geode_mfgpt_timer_t *req)
 {
 	int i;
 	u32 base = geode_get_dev_base(GEODE_DEV_MFGPT);
 
-	/* If they requested a specific timer, try to honor that */
 	if (!base)
 		return -ENODEV;
 
-	if (timer != MFGPT_TIMER_ANY) {
-		if (mfgpt_timers[timer].flags & F_AVAIL) {
-			mfgpt_timers[timer].flags &= ~F_AVAIL;
-			mfgpt_timers[timer].owner = owner;
+	/* If they requested a specific timer, try to honor that */
 
-			printk("geode-mfgpt:  Registered timer %d\n", timer);
-			return timer;
+	if (req->timer != MFGPT_TIMER_ANY) {
+		if (mfgpt_timers[req->timer].flags & F_AVAIL) {
+			geode_mfgpt_init_timer(req->timer, req);
+			printk(KERN_INFO MFGPT_NAME ": Registered timer %d\n", req->timer);
+			return req->timer;
 		}
-		else if (mfgpt_timers[timer].owner == owner)
-			return timer;
+		else if (mfgpt_timers[req->timer].owner == req->owner)
+			return req->timer;
 
 		/* Request failed - somebody else owns it */
 		return -1;
@@ -212,14 +234,12 @@ geode_mfgpt_alloc_timer(int timer, int d
 
 		if ((mfgpt_timers[i].flags & F_AVAIL) &&
 		    !(mfgpt_timers[i].flags & F_RESERVED)) {
-			mfgpt_timers[i].flags &= ~F_AVAIL;
-			mfgpt_timers[i].owner = owner;
-
-			printk("geode-mfgpt:  Registered timer %d\n", i);
+			geode_mfgpt_init_timer(i, req);
+			printk(KERN_INFO MFGPT_NAME ": Registered timer %d\n", i);
 			return i;
 		}
 
-		if (i == 5 && domain == MFGPT_DOMAIN_WORKING)
+		if (i == 5 && req->domain == MFGPT_DOMAIN_WORKING)
 			break;
 	}
 
@@ -229,8 +249,7 @@ geode_mfgpt_alloc_timer(int timer, int d
 
 EXPORT_SYMBOL(geode_mfgpt_alloc_timer);
 
-static int
-mfgpt_setup_timer(int timer)
+static int mfgpt_setup_timer(int timer)
 {
 	u16 val = geode_mfgpt_read(timer, MFGPT_REG_SETUP);
 	mfgpt_timers[timer].index = timer;
@@ -246,10 +265,22 @@ mfgpt_setup_timer(int timer)
 	return 0;
 }
 
+static struct sysdev_class mfgpt_class = {
+	set_kset_name(MFGPT_NAME),
+	.resume = geode_mfgpt_resume,
+	.suspend = geode_mfgpt_suspend
+};
+
+static struct sys_device device_mfgpt = {
+	.id =  0,
+	.cls = &mfgpt_class
+};
+
 static void __init
 geode_mfgpt_init(void)
 {
 	int count = 0, i;
+	int ret;
 
 #ifdef CONFIG_OLPC
 	/* On the OLPC, we want to be able to use all the timers.  VSA
@@ -261,8 +292,8 @@ #ifdef CONFIG_OLPC
          */
 
 	{
-		u64 val = 0xFF;	
-		if (geode_mfgpt_read(1, MFGPT_REG_SETUP) & (1 << 12))	
+		u64 val = 0xFF;
+		if (geode_mfgpt_read(1, MFGPT_REG_SETUP) & (1 << 12))
 			wrmsrl(0x5140002B, val);
 	}
 #endif
@@ -270,11 +301,20 @@ #endif
 	for(i = 0; i < MFGPT_MAX_TIMERS; i++)
 		count += mfgpt_setup_timer(i);
 
-	printk(KERN_INFO "geode-mfgpt:  %d timers available.\n", count);
+	if (count > 0) {
+		ret = sysdev_class_register(&mfgpt_class);
+		if (!ret)
+			ret = sysdev_register(&device_mfgpt);
+
+		if (ret)
+			printk(KERN_ERR MFGPT_NAME ": Unable to register the system device (%d)\n", ret);
+
+		printk(KERN_INFO MFGPT_NAME ": %d timers available.\n", count);
+	} else
+		printk(KERN_ERR MFGPT_NAME ": No timers are available. Your BIOS is broken.\n");
 }
 
-static int __init
-geode_southbridge_init(void) {
+static int __init geode_southbridge_init(void) {
 
 	u32 lo, hi;
 	int i;
@@ -284,7 +324,7 @@ geode_southbridge_init(void) {
 
 	for(i = 0; i < ARRAY_SIZE(lbars); i++) {
 		rdmsr(lbars[i].msr, lo, hi);
-		if (hi & 0x01) 
+		if (hi & 0x01)
 			lbars[i].base = lo & 0x0000ffff;
 
 		if (lbars[i].base == 0)
diff --git a/arch/i386/kernel/olpc-pm.c b/arch/i386/kernel/olpc-pm.c
index 4655b13..f0e4d97 100644
--- a/arch/i386/kernel/olpc-pm.c
+++ b/arch/i386/kernel/olpc-pm.c
@@ -125,63 +125,18 @@ static int olpc_pm_enter (suspend_state_
 	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));
-}
-
-#define PM_SCLK_VAL  0x40000e00 /* [29:0] is the delay */
-#define PM_SED_VAL   0x40004601 /* [29:0] is the delay */
-#define PM_WKXD_VAL  0x040000a0 /* [19:0] is the delay */
+/* This function handles any last minute configuration that we want 
+   to do before we go to sleep.  Mainly this will involve setting 
+   wake events. This is called from do_olpc_suspend_lowlevel */
 
 int asmlinkage
-olpc_enter_sleep_state(u8 sleep_state)
+olpc_setup_sleep_state(u8 sleep_state)
 {
-	/* Set up the PMC sleep clock */
-	outl(PM_SCLK_VAL, pms_base + PM_SCLK);
-
-	/* Set up the Sleep End Delay */
-	outl(PM_SED_VAL, pms_base + PM_SED);
-
-	/* Set up the WORK_AUX delay */
-	outl(PM_WKXD_VAL, pms_base + PM_WKXD);
-
-	/* Clear PM_SSC */
-	outl(0x2FFFF, pms_base + PM_SSC);
-
-	/* 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 */
+	/* FIXME: Set the SCI bits we want to wake up on here */
 
 	outl((CS5536_PM_PWRBTN << 16) | 0xFFFF, acpi_base + PM1_STS);
 
-	/* 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();
-
+	/* FIXME: Set the GPE0 bits we want to wake on here */
 	return 0;
 }
 
@@ -245,17 +200,6 @@ #ifdef ENABLE_EC_SCI
 #endif
 }
 
-void __init
-olpc_reserve_bootmem(void)
-{
-  olpc_wakeup_address = (unsigned long)alloc_bootmem_low(PAGE_SIZE);
-
-  if (!olpc_wakeup_address) {
-    printk(KERN_ERR "olpc-pm:  Could not allocate lowmem - suspend is disabled.\n");
-    return;
-  }
-}
-
 static int __init olpc_pm_init(void)
 {
 	uint32_t lo, hi;
diff --git a/arch/i386/kernel/olpc-wakeup.S b/arch/i386/kernel/olpc-wakeup.S
index 21ef6b2..c344a7f 100644
--- a/arch/i386/kernel/olpc-wakeup.S
+++ b/arch/i386/kernel/olpc-wakeup.S
@@ -3,226 +3,49 @@ #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
+# OFW will remember most of the protected mode settings - we just need
+# to worry about saving the current state, and then pass control to
+# OFW.  The very last C setup we do will be done by olpc_setup_sleep_state()
+# which will configure the wakeup events
 
 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
+        leal (%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
+        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	
+        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
+	call	olpc_setup_sleep_state
 	addl	$4, %esp
 
-#	In case of S3 failure, we'll emerge here.  Jump
-# 	to ret_point to recover
+#	We're ready to sleep - go, go, gadget sleep
+	mov     $0xf0000,%eax
+	lcall   *(%eax)
+	
+#	We are back - restore the stack
+	movl    saved_context_esp, %esp
+		
+#	Jump to the return point and restore our state
 	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
-
diff --git a/arch/i386/kernel/setup.c b/arch/i386/kernel/setup.c
index 0b476e1..122623d 100644
--- a/arch/i386/kernel/setup.c
+++ b/arch/i386/kernel/setup.c
@@ -437,11 +437,6 @@ #ifdef CONFIG_ACPI_SLEEP
 	 */
 	acpi_reserve_bootmem();
 #endif
-#ifdef CONFIG_OLPC_PM
-	/* Reserve low memory for OLPC resume
-	 */
-	olpc_reserve_bootmem();
-#endif
 #ifdef CONFIG_X86_FIND_SMP_CONFIG
 	/*
 	 * Find and reserve possible boot-time SMP configuration:
diff --git a/drivers/clocksource/mfgpt.c b/drivers/clocksource/mfgpt.c
index 67f3e33..03842a7 100644
--- a/drivers/clocksource/mfgpt.c
+++ b/drivers/clocksource/mfgpt.c
@@ -1,6 +1,6 @@
 /* Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT)
  *
- * Copyright (C) 2006, Advanced Micro Devices, Inc.
+ * Copyright (C) 2006-2007, Advanced Micro Devices, Inc.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -125,6 +125,22 @@ static irqreturn_t mfgpt_tick(int irq, v
 	return IRQ_HANDLED;
 }
 
+static void mfgpt_timer_do_setup(void) 
+{
+	geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, 
+			  MFGPT_SCALE | MFGPT_SETUP_CMP2_EVENT);
+       
+	geode_mfgpt_setup_irq(mfgpt_event_clock, MFGPT_CMP2, irq);
+}
+
+static void mfgpt_timer_resume(int timer)
+{
+	if (timer != mfgpt_event_clock)
+		return;
+
+	mfgpt_timer_do_setup();
+}
+
 static struct irqaction mfgptirq  = {
 	.handler = mfgpt_tick,
 	.flags = IRQF_DISABLED | IRQF_NOBALANCING,
@@ -132,14 +148,21 @@ static struct irqaction mfgptirq  = {
 	.name = "mfgpt-timer"
 };
 
-static int __init
-mfgpt_timer_setup(void)
+/* Note that we don't have a suspend hook - the setup value is known, so we don't 
+   really need to save it off.
+*/
+
+struct geode_mfgpt_timer_t tick_timer = {
+	.timer = MFGPT_TIMER_ANY,
+	.domain = MFGPT_DOMAIN_WORKING,
+	.owner = THIS_MODULE,
+	.resume = mfgpt_timer_resume
+};
+
+static int __init mfgpt_timer_setup(void)
 {
 	int ret;
-	u16 val;
-
-	int timer = geode_mfgpt_alloc_timer(MFGPT_TIMER_ANY,
-	MFGPT_DOMAIN_WORKING, THIS_MODULE);
+	int timer = geode_mfgpt_alloc_timer(&tick_timer);
 
 	if (timer < 0) {
 		printk(KERN_ERR "mfgpt-timer:  Could not allocate a MFPGT timer\n");
@@ -147,13 +170,8 @@ mfgpt_timer_setup(void)
 	}
 
 	mfgpt_event_clock = timer;
-	/* Set the clock scale and enable the event mode for CMP2 */
-	val = MFGPT_SCALE | (3 << 8);
-
-	geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, val);
 
-	/* Set up the IRQ on the MFGPT side */
-	geode_mfgpt_setup_irq(mfgpt_event_clock, MFGPT_CMP2, irq);
+	mfgpt_timer_do_setup();
 
 	/* And register it with the kernel */
         ret = setup_irq(irq, &mfgptirq);
diff --git a/include/asm-i386/geode.h b/include/asm-i386/geode.h
index f9f29ec..50ec31f 100644
--- a/include/asm-i386/geode.h
+++ b/include/asm-i386/geode.h
@@ -71,7 +71,7 @@ #define PM_SCXA                0x04
 #define PM_SCYA                0x08
 #define PM_OUT_SLPCTL          0x0C
 #define PM_SCLK                0x10
-#define PM_SED                 0x1
+#define PM_SED                 0x14
 #define PM_SCXD                0x18
 #define PM_SCYD                0x1C
 #define PM_IN_SLPCTL           0x20
@@ -135,6 +135,7 @@ #define MFGPT_EVENT_NMI   1
 #define MFGPT_EVENT_RESET 3
 
 #define MFGPT_REG_CMP1    0
+
 #define MFGPT_REG_CMP2    2
 #define MFGPT_REG_COUNTER 4
 #define MFGPT_REG_SETUP   6
@@ -145,9 +146,31 @@ #define MFGPT_SETUP_CMP1   (1 << 13)
 #define MFGPT_SETUP_SETUP  (1 << 12)
 #define MFGPT_SETUP_STOPEN (1 << 11)
 #define MFGPT_SETUP_EXTEN  (1 << 10)
+
+#define MFGPT_SETUP_CMP2_DISABLE 0
+#define MFGPT_SETUP_CMP2_EQUAL   (1 << 8)
+#define MFGPT_SETUP_CMP2_GE      (2 << 8)
+#define MFGPT_SETUP_CMP2_EVENT   (3 << 8)
+
+#define MFGPT_SETUP_CMP1_DISABLE 0
+#define MFGPT_SETUP_CMP1_EQUAL   (1 << 6)
+#define MFGPT_SETUP_CMP1_GE      (2 << 6)
+#define MFGPT_SETUP_CMP1_EVENT   (3 << 6)
+
+
 #define MFGPT_SETUP_REVEN  (1 << 5)
 #define MFGPT_SETUP_CLKSEL (1 << 4)
 
+
+
+struct geode_mfgpt_timer_t {
+	int timer;
+	int domain;
+	struct module *owner;
+	void (*suspend)(int);
+	void (*resume)(int);
+};
+
 static inline void
 geode_mfgpt_write(int i, u16 r, u16 v)
 {
@@ -165,6 +188,10 @@ geode_mfgpt_read(int i, u16 r)
 #define geode_mfgpt_setup_irq(t,c,i) geode_mfgpt_set_irq((t),(c),(i),1)
 #define geode_mfgpt_release_irq(t,c,i) geode_mfgpt_set_irq((t),(c),(i),0)
 
+int geode_mfgpt_alloc_timer(struct geode_mfgpt_timer_t *);
+
+/* Generic Geode macros */
+
 static inline int
 is_geode_gx(void) {
 	return ((boot_cpu_data.x86_vendor == X86_VENDOR_NSC) &&


More information about the Devel mailing list