diff options
author | Daniel Drake <dsd@laptop.org> | 2011-06-25 12:34:11 -0400 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-07-06 17:44:32 -0400 |
commit | 97c4cb71c18fe045a763ff6681a8ebbbbbec0b2b (patch) | |
tree | 66874802ab61cbf45c1d4d8d645b93e7600cdc0c /arch/x86 | |
parent | a3128588b3c6be634a9013a375903e0b55668f0a (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/Kconfig | 4 | ||||
-rw-r--r-- | arch/x86/include/asm/olpc.h | 15 | ||||
-rw-r--r-- | arch/x86/platform/olpc/Makefile | 2 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-pm.c | 92 | ||||
-rw-r--r-- | arch/x86/platform/olpc/xo1-wakeup.S | 124 |
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 | ||
2076 | config OLPC_XO1_PM | 2076 | config 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 | ||
2083 | endif # X86_32 | 2083 | endif # 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 | ||
80 | extern void do_olpc_suspend_lowlevel(void); | ||
81 | extern void olpc_xo1_pm_wakeup_set(u16 value); | ||
82 | extern void olpc_xo1_pm_wakeup_clear(u16 value); | ||
83 | #endif | ||
84 | |||
79 | extern int pci_olpc_init(void); | 85 | extern 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 @@ | |||
1 | obj-$(CONFIG_OLPC) += olpc.o olpc_ofw.o olpc_dt.o | 1 | obj-$(CONFIG_OLPC) += olpc.o olpc_ofw.o olpc_dt.o |
2 | obj-$(CONFIG_OLPC_XO1_PM) += olpc-xo1-pm.o | 2 | obj-$(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 @@ | |||
25 | static unsigned long acpi_base; | 26 | static unsigned long acpi_base; |
26 | static unsigned long pms_base; | 27 | static unsigned long pms_base; |
27 | 28 | ||
29 | static u16 wakeup_mask = CS5536_PM_PWRBTN; | ||
30 | |||
31 | static 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 */ | ||
37 | void olpc_xo1_pm_wakeup_set(u16 value) | ||
38 | { | ||
39 | wakeup_mask |= value; | ||
40 | } | ||
41 | EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set); | ||
42 | |||
43 | /* Clear bits in the wakeup mask */ | ||
44 | void olpc_xo1_pm_wakeup_clear(u16 value) | ||
45 | { | ||
46 | wakeup_mask &= ~value; | ||
47 | } | ||
48 | EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear); | ||
49 | |||
50 | static 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 | |||
91 | asmlinkage 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 | |||
28 | static void xo1_power_off(void) | 108 | static 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 | ||
126 | static 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 | |||
132 | static const struct platform_suspend_ops xo1_suspend_ops = { | ||
133 | .valid = xo1_power_state_valid, | ||
134 | .enter = xo1_power_state_enter, | ||
135 | }; | ||
136 | |||
46 | static int __devinit xo1_pm_probe(struct platform_device *pdev) | 137 | static 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 | |||
14 | wakeup_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 | ||
36 | 1: | ||
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 | ||
49 | 1: | ||
50 | movl %cr3, %eax | ||
51 | movl %eax, %cr3 | ||
52 | wbinvd | ||
53 | |||
54 | # Go back to the return point | ||
55 | jmp ret_point | ||
56 | |||
57 | save_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 | |||
81 | restore_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 | |||
92 | ENTRY(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 | ||
104 | ret_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 | ||
114 | saved_gdt: .long 0,0 | ||
115 | saved_idt: .long 0,0 | ||
116 | saved_ldt: .long 0 | ||
117 | saved_cr4: .long 0 | ||
118 | saved_cr0: .long 0 | ||
119 | saved_context_esp: .long 0 | ||
120 | saved_context_edi: .long 0 | ||
121 | saved_context_esi: .long 0 | ||
122 | saved_context_ebx: .long 0 | ||
123 | saved_context_ebp: .long 0 | ||
124 | saved_context_eflags: .long 0 | ||