Commit fa8f53ace4af9470d8414427cb3dc3c0ffc4f182
Exists in
master
and in
38 other branches
Merge branch 'x86-olpc-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip
* 'x86-olpc-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip: x86, olpc-xo15-sci: Enable EC wakeup capability x86, olpc: Fix dependency on POWER_SUPPLY x86, olpc: Add XO-1.5 SCI driver x86, olpc: Add XO-1 RTC driver x86, olpc-xo1-sci: Propagate power supply/battery events x86, olpc-xo1-sci: Add lid switch functionality x86, olpc-xo1-sci: Add GPE handler and ebook switch functionality x86, olpc: EC SCI wakeup mask functionality x86, olpc: Add XO-1 SCI driver and power button control x86, olpc: Add XO-1 suspend/resume support x86, olpc: Rename olpc-xo1 to olpc-xo1-pm x86, olpc: Move CS5536-related constants to cs5535.h x86, olpc: Add missing elements to device tree
Showing 13 changed files Side-by-side Diff
- Documentation/devicetree/bindings/rtc/olpc-xo1-rtc.txt
- arch/x86/Kconfig
- arch/x86/include/asm/olpc.h
- arch/x86/platform/olpc/Makefile
- arch/x86/platform/olpc/olpc-xo1-pm.c
- arch/x86/platform/olpc/olpc-xo1-rtc.c
- arch/x86/platform/olpc/olpc-xo1-sci.c
- arch/x86/platform/olpc/olpc-xo1.c
- arch/x86/platform/olpc/olpc-xo15-sci.c
- arch/x86/platform/olpc/olpc.c
- arch/x86/platform/olpc/olpc_dt.c
- arch/x86/platform/olpc/xo1-wakeup.S
- include/linux/cs5535.h
Documentation/devicetree/bindings/rtc/olpc-xo1-rtc.txt
arch/x86/Kconfig
... | ... | @@ -2024,11 +2024,44 @@ |
2024 | 2024 | Add support for detecting the unique features of the OLPC |
2025 | 2025 | XO hardware. |
2026 | 2026 | |
2027 | -config OLPC_XO1 | |
2028 | - tristate "OLPC XO-1 support" | |
2029 | - depends on OLPC && MFD_CS5535 | |
2027 | +config OLPC_XO1_PM | |
2028 | + bool "OLPC XO-1 Power Management" | |
2029 | + depends on OLPC && MFD_CS5535 && PM_SLEEP | |
2030 | + select MFD_CORE | |
2030 | 2031 | ---help--- |
2031 | - Add support for non-essential features of the OLPC XO-1 laptop. | |
2032 | + Add support for poweroff and suspend of the OLPC XO-1 laptop. | |
2033 | + | |
2034 | +config OLPC_XO1_RTC | |
2035 | + bool "OLPC XO-1 Real Time Clock" | |
2036 | + depends on OLPC_XO1_PM && RTC_DRV_CMOS | |
2037 | + ---help--- | |
2038 | + Add support for the XO-1 real time clock, which can be used as a | |
2039 | + programmable wakeup source. | |
2040 | + | |
2041 | +config OLPC_XO1_SCI | |
2042 | + bool "OLPC XO-1 SCI extras" | |
2043 | + depends on OLPC && OLPC_XO1_PM | |
2044 | + select POWER_SUPPLY | |
2045 | + select GPIO_CS5535 | |
2046 | + select MFD_CORE | |
2047 | + ---help--- | |
2048 | + Add support for SCI-based features of the OLPC XO-1 laptop: | |
2049 | + - EC-driven system wakeups | |
2050 | + - Power button | |
2051 | + - Ebook switch | |
2052 | + - Lid switch | |
2053 | + - AC adapter status updates | |
2054 | + - Battery status updates | |
2055 | + | |
2056 | +config OLPC_XO15_SCI | |
2057 | + bool "OLPC XO-1.5 SCI extras" | |
2058 | + depends on OLPC && ACPI | |
2059 | + select POWER_SUPPLY | |
2060 | + ---help--- | |
2061 | + Add support for SCI-based features of the OLPC XO-1.5 laptop: | |
2062 | + - EC-driven system wakeups | |
2063 | + - AC adapter status updates | |
2064 | + - Battery status updates | |
2032 | 2065 | |
2033 | 2066 | endif # X86_32 |
2034 | 2067 |
arch/x86/include/asm/olpc.h
... | ... | @@ -13,6 +13,7 @@ |
13 | 13 | |
14 | 14 | #define OLPC_F_PRESENT 0x01 |
15 | 15 | #define OLPC_F_DCON 0x02 |
16 | +#define OLPC_F_EC_WIDE_SCI 0x04 | |
16 | 17 | |
17 | 18 | #ifdef CONFIG_OLPC |
18 | 19 | |
... | ... | @@ -62,6 +63,13 @@ |
62 | 63 | return olpc_platform_info.boardrev >= rev; |
63 | 64 | } |
64 | 65 | |
66 | +extern void olpc_ec_wakeup_set(u16 value); | |
67 | +extern void olpc_ec_wakeup_clear(u16 value); | |
68 | +extern bool olpc_ec_wakeup_available(void); | |
69 | + | |
70 | +extern int olpc_ec_mask_write(u16 bits); | |
71 | +extern int olpc_ec_sci_query(u16 *sci_value); | |
72 | + | |
65 | 73 | #else |
66 | 74 | |
67 | 75 | static inline int machine_is_olpc(void) |
68 | 76 | |
... | ... | @@ -74,8 +82,22 @@ |
74 | 82 | return 0; |
75 | 83 | } |
76 | 84 | |
85 | +static inline void olpc_ec_wakeup_set(u16 value) { } | |
86 | +static inline void olpc_ec_wakeup_clear(u16 value) { } | |
87 | + | |
88 | +static inline bool olpc_ec_wakeup_available(void) | |
89 | +{ | |
90 | + return false; | |
91 | +} | |
92 | + | |
77 | 93 | #endif |
78 | 94 | |
95 | +#ifdef CONFIG_OLPC_XO1_PM | |
96 | +extern void do_olpc_suspend_lowlevel(void); | |
97 | +extern void olpc_xo1_pm_wakeup_set(u16 value); | |
98 | +extern void olpc_xo1_pm_wakeup_clear(u16 value); | |
99 | +#endif | |
100 | + | |
79 | 101 | extern int pci_olpc_init(void); |
80 | 102 | |
81 | 103 | /* EC related functions */ |
82 | 104 | |
... | ... | @@ -83,14 +105,19 @@ |
83 | 105 | extern int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, |
84 | 106 | unsigned char *outbuf, size_t outlen); |
85 | 107 | |
86 | -extern int olpc_ec_mask_set(uint8_t bits); | |
87 | -extern int olpc_ec_mask_unset(uint8_t bits); | |
88 | - | |
89 | 108 | /* EC commands */ |
90 | 109 | |
91 | -#define EC_FIRMWARE_REV 0x08 | |
92 | -#define EC_WLAN_ENTER_RESET 0x35 | |
93 | -#define EC_WLAN_LEAVE_RESET 0x25 | |
110 | +#define EC_FIRMWARE_REV 0x08 | |
111 | +#define EC_WRITE_SCI_MASK 0x1b | |
112 | +#define EC_WAKE_UP_WLAN 0x24 | |
113 | +#define EC_WLAN_LEAVE_RESET 0x25 | |
114 | +#define EC_READ_EB_MODE 0x2a | |
115 | +#define EC_SET_SCI_INHIBIT 0x32 | |
116 | +#define EC_SET_SCI_INHIBIT_RELEASE 0x34 | |
117 | +#define EC_WLAN_ENTER_RESET 0x35 | |
118 | +#define EC_WRITE_EXT_SCI_MASK 0x38 | |
119 | +#define EC_SCI_QUERY 0x84 | |
120 | +#define EC_EXT_SCI_QUERY 0x85 | |
94 | 121 | |
95 | 122 | /* SCI source values */ |
96 | 123 | |
97 | 124 | |
... | ... | @@ -99,10 +126,12 @@ |
99 | 126 | #define EC_SCI_SRC_BATTERY 0x02 |
100 | 127 | #define EC_SCI_SRC_BATSOC 0x04 |
101 | 128 | #define EC_SCI_SRC_BATERR 0x08 |
102 | -#define EC_SCI_SRC_EBOOK 0x10 | |
103 | -#define EC_SCI_SRC_WLAN 0x20 | |
129 | +#define EC_SCI_SRC_EBOOK 0x10 /* XO-1 only */ | |
130 | +#define EC_SCI_SRC_WLAN 0x20 /* XO-1 only */ | |
104 | 131 | #define EC_SCI_SRC_ACPWR 0x40 |
105 | -#define EC_SCI_SRC_ALL 0x7F | |
132 | +#define EC_SCI_SRC_BATCRIT 0x80 | |
133 | +#define EC_SCI_SRC_GPWAKE 0x100 /* XO-1.5 only */ | |
134 | +#define EC_SCI_SRC_ALL 0x1FF | |
106 | 135 | |
107 | 136 | /* GPIO assignments */ |
108 | 137 | |
... | ... | @@ -116,8 +145,8 @@ |
116 | 145 | #define OLPC_GPIO_SMB_CLK 14 |
117 | 146 | #define OLPC_GPIO_SMB_DATA 15 |
118 | 147 | #define OLPC_GPIO_WORKAUX geode_gpio(24) |
119 | -#define OLPC_GPIO_LID geode_gpio(26) | |
120 | -#define OLPC_GPIO_ECSCI geode_gpio(27) | |
148 | +#define OLPC_GPIO_LID 26 | |
149 | +#define OLPC_GPIO_ECSCI 27 | |
121 | 150 | |
122 | 151 | #endif /* _ASM_X86_OLPC_H */ |
arch/x86/platform/olpc/Makefile
arch/x86/platform/olpc/olpc-xo1-pm.c
1 | +/* | |
2 | + * Support for power management features of the OLPC XO-1 laptop | |
3 | + * | |
4 | + * Copyright (C) 2010 Andres Salomon <dilinger@queued.net> | |
5 | + * Copyright (C) 2010 One Laptop per Child | |
6 | + * Copyright (C) 2006 Red Hat, Inc. | |
7 | + * Copyright (C) 2006 Advanced Micro Devices, Inc. | |
8 | + * | |
9 | + * This program is free software; you can redistribute it and/or modify | |
10 | + * it under the terms of the GNU General Public License as published by | |
11 | + * the Free Software Foundation; either version 2 of the License, or | |
12 | + * (at your option) any later version. | |
13 | + */ | |
14 | + | |
15 | +#include <linux/cs5535.h> | |
16 | +#include <linux/platform_device.h> | |
17 | +#include <linux/pm.h> | |
18 | +#include <linux/mfd/core.h> | |
19 | +#include <linux/suspend.h> | |
20 | + | |
21 | +#include <asm/io.h> | |
22 | +#include <asm/olpc.h> | |
23 | + | |
24 | +#define DRV_NAME "olpc-xo1-pm" | |
25 | + | |
26 | +static unsigned long acpi_base; | |
27 | +static unsigned long pms_base; | |
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 | + | |
108 | +static void xo1_power_off(void) | |
109 | +{ | |
110 | + printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); | |
111 | + | |
112 | + /* Enable all of these controls with 0 delay */ | |
113 | + outl(0x40000000, pms_base + CS5536_PM_SCLK); | |
114 | + outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); | |
115 | + outl(0x40000000, pms_base + CS5536_PM_WKXD); | |
116 | + outl(0x40000000, pms_base + CS5536_PM_WKD); | |
117 | + | |
118 | + /* Clear status bits (possibly unnecessary) */ | |
119 | + outl(0x0002ffff, pms_base + CS5536_PM_SSC); | |
120 | + outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); | |
121 | + | |
122 | + /* Write SLP_EN bit to start the machinery */ | |
123 | + outl(0x00002000, acpi_base + CS5536_PM1_CNT); | |
124 | +} | |
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 | + | |
137 | +static int __devinit xo1_pm_probe(struct platform_device *pdev) | |
138 | +{ | |
139 | + struct resource *res; | |
140 | + int err; | |
141 | + | |
142 | + /* don't run on non-XOs */ | |
143 | + if (!machine_is_olpc()) | |
144 | + return -ENODEV; | |
145 | + | |
146 | + err = mfd_cell_enable(pdev); | |
147 | + if (err) | |
148 | + return err; | |
149 | + | |
150 | + res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
151 | + if (!res) { | |
152 | + dev_err(&pdev->dev, "can't fetch device resource info\n"); | |
153 | + return -EIO; | |
154 | + } | |
155 | + if (strcmp(pdev->name, "cs5535-pms") == 0) | |
156 | + pms_base = res->start; | |
157 | + else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) | |
158 | + acpi_base = res->start; | |
159 | + | |
160 | + /* If we have both addresses, we can override the poweroff hook */ | |
161 | + if (pms_base && acpi_base) { | |
162 | + suspend_set_ops(&xo1_suspend_ops); | |
163 | + pm_power_off = xo1_power_off; | |
164 | + printk(KERN_INFO "OLPC XO-1 support registered\n"); | |
165 | + } | |
166 | + | |
167 | + return 0; | |
168 | +} | |
169 | + | |
170 | +static int __devexit xo1_pm_remove(struct platform_device *pdev) | |
171 | +{ | |
172 | + mfd_cell_disable(pdev); | |
173 | + | |
174 | + if (strcmp(pdev->name, "cs5535-pms") == 0) | |
175 | + pms_base = 0; | |
176 | + else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) | |
177 | + acpi_base = 0; | |
178 | + | |
179 | + pm_power_off = NULL; | |
180 | + return 0; | |
181 | +} | |
182 | + | |
183 | +static struct platform_driver cs5535_pms_driver = { | |
184 | + .driver = { | |
185 | + .name = "cs5535-pms", | |
186 | + .owner = THIS_MODULE, | |
187 | + }, | |
188 | + .probe = xo1_pm_probe, | |
189 | + .remove = __devexit_p(xo1_pm_remove), | |
190 | +}; | |
191 | + | |
192 | +static struct platform_driver cs5535_acpi_driver = { | |
193 | + .driver = { | |
194 | + .name = "olpc-xo1-pm-acpi", | |
195 | + .owner = THIS_MODULE, | |
196 | + }, | |
197 | + .probe = xo1_pm_probe, | |
198 | + .remove = __devexit_p(xo1_pm_remove), | |
199 | +}; | |
200 | + | |
201 | +static int __init xo1_pm_init(void) | |
202 | +{ | |
203 | + int r; | |
204 | + | |
205 | + r = platform_driver_register(&cs5535_pms_driver); | |
206 | + if (r) | |
207 | + return r; | |
208 | + | |
209 | + r = platform_driver_register(&cs5535_acpi_driver); | |
210 | + if (r) | |
211 | + platform_driver_unregister(&cs5535_pms_driver); | |
212 | + | |
213 | + return r; | |
214 | +} | |
215 | +arch_initcall(xo1_pm_init); |
arch/x86/platform/olpc/olpc-xo1-rtc.c
1 | +/* | |
2 | + * Support for OLPC XO-1 Real Time Clock (RTC) | |
3 | + * | |
4 | + * Copyright (C) 2011 One Laptop per Child | |
5 | + * | |
6 | + * This program is free software; you can redistribute it and/or modify | |
7 | + * it under the terms of the GNU General Public License as published by | |
8 | + * the Free Software Foundation; either version 2 of the License, or | |
9 | + * (at your option) any later version. | |
10 | + */ | |
11 | + | |
12 | +#include <linux/mc146818rtc.h> | |
13 | +#include <linux/platform_device.h> | |
14 | +#include <linux/rtc.h> | |
15 | +#include <linux/of.h> | |
16 | + | |
17 | +#include <asm/msr.h> | |
18 | +#include <asm/olpc.h> | |
19 | + | |
20 | +static void rtc_wake_on(struct device *dev) | |
21 | +{ | |
22 | + olpc_xo1_pm_wakeup_set(CS5536_PM_RTC); | |
23 | +} | |
24 | + | |
25 | +static void rtc_wake_off(struct device *dev) | |
26 | +{ | |
27 | + olpc_xo1_pm_wakeup_clear(CS5536_PM_RTC); | |
28 | +} | |
29 | + | |
30 | +static struct resource rtc_platform_resource[] = { | |
31 | + [0] = { | |
32 | + .start = RTC_PORT(0), | |
33 | + .end = RTC_PORT(1), | |
34 | + .flags = IORESOURCE_IO, | |
35 | + }, | |
36 | + [1] = { | |
37 | + .start = RTC_IRQ, | |
38 | + .end = RTC_IRQ, | |
39 | + .flags = IORESOURCE_IRQ, | |
40 | + } | |
41 | +}; | |
42 | + | |
43 | +static struct cmos_rtc_board_info rtc_info = { | |
44 | + .rtc_day_alarm = 0, | |
45 | + .rtc_mon_alarm = 0, | |
46 | + .rtc_century = 0, | |
47 | + .wake_on = rtc_wake_on, | |
48 | + .wake_off = rtc_wake_off, | |
49 | +}; | |
50 | + | |
51 | +static struct platform_device xo1_rtc_device = { | |
52 | + .name = "rtc_cmos", | |
53 | + .id = -1, | |
54 | + .num_resources = ARRAY_SIZE(rtc_platform_resource), | |
55 | + .dev.platform_data = &rtc_info, | |
56 | + .resource = rtc_platform_resource, | |
57 | +}; | |
58 | + | |
59 | +static int __init xo1_rtc_init(void) | |
60 | +{ | |
61 | + int r; | |
62 | + struct device_node *node; | |
63 | + | |
64 | + node = of_find_compatible_node(NULL, NULL, "olpc,xo1-rtc"); | |
65 | + if (!node) | |
66 | + return 0; | |
67 | + of_node_put(node); | |
68 | + | |
69 | + pr_info("olpc-xo1-rtc: Initializing OLPC XO-1 RTC\n"); | |
70 | + rdmsrl(MSR_RTC_DOMA_OFFSET, rtc_info.rtc_day_alarm); | |
71 | + rdmsrl(MSR_RTC_MONA_OFFSET, rtc_info.rtc_mon_alarm); | |
72 | + rdmsrl(MSR_RTC_CEN_OFFSET, rtc_info.rtc_century); | |
73 | + | |
74 | + r = platform_device_register(&xo1_rtc_device); | |
75 | + if (r) | |
76 | + return r; | |
77 | + | |
78 | + device_init_wakeup(&xo1_rtc_device.dev, 1); | |
79 | + return 0; | |
80 | +} | |
81 | +arch_initcall(xo1_rtc_init); |
arch/x86/platform/olpc/olpc-xo1-sci.c
1 | +/* | |
2 | + * Support for OLPC XO-1 System Control Interrupts (SCI) | |
3 | + * | |
4 | + * Copyright (C) 2010 One Laptop per Child | |
5 | + * Copyright (C) 2006 Red Hat, Inc. | |
6 | + * Copyright (C) 2006 Advanced Micro Devices, Inc. | |
7 | + * | |
8 | + * This program is free software; you can redistribute it and/or modify | |
9 | + * it under the terms of the GNU General Public License as published by | |
10 | + * the Free Software Foundation; either version 2 of the License, or | |
11 | + * (at your option) any later version. | |
12 | + */ | |
13 | + | |
14 | +#include <linux/cs5535.h> | |
15 | +#include <linux/device.h> | |
16 | +#include <linux/gpio.h> | |
17 | +#include <linux/input.h> | |
18 | +#include <linux/interrupt.h> | |
19 | +#include <linux/platform_device.h> | |
20 | +#include <linux/pm.h> | |
21 | +#include <linux/mfd/core.h> | |
22 | +#include <linux/power_supply.h> | |
23 | +#include <linux/suspend.h> | |
24 | +#include <linux/workqueue.h> | |
25 | + | |
26 | +#include <asm/io.h> | |
27 | +#include <asm/msr.h> | |
28 | +#include <asm/olpc.h> | |
29 | + | |
30 | +#define DRV_NAME "olpc-xo1-sci" | |
31 | +#define PFX DRV_NAME ": " | |
32 | + | |
33 | +static unsigned long acpi_base; | |
34 | +static struct input_dev *power_button_idev; | |
35 | +static struct input_dev *ebook_switch_idev; | |
36 | +static struct input_dev *lid_switch_idev; | |
37 | + | |
38 | +static int sci_irq; | |
39 | + | |
40 | +static bool lid_open; | |
41 | +static bool lid_inverted; | |
42 | +static int lid_wake_mode; | |
43 | + | |
44 | +enum lid_wake_modes { | |
45 | + LID_WAKE_ALWAYS, | |
46 | + LID_WAKE_OPEN, | |
47 | + LID_WAKE_CLOSE, | |
48 | +}; | |
49 | + | |
50 | +static const char * const lid_wake_mode_names[] = { | |
51 | + [LID_WAKE_ALWAYS] = "always", | |
52 | + [LID_WAKE_OPEN] = "open", | |
53 | + [LID_WAKE_CLOSE] = "close", | |
54 | +}; | |
55 | + | |
56 | +static void battery_status_changed(void) | |
57 | +{ | |
58 | + struct power_supply *psy = power_supply_get_by_name("olpc-battery"); | |
59 | + | |
60 | + if (psy) { | |
61 | + power_supply_changed(psy); | |
62 | + put_device(psy->dev); | |
63 | + } | |
64 | +} | |
65 | + | |
66 | +static void ac_status_changed(void) | |
67 | +{ | |
68 | + struct power_supply *psy = power_supply_get_by_name("olpc-ac"); | |
69 | + | |
70 | + if (psy) { | |
71 | + power_supply_changed(psy); | |
72 | + put_device(psy->dev); | |
73 | + } | |
74 | +} | |
75 | + | |
76 | +/* Report current ebook switch state through input layer */ | |
77 | +static void send_ebook_state(void) | |
78 | +{ | |
79 | + unsigned char state; | |
80 | + | |
81 | + if (olpc_ec_cmd(EC_READ_EB_MODE, NULL, 0, &state, 1)) { | |
82 | + pr_err(PFX "failed to get ebook state\n"); | |
83 | + return; | |
84 | + } | |
85 | + | |
86 | + input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state); | |
87 | + input_sync(ebook_switch_idev); | |
88 | +} | |
89 | + | |
90 | +static void flip_lid_inverter(void) | |
91 | +{ | |
92 | + /* gpio is high; invert so we'll get l->h event interrupt */ | |
93 | + if (lid_inverted) | |
94 | + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); | |
95 | + else | |
96 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_INVERT); | |
97 | + lid_inverted = !lid_inverted; | |
98 | +} | |
99 | + | |
100 | +static void detect_lid_state(void) | |
101 | +{ | |
102 | + /* | |
103 | + * the edge detector hookup on the gpio inputs on the geode is | |
104 | + * odd, to say the least. See http://dev.laptop.org/ticket/5703 | |
105 | + * for details, but in a nutshell: we don't use the edge | |
106 | + * detectors. instead, we make use of an anomoly: with the both | |
107 | + * edge detectors turned off, we still get an edge event on a | |
108 | + * positive edge transition. to take advantage of this, we use the | |
109 | + * front-end inverter to ensure that that's the edge we're always | |
110 | + * going to see next. | |
111 | + */ | |
112 | + | |
113 | + int state; | |
114 | + | |
115 | + state = cs5535_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK); | |
116 | + lid_open = !state ^ !lid_inverted; /* x ^^ y */ | |
117 | + if (!state) | |
118 | + return; | |
119 | + | |
120 | + flip_lid_inverter(); | |
121 | +} | |
122 | + | |
123 | +/* Report current lid switch state through input layer */ | |
124 | +static void send_lid_state(void) | |
125 | +{ | |
126 | + input_report_switch(lid_switch_idev, SW_LID, !lid_open); | |
127 | + input_sync(lid_switch_idev); | |
128 | +} | |
129 | + | |
130 | +static ssize_t lid_wake_mode_show(struct device *dev, | |
131 | + struct device_attribute *attr, char *buf) | |
132 | +{ | |
133 | + const char *mode = lid_wake_mode_names[lid_wake_mode]; | |
134 | + return sprintf(buf, "%s\n", mode); | |
135 | +} | |
136 | +static ssize_t lid_wake_mode_set(struct device *dev, | |
137 | + struct device_attribute *attr, | |
138 | + const char *buf, size_t count) | |
139 | +{ | |
140 | + int i; | |
141 | + for (i = 0; i < ARRAY_SIZE(lid_wake_mode_names); i++) { | |
142 | + const char *mode = lid_wake_mode_names[i]; | |
143 | + if (strlen(mode) != count || strncasecmp(mode, buf, count)) | |
144 | + continue; | |
145 | + | |
146 | + lid_wake_mode = i; | |
147 | + return count; | |
148 | + } | |
149 | + return -EINVAL; | |
150 | +} | |
151 | +static DEVICE_ATTR(lid_wake_mode, S_IWUSR | S_IRUGO, lid_wake_mode_show, | |
152 | + lid_wake_mode_set); | |
153 | + | |
154 | +/* | |
155 | + * Process all items in the EC's SCI queue. | |
156 | + * | |
157 | + * This is handled in a workqueue because olpc_ec_cmd can be slow (and | |
158 | + * can even timeout). | |
159 | + * | |
160 | + * If propagate_events is false, the queue is drained without events being | |
161 | + * generated for the interrupts. | |
162 | + */ | |
163 | +static void process_sci_queue(bool propagate_events) | |
164 | +{ | |
165 | + int r; | |
166 | + u16 data; | |
167 | + | |
168 | + do { | |
169 | + r = olpc_ec_sci_query(&data); | |
170 | + if (r || !data) | |
171 | + break; | |
172 | + | |
173 | + pr_debug(PFX "SCI 0x%x received\n", data); | |
174 | + | |
175 | + switch (data) { | |
176 | + case EC_SCI_SRC_BATERR: | |
177 | + case EC_SCI_SRC_BATSOC: | |
178 | + case EC_SCI_SRC_BATTERY: | |
179 | + case EC_SCI_SRC_BATCRIT: | |
180 | + battery_status_changed(); | |
181 | + break; | |
182 | + case EC_SCI_SRC_ACPWR: | |
183 | + ac_status_changed(); | |
184 | + break; | |
185 | + } | |
186 | + | |
187 | + if (data == EC_SCI_SRC_EBOOK && propagate_events) | |
188 | + send_ebook_state(); | |
189 | + } while (data); | |
190 | + | |
191 | + if (r) | |
192 | + pr_err(PFX "Failed to clear SCI queue"); | |
193 | +} | |
194 | + | |
195 | +static void process_sci_queue_work(struct work_struct *work) | |
196 | +{ | |
197 | + process_sci_queue(true); | |
198 | +} | |
199 | + | |
200 | +static DECLARE_WORK(sci_work, process_sci_queue_work); | |
201 | + | |
202 | +static irqreturn_t xo1_sci_intr(int irq, void *dev_id) | |
203 | +{ | |
204 | + struct platform_device *pdev = dev_id; | |
205 | + u32 sts; | |
206 | + u32 gpe; | |
207 | + | |
208 | + sts = inl(acpi_base + CS5536_PM1_STS); | |
209 | + outl(sts | 0xffff, acpi_base + CS5536_PM1_STS); | |
210 | + | |
211 | + gpe = inl(acpi_base + CS5536_PM_GPE0_STS); | |
212 | + outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); | |
213 | + | |
214 | + dev_dbg(&pdev->dev, "sts %x gpe %x\n", sts, gpe); | |
215 | + | |
216 | + if (sts & CS5536_PWRBTN_FLAG && !(sts & CS5536_WAK_FLAG)) { | |
217 | + input_report_key(power_button_idev, KEY_POWER, 1); | |
218 | + input_sync(power_button_idev); | |
219 | + input_report_key(power_button_idev, KEY_POWER, 0); | |
220 | + input_sync(power_button_idev); | |
221 | + } | |
222 | + | |
223 | + if (gpe & CS5536_GPIOM7_PME_FLAG) { /* EC GPIO */ | |
224 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); | |
225 | + schedule_work(&sci_work); | |
226 | + } | |
227 | + | |
228 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); | |
229 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); | |
230 | + detect_lid_state(); | |
231 | + send_lid_state(); | |
232 | + | |
233 | + return IRQ_HANDLED; | |
234 | +} | |
235 | + | |
236 | +static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state) | |
237 | +{ | |
238 | + if (device_may_wakeup(&power_button_idev->dev)) | |
239 | + olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); | |
240 | + else | |
241 | + olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN); | |
242 | + | |
243 | + if (device_may_wakeup(&ebook_switch_idev->dev)) | |
244 | + olpc_ec_wakeup_set(EC_SCI_SRC_EBOOK); | |
245 | + else | |
246 | + olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); | |
247 | + | |
248 | + if (!device_may_wakeup(&lid_switch_idev->dev)) { | |
249 | + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | |
250 | + } else if ((lid_open && lid_wake_mode == LID_WAKE_OPEN) || | |
251 | + (!lid_open && lid_wake_mode == LID_WAKE_CLOSE)) { | |
252 | + flip_lid_inverter(); | |
253 | + | |
254 | + /* we may have just caused an event */ | |
255 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); | |
256 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); | |
257 | + | |
258 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | |
259 | + } | |
260 | + | |
261 | + return 0; | |
262 | +} | |
263 | + | |
264 | +static int xo1_sci_resume(struct platform_device *pdev) | |
265 | +{ | |
266 | + /* | |
267 | + * We don't know what may have happened while we were asleep. | |
268 | + * Reestablish our lid setup so we're sure to catch all transitions. | |
269 | + */ | |
270 | + detect_lid_state(); | |
271 | + send_lid_state(); | |
272 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | |
273 | + | |
274 | + /* Enable all EC events */ | |
275 | + olpc_ec_mask_write(EC_SCI_SRC_ALL); | |
276 | + | |
277 | + /* Power/battery status might have changed too */ | |
278 | + battery_status_changed(); | |
279 | + ac_status_changed(); | |
280 | + return 0; | |
281 | +} | |
282 | + | |
283 | +static int __devinit setup_sci_interrupt(struct platform_device *pdev) | |
284 | +{ | |
285 | + u32 lo, hi; | |
286 | + u32 sts; | |
287 | + int r; | |
288 | + | |
289 | + rdmsr(0x51400020, lo, hi); | |
290 | + sci_irq = (lo >> 20) & 15; | |
291 | + | |
292 | + if (sci_irq) { | |
293 | + dev_info(&pdev->dev, "SCI is mapped to IRQ %d\n", sci_irq); | |
294 | + } else { | |
295 | + /* Zero means masked */ | |
296 | + dev_info(&pdev->dev, "SCI unmapped. Mapping to IRQ 3\n"); | |
297 | + sci_irq = 3; | |
298 | + lo |= 0x00300000; | |
299 | + wrmsrl(0x51400020, lo); | |
300 | + } | |
301 | + | |
302 | + /* Select level triggered in PIC */ | |
303 | + if (sci_irq < 8) { | |
304 | + lo = inb(CS5536_PIC_INT_SEL1); | |
305 | + lo |= 1 << sci_irq; | |
306 | + outb(lo, CS5536_PIC_INT_SEL1); | |
307 | + } else { | |
308 | + lo = inb(CS5536_PIC_INT_SEL2); | |
309 | + lo |= 1 << (sci_irq - 8); | |
310 | + outb(lo, CS5536_PIC_INT_SEL2); | |
311 | + } | |
312 | + | |
313 | + /* Enable SCI from power button, and clear pending interrupts */ | |
314 | + sts = inl(acpi_base + CS5536_PM1_STS); | |
315 | + outl((CS5536_PM_PWRBTN << 16) | 0xffff, acpi_base + CS5536_PM1_STS); | |
316 | + | |
317 | + r = request_irq(sci_irq, xo1_sci_intr, 0, DRV_NAME, pdev); | |
318 | + if (r) | |
319 | + dev_err(&pdev->dev, "can't request interrupt\n"); | |
320 | + | |
321 | + return r; | |
322 | +} | |
323 | + | |
324 | +static int __devinit setup_ec_sci(void) | |
325 | +{ | |
326 | + int r; | |
327 | + | |
328 | + r = gpio_request(OLPC_GPIO_ECSCI, "OLPC-ECSCI"); | |
329 | + if (r) | |
330 | + return r; | |
331 | + | |
332 | + gpio_direction_input(OLPC_GPIO_ECSCI); | |
333 | + | |
334 | + /* Clear pending EC SCI events */ | |
335 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); | |
336 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS); | |
337 | + | |
338 | + /* | |
339 | + * Enable EC SCI events, and map them to both a PME and the SCI | |
340 | + * interrupt. | |
341 | + * | |
342 | + * Ordinarily, in addition to functioning as GPIOs, Geode GPIOs can | |
343 | + * be mapped to regular interrupts *or* Geode-specific Power | |
344 | + * Management Events (PMEs) - events that bring the system out of | |
345 | + * suspend. In this case, we want both of those things - the system | |
346 | + * wakeup, *and* the ability to get an interrupt when an event occurs. | |
347 | + * | |
348 | + * To achieve this, we map the GPIO to a PME, and then we use one | |
349 | + * of the many generic knobs on the CS5535 PIC to additionally map the | |
350 | + * PME to the regular SCI interrupt line. | |
351 | + */ | |
352 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE); | |
353 | + | |
354 | + /* Set the SCI to cause a PME event on group 7 */ | |
355 | + cs5535_gpio_setup_event(OLPC_GPIO_ECSCI, 7, 1); | |
356 | + | |
357 | + /* And have group 7 also fire the SCI interrupt */ | |
358 | + cs5535_pic_unreqz_select_high(7, sci_irq); | |
359 | + | |
360 | + return 0; | |
361 | +} | |
362 | + | |
363 | +static void free_ec_sci(void) | |
364 | +{ | |
365 | + gpio_free(OLPC_GPIO_ECSCI); | |
366 | +} | |
367 | + | |
368 | +static int __devinit setup_lid_events(void) | |
369 | +{ | |
370 | + int r; | |
371 | + | |
372 | + r = gpio_request(OLPC_GPIO_LID, "OLPC-LID"); | |
373 | + if (r) | |
374 | + return r; | |
375 | + | |
376 | + gpio_direction_input(OLPC_GPIO_LID); | |
377 | + | |
378 | + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); | |
379 | + lid_inverted = 0; | |
380 | + | |
381 | + /* Clear edge detection and event enable for now */ | |
382 | + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | |
383 | + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN); | |
384 | + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN); | |
385 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); | |
386 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); | |
387 | + | |
388 | + /* Set the LID to cause an PME event on group 6 */ | |
389 | + cs5535_gpio_setup_event(OLPC_GPIO_LID, 6, 1); | |
390 | + | |
391 | + /* Set PME group 6 to fire the SCI interrupt */ | |
392 | + cs5535_gpio_set_irq(6, sci_irq); | |
393 | + | |
394 | + /* Enable the event */ | |
395 | + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | |
396 | + | |
397 | + return 0; | |
398 | +} | |
399 | + | |
400 | +static void free_lid_events(void) | |
401 | +{ | |
402 | + gpio_free(OLPC_GPIO_LID); | |
403 | +} | |
404 | + | |
405 | +static int __devinit setup_power_button(struct platform_device *pdev) | |
406 | +{ | |
407 | + int r; | |
408 | + | |
409 | + power_button_idev = input_allocate_device(); | |
410 | + if (!power_button_idev) | |
411 | + return -ENOMEM; | |
412 | + | |
413 | + power_button_idev->name = "Power Button"; | |
414 | + power_button_idev->phys = DRV_NAME "/input0"; | |
415 | + set_bit(EV_KEY, power_button_idev->evbit); | |
416 | + set_bit(KEY_POWER, power_button_idev->keybit); | |
417 | + | |
418 | + power_button_idev->dev.parent = &pdev->dev; | |
419 | + device_init_wakeup(&power_button_idev->dev, 1); | |
420 | + | |
421 | + r = input_register_device(power_button_idev); | |
422 | + if (r) { | |
423 | + dev_err(&pdev->dev, "failed to register power button: %d\n", r); | |
424 | + input_free_device(power_button_idev); | |
425 | + } | |
426 | + | |
427 | + return r; | |
428 | +} | |
429 | + | |
430 | +static void free_power_button(void) | |
431 | +{ | |
432 | + input_unregister_device(power_button_idev); | |
433 | + input_free_device(power_button_idev); | |
434 | +} | |
435 | + | |
436 | +static int __devinit setup_ebook_switch(struct platform_device *pdev) | |
437 | +{ | |
438 | + int r; | |
439 | + | |
440 | + ebook_switch_idev = input_allocate_device(); | |
441 | + if (!ebook_switch_idev) | |
442 | + return -ENOMEM; | |
443 | + | |
444 | + ebook_switch_idev->name = "EBook Switch"; | |
445 | + ebook_switch_idev->phys = DRV_NAME "/input1"; | |
446 | + set_bit(EV_SW, ebook_switch_idev->evbit); | |
447 | + set_bit(SW_TABLET_MODE, ebook_switch_idev->swbit); | |
448 | + | |
449 | + ebook_switch_idev->dev.parent = &pdev->dev; | |
450 | + device_set_wakeup_capable(&ebook_switch_idev->dev, true); | |
451 | + | |
452 | + r = input_register_device(ebook_switch_idev); | |
453 | + if (r) { | |
454 | + dev_err(&pdev->dev, "failed to register ebook switch: %d\n", r); | |
455 | + input_free_device(ebook_switch_idev); | |
456 | + } | |
457 | + | |
458 | + return r; | |
459 | +} | |
460 | + | |
461 | +static void free_ebook_switch(void) | |
462 | +{ | |
463 | + input_unregister_device(ebook_switch_idev); | |
464 | + input_free_device(ebook_switch_idev); | |
465 | +} | |
466 | + | |
467 | +static int __devinit setup_lid_switch(struct platform_device *pdev) | |
468 | +{ | |
469 | + int r; | |
470 | + | |
471 | + lid_switch_idev = input_allocate_device(); | |
472 | + if (!lid_switch_idev) | |
473 | + return -ENOMEM; | |
474 | + | |
475 | + lid_switch_idev->name = "Lid Switch"; | |
476 | + lid_switch_idev->phys = DRV_NAME "/input2"; | |
477 | + set_bit(EV_SW, lid_switch_idev->evbit); | |
478 | + set_bit(SW_LID, lid_switch_idev->swbit); | |
479 | + | |
480 | + lid_switch_idev->dev.parent = &pdev->dev; | |
481 | + device_set_wakeup_capable(&lid_switch_idev->dev, true); | |
482 | + | |
483 | + r = input_register_device(lid_switch_idev); | |
484 | + if (r) { | |
485 | + dev_err(&pdev->dev, "failed to register lid switch: %d\n", r); | |
486 | + goto err_register; | |
487 | + } | |
488 | + | |
489 | + r = device_create_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); | |
490 | + if (r) { | |
491 | + dev_err(&pdev->dev, "failed to create wake mode attr: %d\n", r); | |
492 | + goto err_create_attr; | |
493 | + } | |
494 | + | |
495 | + return 0; | |
496 | + | |
497 | +err_create_attr: | |
498 | + input_unregister_device(lid_switch_idev); | |
499 | +err_register: | |
500 | + input_free_device(lid_switch_idev); | |
501 | + return r; | |
502 | +} | |
503 | + | |
504 | +static void free_lid_switch(void) | |
505 | +{ | |
506 | + device_remove_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); | |
507 | + input_unregister_device(lid_switch_idev); | |
508 | + input_free_device(lid_switch_idev); | |
509 | +} | |
510 | + | |
511 | +static int __devinit xo1_sci_probe(struct platform_device *pdev) | |
512 | +{ | |
513 | + struct resource *res; | |
514 | + int r; | |
515 | + | |
516 | + /* don't run on non-XOs */ | |
517 | + if (!machine_is_olpc()) | |
518 | + return -ENODEV; | |
519 | + | |
520 | + r = mfd_cell_enable(pdev); | |
521 | + if (r) | |
522 | + return r; | |
523 | + | |
524 | + res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
525 | + if (!res) { | |
526 | + dev_err(&pdev->dev, "can't fetch device resource info\n"); | |
527 | + return -EIO; | |
528 | + } | |
529 | + acpi_base = res->start; | |
530 | + | |
531 | + r = setup_power_button(pdev); | |
532 | + if (r) | |
533 | + return r; | |
534 | + | |
535 | + r = setup_ebook_switch(pdev); | |
536 | + if (r) | |
537 | + goto err_ebook; | |
538 | + | |
539 | + r = setup_lid_switch(pdev); | |
540 | + if (r) | |
541 | + goto err_lid; | |
542 | + | |
543 | + r = setup_lid_events(); | |
544 | + if (r) | |
545 | + goto err_lidevt; | |
546 | + | |
547 | + r = setup_ec_sci(); | |
548 | + if (r) | |
549 | + goto err_ecsci; | |
550 | + | |
551 | + /* Enable PME generation for EC-generated events */ | |
552 | + outl(CS5536_GPIOM6_PME_EN | CS5536_GPIOM7_PME_EN, | |
553 | + acpi_base + CS5536_PM_GPE0_EN); | |
554 | + | |
555 | + /* Clear pending events */ | |
556 | + outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); | |
557 | + process_sci_queue(false); | |
558 | + | |
559 | + /* Initial sync */ | |
560 | + send_ebook_state(); | |
561 | + detect_lid_state(); | |
562 | + send_lid_state(); | |
563 | + | |
564 | + r = setup_sci_interrupt(pdev); | |
565 | + if (r) | |
566 | + goto err_sci; | |
567 | + | |
568 | + /* Enable all EC events */ | |
569 | + olpc_ec_mask_write(EC_SCI_SRC_ALL); | |
570 | + | |
571 | + return r; | |
572 | + | |
573 | +err_sci: | |
574 | + free_ec_sci(); | |
575 | +err_ecsci: | |
576 | + free_lid_events(); | |
577 | +err_lidevt: | |
578 | + free_lid_switch(); | |
579 | +err_lid: | |
580 | + free_ebook_switch(); | |
581 | +err_ebook: | |
582 | + free_power_button(); | |
583 | + return r; | |
584 | +} | |
585 | + | |
586 | +static int __devexit xo1_sci_remove(struct platform_device *pdev) | |
587 | +{ | |
588 | + mfd_cell_disable(pdev); | |
589 | + free_irq(sci_irq, pdev); | |
590 | + cancel_work_sync(&sci_work); | |
591 | + free_ec_sci(); | |
592 | + free_lid_events(); | |
593 | + free_lid_switch(); | |
594 | + free_ebook_switch(); | |
595 | + free_power_button(); | |
596 | + acpi_base = 0; | |
597 | + return 0; | |
598 | +} | |
599 | + | |
600 | +static struct platform_driver xo1_sci_driver = { | |
601 | + .driver = { | |
602 | + .name = "olpc-xo1-sci-acpi", | |
603 | + }, | |
604 | + .probe = xo1_sci_probe, | |
605 | + .remove = __devexit_p(xo1_sci_remove), | |
606 | + .suspend = xo1_sci_suspend, | |
607 | + .resume = xo1_sci_resume, | |
608 | +}; | |
609 | + | |
610 | +static int __init xo1_sci_init(void) | |
611 | +{ | |
612 | + return platform_driver_register(&xo1_sci_driver); | |
613 | +} | |
614 | +arch_initcall(xo1_sci_init); |
arch/x86/platform/olpc/olpc-xo1.c
1 | -/* | |
2 | - * Support for features of the OLPC XO-1 laptop | |
3 | - * | |
4 | - * Copyright (C) 2010 Andres Salomon <dilinger@queued.net> | |
5 | - * Copyright (C) 2010 One Laptop per Child | |
6 | - * Copyright (C) 2006 Red Hat, Inc. | |
7 | - * Copyright (C) 2006 Advanced Micro Devices, Inc. | |
8 | - * | |
9 | - * This program is free software; you can redistribute it and/or modify | |
10 | - * it under the terms of the GNU General Public License as published by | |
11 | - * the Free Software Foundation; either version 2 of the License, or | |
12 | - * (at your option) any later version. | |
13 | - */ | |
14 | - | |
15 | -#include <linux/module.h> | |
16 | -#include <linux/platform_device.h> | |
17 | -#include <linux/pm.h> | |
18 | -#include <linux/mfd/core.h> | |
19 | - | |
20 | -#include <asm/io.h> | |
21 | -#include <asm/olpc.h> | |
22 | - | |
23 | -#define DRV_NAME "olpc-xo1" | |
24 | - | |
25 | -/* PMC registers (PMS block) */ | |
26 | -#define PM_SCLK 0x10 | |
27 | -#define PM_IN_SLPCTL 0x20 | |
28 | -#define PM_WKXD 0x34 | |
29 | -#define PM_WKD 0x30 | |
30 | -#define PM_SSC 0x54 | |
31 | - | |
32 | -/* PM registers (ACPI block) */ | |
33 | -#define PM1_CNT 0x08 | |
34 | -#define PM_GPE0_STS 0x18 | |
35 | - | |
36 | -static unsigned long acpi_base; | |
37 | -static unsigned long pms_base; | |
38 | - | |
39 | -static void xo1_power_off(void) | |
40 | -{ | |
41 | - printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); | |
42 | - | |
43 | - /* Enable all of these controls with 0 delay */ | |
44 | - outl(0x40000000, pms_base + PM_SCLK); | |
45 | - outl(0x40000000, pms_base + PM_IN_SLPCTL); | |
46 | - outl(0x40000000, pms_base + PM_WKXD); | |
47 | - outl(0x40000000, pms_base + PM_WKD); | |
48 | - | |
49 | - /* Clear status bits (possibly unnecessary) */ | |
50 | - outl(0x0002ffff, pms_base + PM_SSC); | |
51 | - outl(0xffffffff, acpi_base + PM_GPE0_STS); | |
52 | - | |
53 | - /* Write SLP_EN bit to start the machinery */ | |
54 | - outl(0x00002000, acpi_base + PM1_CNT); | |
55 | -} | |
56 | - | |
57 | -static int __devinit olpc_xo1_probe(struct platform_device *pdev) | |
58 | -{ | |
59 | - struct resource *res; | |
60 | - int err; | |
61 | - | |
62 | - /* don't run on non-XOs */ | |
63 | - if (!machine_is_olpc()) | |
64 | - return -ENODEV; | |
65 | - | |
66 | - err = mfd_cell_enable(pdev); | |
67 | - if (err) | |
68 | - return err; | |
69 | - | |
70 | - res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
71 | - if (!res) { | |
72 | - dev_err(&pdev->dev, "can't fetch device resource info\n"); | |
73 | - return -EIO; | |
74 | - } | |
75 | - if (strcmp(pdev->name, "cs5535-pms") == 0) | |
76 | - pms_base = res->start; | |
77 | - else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) | |
78 | - acpi_base = res->start; | |
79 | - | |
80 | - /* If we have both addresses, we can override the poweroff hook */ | |
81 | - if (pms_base && acpi_base) { | |
82 | - pm_power_off = xo1_power_off; | |
83 | - printk(KERN_INFO "OLPC XO-1 support registered\n"); | |
84 | - } | |
85 | - | |
86 | - return 0; | |
87 | -} | |
88 | - | |
89 | -static int __devexit olpc_xo1_remove(struct platform_device *pdev) | |
90 | -{ | |
91 | - mfd_cell_disable(pdev); | |
92 | - | |
93 | - if (strcmp(pdev->name, "cs5535-pms") == 0) | |
94 | - pms_base = 0; | |
95 | - else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) | |
96 | - acpi_base = 0; | |
97 | - | |
98 | - pm_power_off = NULL; | |
99 | - return 0; | |
100 | -} | |
101 | - | |
102 | -static struct platform_driver cs5535_pms_drv = { | |
103 | - .driver = { | |
104 | - .name = "cs5535-pms", | |
105 | - .owner = THIS_MODULE, | |
106 | - }, | |
107 | - .probe = olpc_xo1_probe, | |
108 | - .remove = __devexit_p(olpc_xo1_remove), | |
109 | -}; | |
110 | - | |
111 | -static struct platform_driver cs5535_acpi_drv = { | |
112 | - .driver = { | |
113 | - .name = "olpc-xo1-pm-acpi", | |
114 | - .owner = THIS_MODULE, | |
115 | - }, | |
116 | - .probe = olpc_xo1_probe, | |
117 | - .remove = __devexit_p(olpc_xo1_remove), | |
118 | -}; | |
119 | - | |
120 | -static int __init olpc_xo1_init(void) | |
121 | -{ | |
122 | - int r; | |
123 | - | |
124 | - r = platform_driver_register(&cs5535_pms_drv); | |
125 | - if (r) | |
126 | - return r; | |
127 | - | |
128 | - r = platform_driver_register(&cs5535_acpi_drv); | |
129 | - if (r) | |
130 | - platform_driver_unregister(&cs5535_pms_drv); | |
131 | - | |
132 | - return r; | |
133 | -} | |
134 | - | |
135 | -static void __exit olpc_xo1_exit(void) | |
136 | -{ | |
137 | - platform_driver_unregister(&cs5535_acpi_drv); | |
138 | - platform_driver_unregister(&cs5535_pms_drv); | |
139 | -} | |
140 | - | |
141 | -MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>"); | |
142 | -MODULE_LICENSE("GPL"); | |
143 | -MODULE_ALIAS("platform:cs5535-pms"); | |
144 | - | |
145 | -module_init(olpc_xo1_init); | |
146 | -module_exit(olpc_xo1_exit); |
arch/x86/platform/olpc/olpc-xo15-sci.c
1 | +/* | |
2 | + * Support for OLPC XO-1.5 System Control Interrupts (SCI) | |
3 | + * | |
4 | + * Copyright (C) 2009-2010 One Laptop per Child | |
5 | + * | |
6 | + * This program is free software; you can redistribute it and/or modify | |
7 | + * it under the terms of the GNU General Public License as published by | |
8 | + * the Free Software Foundation; either version 2 of the License, or | |
9 | + * (at your option) any later version. | |
10 | + */ | |
11 | + | |
12 | +#include <linux/device.h> | |
13 | +#include <linux/slab.h> | |
14 | +#include <linux/workqueue.h> | |
15 | +#include <linux/power_supply.h> | |
16 | + | |
17 | +#include <acpi/acpi_bus.h> | |
18 | +#include <acpi/acpi_drivers.h> | |
19 | +#include <asm/olpc.h> | |
20 | + | |
21 | +#define DRV_NAME "olpc-xo15-sci" | |
22 | +#define PFX DRV_NAME ": " | |
23 | +#define XO15_SCI_CLASS DRV_NAME | |
24 | +#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" | |
25 | + | |
26 | +static unsigned long xo15_sci_gpe; | |
27 | + | |
28 | +static void battery_status_changed(void) | |
29 | +{ | |
30 | + struct power_supply *psy = power_supply_get_by_name("olpc-battery"); | |
31 | + | |
32 | + if (psy) { | |
33 | + power_supply_changed(psy); | |
34 | + put_device(psy->dev); | |
35 | + } | |
36 | +} | |
37 | + | |
38 | +static void ac_status_changed(void) | |
39 | +{ | |
40 | + struct power_supply *psy = power_supply_get_by_name("olpc-ac"); | |
41 | + | |
42 | + if (psy) { | |
43 | + power_supply_changed(psy); | |
44 | + put_device(psy->dev); | |
45 | + } | |
46 | +} | |
47 | + | |
48 | +static void process_sci_queue(void) | |
49 | +{ | |
50 | + u16 data; | |
51 | + int r; | |
52 | + | |
53 | + do { | |
54 | + r = olpc_ec_sci_query(&data); | |
55 | + if (r || !data) | |
56 | + break; | |
57 | + | |
58 | + pr_debug(PFX "SCI 0x%x received\n", data); | |
59 | + | |
60 | + switch (data) { | |
61 | + case EC_SCI_SRC_BATERR: | |
62 | + case EC_SCI_SRC_BATSOC: | |
63 | + case EC_SCI_SRC_BATTERY: | |
64 | + case EC_SCI_SRC_BATCRIT: | |
65 | + battery_status_changed(); | |
66 | + break; | |
67 | + case EC_SCI_SRC_ACPWR: | |
68 | + ac_status_changed(); | |
69 | + break; | |
70 | + } | |
71 | + } while (data); | |
72 | + | |
73 | + if (r) | |
74 | + pr_err(PFX "Failed to clear SCI queue"); | |
75 | +} | |
76 | + | |
77 | +static void process_sci_queue_work(struct work_struct *work) | |
78 | +{ | |
79 | + process_sci_queue(); | |
80 | +} | |
81 | + | |
82 | +static DECLARE_WORK(sci_work, process_sci_queue_work); | |
83 | + | |
84 | +static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context) | |
85 | +{ | |
86 | + schedule_work(&sci_work); | |
87 | + return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; | |
88 | +} | |
89 | + | |
90 | +static int xo15_sci_add(struct acpi_device *device) | |
91 | +{ | |
92 | + unsigned long long tmp; | |
93 | + acpi_status status; | |
94 | + | |
95 | + if (!device) | |
96 | + return -EINVAL; | |
97 | + | |
98 | + strcpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); | |
99 | + strcpy(acpi_device_class(device), XO15_SCI_CLASS); | |
100 | + | |
101 | + /* Get GPE bit assignment (EC events). */ | |
102 | + status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); | |
103 | + if (ACPI_FAILURE(status)) | |
104 | + return -EINVAL; | |
105 | + | |
106 | + xo15_sci_gpe = tmp; | |
107 | + status = acpi_install_gpe_handler(NULL, xo15_sci_gpe, | |
108 | + ACPI_GPE_EDGE_TRIGGERED, | |
109 | + xo15_sci_gpe_handler, device); | |
110 | + if (ACPI_FAILURE(status)) | |
111 | + return -ENODEV; | |
112 | + | |
113 | + dev_info(&device->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe); | |
114 | + | |
115 | + /* Flush queue, and enable all SCI events */ | |
116 | + process_sci_queue(); | |
117 | + olpc_ec_mask_write(EC_SCI_SRC_ALL); | |
118 | + | |
119 | + acpi_enable_gpe(NULL, xo15_sci_gpe); | |
120 | + | |
121 | + /* Enable wake-on-EC */ | |
122 | + if (device->wakeup.flags.valid) | |
123 | + device_init_wakeup(&device->dev, true); | |
124 | + | |
125 | + return 0; | |
126 | +} | |
127 | + | |
128 | +static int xo15_sci_remove(struct acpi_device *device, int type) | |
129 | +{ | |
130 | + acpi_disable_gpe(NULL, xo15_sci_gpe); | |
131 | + acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler); | |
132 | + cancel_work_sync(&sci_work); | |
133 | + return 0; | |
134 | +} | |
135 | + | |
136 | +static int xo15_sci_resume(struct acpi_device *device) | |
137 | +{ | |
138 | + /* Enable all EC events */ | |
139 | + olpc_ec_mask_write(EC_SCI_SRC_ALL); | |
140 | + | |
141 | + /* Power/battery status might have changed */ | |
142 | + battery_status_changed(); | |
143 | + ac_status_changed(); | |
144 | + | |
145 | + return 0; | |
146 | +} | |
147 | + | |
148 | +static const struct acpi_device_id xo15_sci_device_ids[] = { | |
149 | + {"XO15EC", 0}, | |
150 | + {"", 0}, | |
151 | +}; | |
152 | + | |
153 | +static struct acpi_driver xo15_sci_drv = { | |
154 | + .name = DRV_NAME, | |
155 | + .class = XO15_SCI_CLASS, | |
156 | + .ids = xo15_sci_device_ids, | |
157 | + .ops = { | |
158 | + .add = xo15_sci_add, | |
159 | + .remove = xo15_sci_remove, | |
160 | + .resume = xo15_sci_resume, | |
161 | + }, | |
162 | +}; | |
163 | + | |
164 | +static int __init xo15_sci_init(void) | |
165 | +{ | |
166 | + return acpi_bus_register_driver(&xo15_sci_drv); | |
167 | +} | |
168 | +device_initcall(xo15_sci_init); |
arch/x86/platform/olpc/olpc.c
... | ... | @@ -19,6 +19,7 @@ |
19 | 19 | #include <linux/string.h> |
20 | 20 | #include <linux/platform_device.h> |
21 | 21 | #include <linux/of.h> |
22 | +#include <linux/syscore_ops.h> | |
22 | 23 | |
23 | 24 | #include <asm/geode.h> |
24 | 25 | #include <asm/setup.h> |
... | ... | @@ -30,6 +31,9 @@ |
30 | 31 | |
31 | 32 | static DEFINE_SPINLOCK(ec_lock); |
32 | 33 | |
34 | +/* EC event mask to be applied during suspend (defining wakeup sources). */ | |
35 | +static u16 ec_wakeup_mask; | |
36 | + | |
33 | 37 | /* what the timeout *should* be (in ms) */ |
34 | 38 | #define EC_BASE_TIMEOUT 20 |
35 | 39 | |
... | ... | @@ -188,6 +192,88 @@ |
188 | 192 | } |
189 | 193 | EXPORT_SYMBOL_GPL(olpc_ec_cmd); |
190 | 194 | |
195 | +void olpc_ec_wakeup_set(u16 value) | |
196 | +{ | |
197 | + ec_wakeup_mask |= value; | |
198 | +} | |
199 | +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set); | |
200 | + | |
201 | +void olpc_ec_wakeup_clear(u16 value) | |
202 | +{ | |
203 | + ec_wakeup_mask &= ~value; | |
204 | +} | |
205 | +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear); | |
206 | + | |
207 | +/* | |
208 | + * Returns true if the compile and runtime configurations allow for EC events | |
209 | + * to wake the system. | |
210 | + */ | |
211 | +bool olpc_ec_wakeup_available(void) | |
212 | +{ | |
213 | + if (!machine_is_olpc()) | |
214 | + return false; | |
215 | + | |
216 | + /* | |
217 | + * XO-1 EC wakeups are available when olpc-xo1-sci driver is | |
218 | + * compiled in | |
219 | + */ | |
220 | +#ifdef CONFIG_OLPC_XO1_SCI | |
221 | + if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */ | |
222 | + return true; | |
223 | +#endif | |
224 | + | |
225 | + /* | |
226 | + * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is | |
227 | + * compiled in | |
228 | + */ | |
229 | +#ifdef CONFIG_OLPC_XO15_SCI | |
230 | + if (olpc_platform_info.boardrev >= olpc_board_pre(0xd0)) /* XO-1.5 */ | |
231 | + return true; | |
232 | +#endif | |
233 | + | |
234 | + return false; | |
235 | +} | |
236 | +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available); | |
237 | + | |
238 | +int olpc_ec_mask_write(u16 bits) | |
239 | +{ | |
240 | + if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { | |
241 | + __be16 ec_word = cpu_to_be16(bits); | |
242 | + return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *) &ec_word, 2, | |
243 | + NULL, 0); | |
244 | + } else { | |
245 | + unsigned char ec_byte = bits & 0xff; | |
246 | + return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0); | |
247 | + } | |
248 | +} | |
249 | +EXPORT_SYMBOL_GPL(olpc_ec_mask_write); | |
250 | + | |
251 | +int olpc_ec_sci_query(u16 *sci_value) | |
252 | +{ | |
253 | + int ret; | |
254 | + | |
255 | + if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { | |
256 | + __be16 ec_word; | |
257 | + ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, | |
258 | + NULL, 0, (void *) &ec_word, 2); | |
259 | + if (ret == 0) | |
260 | + *sci_value = be16_to_cpu(ec_word); | |
261 | + } else { | |
262 | + unsigned char ec_byte; | |
263 | + ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1); | |
264 | + if (ret == 0) | |
265 | + *sci_value = ec_byte; | |
266 | + } | |
267 | + | |
268 | + return ret; | |
269 | +} | |
270 | +EXPORT_SYMBOL_GPL(olpc_ec_sci_query); | |
271 | + | |
272 | +static int olpc_ec_suspend(void) | |
273 | +{ | |
274 | + return olpc_ec_mask_write(ec_wakeup_mask); | |
275 | +} | |
276 | + | |
191 | 277 | static bool __init check_ofw_architecture(struct device_node *root) |
192 | 278 | { |
193 | 279 | const char *olpc_arch; |
... | ... | @@ -242,6 +328,10 @@ |
242 | 328 | return 0; |
243 | 329 | } |
244 | 330 | |
331 | +static struct syscore_ops olpc_syscore_ops = { | |
332 | + .suspend = olpc_ec_suspend, | |
333 | +}; | |
334 | + | |
245 | 335 | static int __init olpc_init(void) |
246 | 336 | { |
247 | 337 | int r = 0; |
... | ... | @@ -266,6 +356,9 @@ |
266 | 356 | !cs5535_has_vsa2()) |
267 | 357 | x86_init.pci.arch_init = pci_olpc_init; |
268 | 358 | #endif |
359 | + /* EC version 0x5f adds support for wide SCI mask */ | |
360 | + if (olpc_platform_info.ecver >= 0x5f) | |
361 | + olpc_platform_info.flags |= OLPC_F_EC_WIDE_SCI; | |
269 | 362 | |
270 | 363 | printk(KERN_INFO "OLPC board revision %s%X (EC=%x)\n", |
271 | 364 | ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", |
... | ... | @@ -277,6 +370,8 @@ |
277 | 370 | if (r) |
278 | 371 | return r; |
279 | 372 | } |
373 | + | |
374 | + register_syscore_ops(&olpc_syscore_ops); | |
280 | 375 | |
281 | 376 | return 0; |
282 | 377 | } |
arch/x86/platform/olpc/olpc_dt.c
... | ... | @@ -165,12 +165,115 @@ |
165 | 165 | .pkg2path = olpc_dt_pkg2path, |
166 | 166 | }; |
167 | 167 | |
168 | +static phandle __init olpc_dt_finddevice(const char *path) | |
169 | +{ | |
170 | + phandle node; | |
171 | + const void *args[] = { path }; | |
172 | + void *res[] = { &node }; | |
173 | + | |
174 | + if (olpc_ofw("finddevice", args, res)) { | |
175 | + pr_err("olpc_dt: finddevice failed!\n"); | |
176 | + return 0; | |
177 | + } | |
178 | + | |
179 | + if ((s32) node == -1) | |
180 | + return 0; | |
181 | + | |
182 | + return node; | |
183 | +} | |
184 | + | |
185 | +static int __init olpc_dt_interpret(const char *words) | |
186 | +{ | |
187 | + int result; | |
188 | + const void *args[] = { words }; | |
189 | + void *res[] = { &result }; | |
190 | + | |
191 | + if (olpc_ofw("interpret", args, res)) { | |
192 | + pr_err("olpc_dt: interpret failed!\n"); | |
193 | + return -1; | |
194 | + } | |
195 | + | |
196 | + return result; | |
197 | +} | |
198 | + | |
199 | +/* | |
200 | + * Extract board revision directly from OFW device tree. | |
201 | + * We can't use olpc_platform_info because that hasn't been set up yet. | |
202 | + */ | |
203 | +static u32 __init olpc_dt_get_board_revision(void) | |
204 | +{ | |
205 | + phandle node; | |
206 | + __be32 rev; | |
207 | + int r; | |
208 | + | |
209 | + node = olpc_dt_finddevice("/"); | |
210 | + if (!node) | |
211 | + return 0; | |
212 | + | |
213 | + r = olpc_dt_getproperty(node, "board-revision-int", | |
214 | + (char *) &rev, sizeof(rev)); | |
215 | + if (r < 0) | |
216 | + return 0; | |
217 | + | |
218 | + return be32_to_cpu(rev); | |
219 | +} | |
220 | + | |
221 | +void __init olpc_dt_fixup(void) | |
222 | +{ | |
223 | + int r; | |
224 | + char buf[64]; | |
225 | + phandle node; | |
226 | + u32 board_rev; | |
227 | + | |
228 | + node = olpc_dt_finddevice("/battery@0"); | |
229 | + if (!node) | |
230 | + return; | |
231 | + | |
232 | + /* | |
233 | + * If the battery node has a compatible property, we are running a new | |
234 | + * enough firmware and don't have fixups to make. | |
235 | + */ | |
236 | + r = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf)); | |
237 | + if (r > 0) | |
238 | + return; | |
239 | + | |
240 | + pr_info("PROM DT: Old firmware detected, applying fixes\n"); | |
241 | + | |
242 | + /* Add olpc,xo1-battery compatible marker to battery node */ | |
243 | + olpc_dt_interpret("\" /battery@0\" find-device" | |
244 | + " \" olpc,xo1-battery\" +compatible" | |
245 | + " device-end"); | |
246 | + | |
247 | + board_rev = olpc_dt_get_board_revision(); | |
248 | + if (!board_rev) | |
249 | + return; | |
250 | + | |
251 | + if (board_rev >= olpc_board_pre(0xd0)) { | |
252 | + /* XO-1.5: add dcon device */ | |
253 | + olpc_dt_interpret("\" /pci/display@1\" find-device" | |
254 | + " new-device" | |
255 | + " \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" | |
256 | + " finish-device device-end"); | |
257 | + } else { | |
258 | + /* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */ | |
259 | + olpc_dt_interpret("\" /pci/display@1,1\" find-device" | |
260 | + " new-device" | |
261 | + " \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" | |
262 | + " finish-device device-end" | |
263 | + " \" /rtc\" find-device" | |
264 | + " \" olpc,xo1-rtc\" +compatible" | |
265 | + " device-end"); | |
266 | + } | |
267 | +} | |
268 | + | |
168 | 269 | void __init olpc_dt_build_devicetree(void) |
169 | 270 | { |
170 | 271 | phandle root; |
171 | 272 | |
172 | 273 | if (!olpc_ofw_is_installed()) |
173 | 274 | return; |
275 | + | |
276 | + olpc_dt_fixup(); | |
174 | 277 | |
175 | 278 | root = olpc_dt_getsibling(0); |
176 | 279 | if (!root) { |
arch/x86/platform/olpc/xo1-wakeup.S
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 |
include/linux/cs5535.h
... | ... | @@ -11,6 +11,8 @@ |
11 | 11 | #ifndef _CS5535_H |
12 | 12 | #define _CS5535_H |
13 | 13 | |
14 | +#include <asm/msr.h> | |
15 | + | |
14 | 16 | /* MSRs */ |
15 | 17 | #define MSR_GLIU_P2D_RO0 0x10000029 |
16 | 18 | |
17 | 19 | |
18 | 20 | |
... | ... | @@ -38,16 +40,74 @@ |
38 | 40 | #define MSR_MFGPT_NR 0x51400029 |
39 | 41 | #define MSR_MFGPT_SETUP 0x5140002B |
40 | 42 | |
43 | +#define MSR_RTC_DOMA_OFFSET 0x51400055 | |
44 | +#define MSR_RTC_MONA_OFFSET 0x51400056 | |
45 | +#define MSR_RTC_CEN_OFFSET 0x51400057 | |
46 | + | |
41 | 47 | #define MSR_LX_SPARE_MSR 0x80000011 /* DC-specific */ |
42 | 48 | |
43 | 49 | #define MSR_GX_GLD_MSR_CONFIG 0xC0002001 |
44 | 50 | #define MSR_GX_MSR_PADSEL 0xC0002011 |
45 | 51 | |
52 | +static inline int cs5535_pic_unreqz_select_high(unsigned int group, | |
53 | + unsigned int irq) | |
54 | +{ | |
55 | + uint32_t lo, hi; | |
56 | + | |
57 | + rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi); | |
58 | + lo &= ~(0xF << (group * 4)); | |
59 | + lo |= (irq & 0xF) << (group * 4); | |
60 | + wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi); | |
61 | + return 0; | |
62 | +} | |
63 | + | |
64 | +/* PIC registers */ | |
65 | +#define CS5536_PIC_INT_SEL1 0x4d0 | |
66 | +#define CS5536_PIC_INT_SEL2 0x4d1 | |
67 | + | |
46 | 68 | /* resource sizes */ |
47 | 69 | #define LBAR_GPIO_SIZE 0xFF |
48 | 70 | #define LBAR_MFGPT_SIZE 0x40 |
49 | 71 | #define LBAR_ACPI_SIZE 0x40 |
50 | 72 | #define LBAR_PMS_SIZE 0x80 |
73 | + | |
74 | +/* | |
75 | + * PMC registers (PMS block) | |
76 | + * It is only safe to access these registers as dword accesses. | |
77 | + * See CS5536 Specification Update erratas 17 & 18 | |
78 | + */ | |
79 | +#define CS5536_PM_SCLK 0x10 | |
80 | +#define CS5536_PM_IN_SLPCTL 0x20 | |
81 | +#define CS5536_PM_WKXD 0x34 | |
82 | +#define CS5536_PM_WKD 0x30 | |
83 | +#define CS5536_PM_SSC 0x54 | |
84 | + | |
85 | +/* | |
86 | + * PM registers (ACPI block) | |
87 | + * It is only safe to access these registers as dword accesses. | |
88 | + * See CS5536 Specification Update erratas 17 & 18 | |
89 | + */ | |
90 | +#define CS5536_PM1_STS 0x00 | |
91 | +#define CS5536_PM1_EN 0x02 | |
92 | +#define CS5536_PM1_CNT 0x08 | |
93 | +#define CS5536_PM_GPE0_STS 0x18 | |
94 | +#define CS5536_PM_GPE0_EN 0x1c | |
95 | + | |
96 | +/* CS5536_PM1_STS bits */ | |
97 | +#define CS5536_WAK_FLAG (1 << 15) | |
98 | +#define CS5536_PWRBTN_FLAG (1 << 8) | |
99 | + | |
100 | +/* CS5536_PM1_EN bits */ | |
101 | +#define CS5536_PM_PWRBTN (1 << 8) | |
102 | +#define CS5536_PM_RTC (1 << 10) | |
103 | + | |
104 | +/* CS5536_PM_GPE0_STS bits */ | |
105 | +#define CS5536_GPIOM7_PME_FLAG (1 << 31) | |
106 | +#define CS5536_GPIOM6_PME_FLAG (1 << 30) | |
107 | + | |
108 | +/* CS5536_PM_GPE0_EN bits */ | |
109 | +#define CS5536_GPIOM7_PME_EN (1 << 31) | |
110 | +#define CS5536_GPIOM6_PME_EN (1 << 30) | |
51 | 111 | |
52 | 112 | /* VSA2 magic values */ |
53 | 113 | #define VSA_VRC_INDEX 0xAC1C |