aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2011-06-25 12:34:11 -0400
committerH. Peter Anvin <hpa@linux.intel.com>2011-07-06 17:44:32 -0400
commit97c4cb71c18fe045a763ff6681a8ebbbbbec0b2b (patch)
tree66874802ab61cbf45c1d4d8d645b93e7600cdc0c /arch/x86
parenta3128588b3c6be634a9013a375903e0b55668f0a (diff)
x86, olpc: Add XO-1 suspend/resume support
Add code needed for basic suspend/resume of the XO-1 laptop. Based on earlier work by Jordan Crouse, Andres Salomon, and others. This patch incorporates all earlier feedback from Thomas Gleixner. To clarify a certain point (now more obvious in the code itself): On resume, OpenFirmware returns execution to Linux in protected mode with a kernel-compatible GDT already set up. The changes and simplifications suggested have all been included. Signed-off-by: Daniel Drake <dsd@laptop.org> Link: http://lkml.kernel.org/r/1309019658-1712-5-git-send-email-dsd@laptop.org Acked-by: Andres Salomon <dilinger@queued.net> Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
Diffstat (limited to 'arch/x86')
-rw-r--r--arch/x86/Kconfig4
-rw-r--r--arch/x86/include/asm/olpc.h15
-rw-r--r--arch/x86/platform/olpc/Makefile2
-rw-r--r--arch/x86/platform/olpc/olpc-xo1-pm.c92
-rw-r--r--arch/x86/platform/olpc/xo1-wakeup.S124
5 files changed, 231 insertions, 6 deletions
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 29615ee688a5..f473151ac991 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -2075,10 +2075,10 @@ config OLPC
2075 2075
2076config OLPC_XO1_PM 2076config OLPC_XO1_PM
2077 bool "OLPC XO-1 Power Management" 2077 bool "OLPC XO-1 Power Management"
2078 depends on OLPC && MFD_CS5535 2078 depends on OLPC && MFD_CS5535 && PM_SLEEP
2079 select MFD_CORE 2079 select MFD_CORE
2080 ---help--- 2080 ---help---
2081 Add support for poweroff of the OLPC XO-1 laptop. 2081 Add support for poweroff and suspend of the OLPC XO-1 laptop.
2082 2082
2083endif # X86_32 2083endif # X86_32
2084 2084
diff --git a/arch/x86/include/asm/olpc.h b/arch/x86/include/asm/olpc.h
index 5ca6801b75f3..10ea59594b71 100644
--- a/arch/x86/include/asm/olpc.h
+++ b/arch/x86/include/asm/olpc.h
@@ -76,6 +76,12 @@ static inline int olpc_has_dcon(void)
76 76
77#endif 77#endif
78 78
79#ifdef CONFIG_OLPC_XO1_PM
80extern void do_olpc_suspend_lowlevel(void);
81extern void olpc_xo1_pm_wakeup_set(u16 value);
82extern void olpc_xo1_pm_wakeup_clear(u16 value);
83#endif
84
79extern int pci_olpc_init(void); 85extern int pci_olpc_init(void);
80 86
81/* EC related functions */ 87/* EC related functions */
@@ -88,9 +94,12 @@ extern int olpc_ec_mask_unset(uint8_t bits);
88 94
89/* EC commands */ 95/* EC commands */
90 96
91#define EC_FIRMWARE_REV 0x08 97#define EC_FIRMWARE_REV 0x08
92#define EC_WLAN_ENTER_RESET 0x35 98#define EC_WAKE_UP_WLAN 0x24
93#define EC_WLAN_LEAVE_RESET 0x25 99#define EC_WLAN_LEAVE_RESET 0x25
100#define EC_SET_SCI_INHIBIT 0x32
101#define EC_SET_SCI_INHIBIT_RELEASE 0x34
102#define EC_WLAN_ENTER_RESET 0x35
94 103
95/* SCI source values */ 104/* SCI source values */
96 105
diff --git a/arch/x86/platform/olpc/Makefile b/arch/x86/platform/olpc/Makefile
index cd250387d4bb..1ae7bed89821 100644
--- a/arch/x86/platform/olpc/Makefile
+++ b/arch/x86/platform/olpc/Makefile
@@ -1,2 +1,2 @@
1obj-$(CONFIG_OLPC) += olpc.o olpc_ofw.o olpc_dt.o 1obj-$(CONFIG_OLPC) += olpc.o olpc_ofw.o olpc_dt.o
2obj-$(CONFIG_OLPC_XO1_PM) += olpc-xo1-pm.o 2obj-$(CONFIG_OLPC_XO1_PM) += olpc-xo1-pm.o xo1-wakeup.o
diff --git a/arch/x86/platform/olpc/olpc-xo1-pm.c b/arch/x86/platform/olpc/olpc-xo1-pm.c
index a2a59d36824d..6f3855a5a2f7 100644
--- a/arch/x86/platform/olpc/olpc-xo1-pm.c
+++ b/arch/x86/platform/olpc/olpc-xo1-pm.c
@@ -16,6 +16,7 @@
16#include <linux/platform_device.h> 16#include <linux/platform_device.h>
17#include <linux/pm.h> 17#include <linux/pm.h>
18#include <linux/mfd/core.h> 18#include <linux/mfd/core.h>
19#include <linux/suspend.h>
19 20
20#include <asm/io.h> 21#include <asm/io.h>
21#include <asm/olpc.h> 22#include <asm/olpc.h>
@@ -25,6 +26,85 @@
25static unsigned long acpi_base; 26static unsigned long acpi_base;
26static unsigned long pms_base; 27static unsigned long pms_base;
27 28
29static u16 wakeup_mask = CS5536_PM_PWRBTN;
30
31static struct {
32 unsigned long address;
33 unsigned short segment;
34} ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS };
35
36/* Set bits in the wakeup mask */
37void olpc_xo1_pm_wakeup_set(u16 value)
38{
39 wakeup_mask |= value;
40}
41EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set);
42
43/* Clear bits in the wakeup mask */
44void olpc_xo1_pm_wakeup_clear(u16 value)
45{
46 wakeup_mask &= ~value;
47}
48EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear);
49
50static int xo1_power_state_enter(suspend_state_t pm_state)
51{
52 unsigned long saved_sci_mask;
53 int r;
54
55 /* Only STR is supported */
56 if (pm_state != PM_SUSPEND_MEM)
57 return -EINVAL;
58
59 r = olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0);
60 if (r)
61 return r;
62
63 /*
64 * Save SCI mask (this gets lost since PM1_EN is used as a mask for
65 * wakeup events, which is not necessarily the same event set)
66 */
67 saved_sci_mask = inl(acpi_base + CS5536_PM1_STS);
68 saved_sci_mask &= 0xffff0000;
69
70 /* Save CPU state */
71 do_olpc_suspend_lowlevel();
72
73 /* Resume path starts here */
74
75 /* Restore SCI mask (using dword access to CS5536_PM1_EN) */
76 outl(saved_sci_mask, acpi_base + CS5536_PM1_STS);
77
78 /* Tell the EC to stop inhibiting SCIs */
79 olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0);
80
81 /*
82 * Tell the wireless module to restart USB communication.
83 * Must be done twice.
84 */
85 olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
86 olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
87
88 return 0;
89}
90
91asmlinkage int xo1_do_sleep(u8 sleep_state)
92{
93 void *pgd_addr = __va(read_cr3());
94
95 /* Program wakeup mask (using dword access to CS5536_PM1_EN) */
96 outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS);
97
98 __asm__("movl %0,%%eax" : : "r" (pgd_addr));
99 __asm__("call *(%%edi); cld"
100 : : "D" (&ofw_bios_entry));
101 __asm__("movb $0x34, %al\n\t"
102 "outb %al, $0x70\n\t"
103 "movb $0x30, %al\n\t"
104 "outb %al, $0x71\n\t");
105 return 0;
106}
107
28static void xo1_power_off(void) 108static void xo1_power_off(void)
29{ 109{
30 printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); 110 printk(KERN_INFO "OLPC XO-1 power off sequence...\n");
@@ -43,6 +123,17 @@ static void xo1_power_off(void)
43 outl(0x00002000, acpi_base + CS5536_PM1_CNT); 123 outl(0x00002000, acpi_base + CS5536_PM1_CNT);
44} 124}
45 125
126static int xo1_power_state_valid(suspend_state_t pm_state)
127{
128 /* suspend-to-RAM only */
129 return pm_state == PM_SUSPEND_MEM;
130}
131
132static const struct platform_suspend_ops xo1_suspend_ops = {
133 .valid = xo1_power_state_valid,
134 .enter = xo1_power_state_enter,
135};
136
46static int __devinit xo1_pm_probe(struct platform_device *pdev) 137static int __devinit xo1_pm_probe(struct platform_device *pdev)
47{ 138{
48 struct resource *res; 139 struct resource *res;
@@ -68,6 +159,7 @@ static int __devinit xo1_pm_probe(struct platform_device *pdev)
68 159
69 /* If we have both addresses, we can override the poweroff hook */ 160 /* If we have both addresses, we can override the poweroff hook */
70 if (pms_base && acpi_base) { 161 if (pms_base && acpi_base) {
162 suspend_set_ops(&xo1_suspend_ops);
71 pm_power_off = xo1_power_off; 163 pm_power_off = xo1_power_off;
72 printk(KERN_INFO "OLPC XO-1 support registered\n"); 164 printk(KERN_INFO "OLPC XO-1 support registered\n");
73 } 165 }
diff --git a/arch/x86/platform/olpc/xo1-wakeup.S b/arch/x86/platform/olpc/xo1-wakeup.S
new file mode 100644
index 000000000000..948deb289753
--- /dev/null
+++ b/arch/x86/platform/olpc/xo1-wakeup.S
@@ -0,0 +1,124 @@
1.text
2#include <linux/linkage.h>
3#include <asm/segment.h>
4#include <asm/page.h>
5#include <asm/pgtable_32.h>
6
7 .macro writepost,value
8 movb $0x34, %al
9 outb %al, $0x70
10 movb $\value, %al
11 outb %al, $0x71
12 .endm
13
14wakeup_start:
15 # OFW lands us here, running in protected mode, with a
16 # kernel-compatible GDT already setup.
17
18 # Clear any dangerous flags
19 pushl $0
20 popfl
21
22 writepost 0x31
23
24 # Set up %cr3
25 movl $initial_page_table - __PAGE_OFFSET, %eax
26 movl %eax, %cr3
27
28 movl saved_cr4, %eax
29 movl %eax, %cr4
30
31 movl saved_cr0, %eax
32 movl %eax, %cr0
33
34 # Control registers were modified, pipeline resync is needed
35 jmp 1f
361:
37
38 movw $__KERNEL_DS, %ax
39 movw %ax, %ss
40 movw %ax, %ds
41 movw %ax, %es
42 movw %ax, %fs
43 movw %ax, %gs
44
45 lgdt saved_gdt
46 lidt saved_idt
47 lldt saved_ldt
48 ljmp $(__KERNEL_CS),$1f
491:
50 movl %cr3, %eax
51 movl %eax, %cr3
52 wbinvd
53
54 # Go back to the return point
55 jmp ret_point
56
57save_registers:
58 sgdt saved_gdt
59 sidt saved_idt
60 sldt saved_ldt
61
62 pushl %edx
63 movl %cr4, %edx
64 movl %edx, saved_cr4
65
66 movl %cr0, %edx
67 movl %edx, saved_cr0
68
69 popl %edx
70
71 movl %ebx, saved_context_ebx
72 movl %ebp, saved_context_ebp
73 movl %esi, saved_context_esi
74 movl %edi, saved_context_edi
75
76 pushfl
77 popl saved_context_eflags
78
79 ret
80
81restore_registers:
82 movl saved_context_ebp, %ebp
83 movl saved_context_ebx, %ebx
84 movl saved_context_esi, %esi
85 movl saved_context_edi, %edi
86
87 pushl saved_context_eflags
88 popfl
89
90 ret
91
92ENTRY(do_olpc_suspend_lowlevel)
93 call save_processor_state
94 call save_registers
95
96 # This is the stack context we want to remember
97 movl %esp, saved_context_esp
98
99 pushl $3
100 call xo1_do_sleep
101
102 jmp wakeup_start
103 .p2align 4,,7
104ret_point:
105 movl saved_context_esp, %esp
106
107 writepost 0x32
108
109 call restore_registers
110 call restore_processor_state
111 ret
112
113.data
114saved_gdt: .long 0,0
115saved_idt: .long 0,0
116saved_ldt: .long 0
117saved_cr4: .long 0
118saved_cr0: .long 0
119saved_context_esp: .long 0
120saved_context_edi: .long 0
121saved_context_esi: .long 0
122saved_context_ebx: .long 0
123saved_context_ebp: .long 0
124saved_context_eflags: .long 0