Commit 7bc74b3df73776fe06f3df9fafd2d2698e6ca28a
Committed by
H. Peter Anvin
1 parent
bc4ecd5a5e
Exists in
master
and in
20 other branches
x86, olpc-xo1-sci: Add GPE handler and ebook switch functionality
The EC in the OLPC XO-1 delivers GPE events to provide various notifications. Add the basic code for GPE/EC event processing and enable the ebook switch, which can be used as a wakeup source. Signed-off-by: Daniel Drake <dsd@laptop.org> Link: http://lkml.kernel.org/r/1309019658-1712-8-git-send-email-dsd@laptop.org Acked-by: Andres Salomon <dilinger@queued.net> Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
Showing 4 changed files with 209 additions and 3 deletions Side-by-side Diff
arch/x86/Kconfig
arch/x86/include/asm/olpc.h
... | ... | @@ -111,6 +111,7 @@ |
111 | 111 | #define EC_WRITE_SCI_MASK 0x1b |
112 | 112 | #define EC_WAKE_UP_WLAN 0x24 |
113 | 113 | #define EC_WLAN_LEAVE_RESET 0x25 |
114 | +#define EC_READ_EB_MODE 0x2a | |
114 | 115 | #define EC_SET_SCI_INHIBIT 0x32 |
115 | 116 | #define EC_SET_SCI_INHIBIT_RELEASE 0x34 |
116 | 117 | #define EC_WLAN_ENTER_RESET 0x35 |
... | ... | @@ -144,8 +145,8 @@ |
144 | 145 | #define OLPC_GPIO_SMB_CLK 14 |
145 | 146 | #define OLPC_GPIO_SMB_DATA 15 |
146 | 147 | #define OLPC_GPIO_WORKAUX geode_gpio(24) |
147 | -#define OLPC_GPIO_LID geode_gpio(26) | |
148 | -#define OLPC_GPIO_ECSCI geode_gpio(27) | |
148 | +#define OLPC_GPIO_LID 26 | |
149 | +#define OLPC_GPIO_ECSCI 27 | |
149 | 150 | |
150 | 151 | #endif /* _ASM_X86_OLPC_H */ |
arch/x86/platform/olpc/olpc-xo1-sci.c
... | ... | @@ -12,12 +12,15 @@ |
12 | 12 | */ |
13 | 13 | |
14 | 14 | #include <linux/cs5535.h> |
15 | +#include <linux/device.h> | |
16 | +#include <linux/gpio.h> | |
15 | 17 | #include <linux/input.h> |
16 | 18 | #include <linux/interrupt.h> |
17 | 19 | #include <linux/platform_device.h> |
18 | 20 | #include <linux/pm.h> |
19 | 21 | #include <linux/mfd/core.h> |
20 | 22 | #include <linux/suspend.h> |
23 | +#include <linux/workqueue.h> | |
21 | 24 | |
22 | 25 | #include <asm/io.h> |
23 | 26 | #include <asm/msr.h> |
24 | 27 | |
... | ... | @@ -28,8 +31,60 @@ |
28 | 31 | |
29 | 32 | static unsigned long acpi_base; |
30 | 33 | static struct input_dev *power_button_idev; |
34 | +static struct input_dev *ebook_switch_idev; | |
35 | + | |
31 | 36 | static int sci_irq; |
32 | 37 | |
38 | +/* Report current ebook switch state through input layer */ | |
39 | +static void send_ebook_state(void) | |
40 | +{ | |
41 | + unsigned char state; | |
42 | + | |
43 | + if (olpc_ec_cmd(EC_READ_EB_MODE, NULL, 0, &state, 1)) { | |
44 | + pr_err(PFX "failed to get ebook state\n"); | |
45 | + return; | |
46 | + } | |
47 | + | |
48 | + input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state); | |
49 | + input_sync(ebook_switch_idev); | |
50 | +} | |
51 | + | |
52 | +/* | |
53 | + * Process all items in the EC's SCI queue. | |
54 | + * | |
55 | + * This is handled in a workqueue because olpc_ec_cmd can be slow (and | |
56 | + * can even timeout). | |
57 | + * | |
58 | + * If propagate_events is false, the queue is drained without events being | |
59 | + * generated for the interrupts. | |
60 | + */ | |
61 | +static void process_sci_queue(bool propagate_events) | |
62 | +{ | |
63 | + int r; | |
64 | + u16 data; | |
65 | + | |
66 | + do { | |
67 | + r = olpc_ec_sci_query(&data); | |
68 | + if (r || !data) | |
69 | + break; | |
70 | + | |
71 | + pr_debug(PFX "SCI 0x%x received\n", data); | |
72 | + | |
73 | + if (data == EC_SCI_SRC_EBOOK && propagate_events) | |
74 | + send_ebook_state(); | |
75 | + } while (data); | |
76 | + | |
77 | + if (r) | |
78 | + pr_err(PFX "Failed to clear SCI queue"); | |
79 | +} | |
80 | + | |
81 | +static void process_sci_queue_work(struct work_struct *work) | |
82 | +{ | |
83 | + process_sci_queue(true); | |
84 | +} | |
85 | + | |
86 | +static DECLARE_WORK(sci_work, process_sci_queue_work); | |
87 | + | |
33 | 88 | static irqreturn_t xo1_sci_intr(int irq, void *dev_id) |
34 | 89 | { |
35 | 90 | struct platform_device *pdev = dev_id; |
... | ... | @@ -51,6 +106,11 @@ |
51 | 106 | input_sync(power_button_idev); |
52 | 107 | } |
53 | 108 | |
109 | + if (gpe & CS5536_GPIOM7_PME_FLAG) { /* EC GPIO */ | |
110 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); | |
111 | + schedule_work(&sci_work); | |
112 | + } | |
113 | + | |
54 | 114 | return IRQ_HANDLED; |
55 | 115 | } |
56 | 116 | |
57 | 117 | |
... | ... | @@ -60,9 +120,22 @@ |
60 | 120 | olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); |
61 | 121 | else |
62 | 122 | olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN); |
123 | + | |
124 | + if (device_may_wakeup(&ebook_switch_idev->dev)) | |
125 | + olpc_ec_wakeup_set(EC_SCI_SRC_EBOOK); | |
126 | + else | |
127 | + olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); | |
128 | + | |
63 | 129 | return 0; |
64 | 130 | } |
65 | 131 | |
132 | +static int xo1_sci_resume(struct platform_device *pdev) | |
133 | +{ | |
134 | + /* Enable all EC events */ | |
135 | + olpc_ec_mask_write(EC_SCI_SRC_ALL); | |
136 | + return 0; | |
137 | +} | |
138 | + | |
66 | 139 | static int __devinit setup_sci_interrupt(struct platform_device *pdev) |
67 | 140 | { |
68 | 141 | u32 lo, hi; |
... | ... | @@ -104,6 +177,50 @@ |
104 | 177 | return r; |
105 | 178 | } |
106 | 179 | |
180 | +static int __devinit setup_ec_sci(void) | |
181 | +{ | |
182 | + int r; | |
183 | + | |
184 | + r = gpio_request(OLPC_GPIO_ECSCI, "OLPC-ECSCI"); | |
185 | + if (r) | |
186 | + return r; | |
187 | + | |
188 | + gpio_direction_input(OLPC_GPIO_ECSCI); | |
189 | + | |
190 | + /* Clear pending EC SCI events */ | |
191 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); | |
192 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS); | |
193 | + | |
194 | + /* | |
195 | + * Enable EC SCI events, and map them to both a PME and the SCI | |
196 | + * interrupt. | |
197 | + * | |
198 | + * Ordinarily, in addition to functioning as GPIOs, Geode GPIOs can | |
199 | + * be mapped to regular interrupts *or* Geode-specific Power | |
200 | + * Management Events (PMEs) - events that bring the system out of | |
201 | + * suspend. In this case, we want both of those things - the system | |
202 | + * wakeup, *and* the ability to get an interrupt when an event occurs. | |
203 | + * | |
204 | + * To achieve this, we map the GPIO to a PME, and then we use one | |
205 | + * of the many generic knobs on the CS5535 PIC to additionally map the | |
206 | + * PME to the regular SCI interrupt line. | |
207 | + */ | |
208 | + cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE); | |
209 | + | |
210 | + /* Set the SCI to cause a PME event on group 7 */ | |
211 | + cs5535_gpio_setup_event(OLPC_GPIO_ECSCI, 7, 1); | |
212 | + | |
213 | + /* And have group 7 also fire the SCI interrupt */ | |
214 | + cs5535_pic_unreqz_select_high(7, sci_irq); | |
215 | + | |
216 | + return 0; | |
217 | +} | |
218 | + | |
219 | +static void free_ec_sci(void) | |
220 | +{ | |
221 | + gpio_free(OLPC_GPIO_ECSCI); | |
222 | +} | |
223 | + | |
107 | 224 | static int __devinit setup_power_button(struct platform_device *pdev) |
108 | 225 | { |
109 | 226 | int r; |
... | ... | @@ -135,6 +252,37 @@ |
135 | 252 | input_free_device(power_button_idev); |
136 | 253 | } |
137 | 254 | |
255 | +static int __devinit setup_ebook_switch(struct platform_device *pdev) | |
256 | +{ | |
257 | + int r; | |
258 | + | |
259 | + ebook_switch_idev = input_allocate_device(); | |
260 | + if (!ebook_switch_idev) | |
261 | + return -ENOMEM; | |
262 | + | |
263 | + ebook_switch_idev->name = "EBook Switch"; | |
264 | + ebook_switch_idev->phys = DRV_NAME "/input1"; | |
265 | + set_bit(EV_SW, ebook_switch_idev->evbit); | |
266 | + set_bit(SW_TABLET_MODE, ebook_switch_idev->swbit); | |
267 | + | |
268 | + ebook_switch_idev->dev.parent = &pdev->dev; | |
269 | + device_set_wakeup_capable(&ebook_switch_idev->dev, true); | |
270 | + | |
271 | + r = input_register_device(ebook_switch_idev); | |
272 | + if (r) { | |
273 | + dev_err(&pdev->dev, "failed to register ebook switch: %d\n", r); | |
274 | + input_free_device(ebook_switch_idev); | |
275 | + } | |
276 | + | |
277 | + return r; | |
278 | +} | |
279 | + | |
280 | +static void free_ebook_switch(void) | |
281 | +{ | |
282 | + input_unregister_device(ebook_switch_idev); | |
283 | + input_free_device(ebook_switch_idev); | |
284 | +} | |
285 | + | |
138 | 286 | static int __devinit xo1_sci_probe(struct platform_device *pdev) |
139 | 287 | { |
140 | 288 | struct resource *res; |
141 | 289 | |
142 | 290 | |
143 | 291 | |
144 | 292 | |
... | ... | @@ -159,17 +307,49 @@ |
159 | 307 | if (r) |
160 | 308 | return r; |
161 | 309 | |
310 | + r = setup_ebook_switch(pdev); | |
311 | + if (r) | |
312 | + goto err_ebook; | |
313 | + | |
314 | + r = setup_ec_sci(); | |
315 | + if (r) | |
316 | + goto err_ecsci; | |
317 | + | |
318 | + /* Enable PME generation for EC-generated events */ | |
319 | + outl(CS5536_GPIOM7_PME_EN, acpi_base + CS5536_PM_GPE0_EN); | |
320 | + | |
321 | + /* Clear pending events */ | |
322 | + outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); | |
323 | + process_sci_queue(false); | |
324 | + | |
325 | + /* Initial sync */ | |
326 | + send_ebook_state(); | |
327 | + | |
162 | 328 | r = setup_sci_interrupt(pdev); |
163 | 329 | if (r) |
164 | - free_power_button(); | |
330 | + goto err_sci; | |
165 | 331 | |
332 | + /* Enable all EC events */ | |
333 | + olpc_ec_mask_write(EC_SCI_SRC_ALL); | |
334 | + | |
166 | 335 | return r; |
336 | + | |
337 | +err_sci: | |
338 | + free_ec_sci(); | |
339 | +err_ecsci: | |
340 | + free_ebook_switch(); | |
341 | +err_ebook: | |
342 | + free_power_button(); | |
343 | + return r; | |
167 | 344 | } |
168 | 345 | |
169 | 346 | static int __devexit xo1_sci_remove(struct platform_device *pdev) |
170 | 347 | { |
171 | 348 | mfd_cell_disable(pdev); |
172 | 349 | free_irq(sci_irq, pdev); |
350 | + cancel_work_sync(&sci_work); | |
351 | + free_ec_sci(); | |
352 | + free_ebook_switch(); | |
173 | 353 | free_power_button(); |
174 | 354 | acpi_base = 0; |
175 | 355 | return 0; |
... | ... | @@ -182,6 +362,7 @@ |
182 | 362 | .probe = xo1_sci_probe, |
183 | 363 | .remove = __devexit_p(xo1_sci_remove), |
184 | 364 | .suspend = xo1_sci_suspend, |
365 | + .resume = xo1_sci_resume, | |
185 | 366 | }; |
186 | 367 | |
187 | 368 | static int __init xo1_sci_init(void) |
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 | |
... | ... | @@ -43,6 +45,18 @@ |
43 | 45 | #define MSR_GX_GLD_MSR_CONFIG 0xC0002001 |
44 | 46 | #define MSR_GX_MSR_PADSEL 0xC0002011 |
45 | 47 | |
48 | +static inline int cs5535_pic_unreqz_select_high(unsigned int group, | |
49 | + unsigned int irq) | |
50 | +{ | |
51 | + uint32_t lo, hi; | |
52 | + | |
53 | + rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi); | |
54 | + lo &= ~(0xF << (group * 4)); | |
55 | + lo |= (irq & 0xF) << (group * 4); | |
56 | + wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi); | |
57 | + return 0; | |
58 | +} | |
59 | + | |
46 | 60 | /* PIC registers */ |
47 | 61 | #define CS5536_PIC_INT_SEL1 0x4d0 |
48 | 62 | #define CS5536_PIC_INT_SEL2 0x4d1 |
... | ... | @@ -73,6 +87,7 @@ |
73 | 87 | #define CS5536_PM1_EN 0x02 |
74 | 88 | #define CS5536_PM1_CNT 0x08 |
75 | 89 | #define CS5536_PM_GPE0_STS 0x18 |
90 | +#define CS5536_PM_GPE0_EN 0x1c | |
76 | 91 | |
77 | 92 | /* CS5536_PM1_STS bits */ |
78 | 93 | #define CS5536_WAK_FLAG (1 << 15) |
... | ... | @@ -80,6 +95,13 @@ |
80 | 95 | |
81 | 96 | /* CS5536_PM1_EN bits */ |
82 | 97 | #define CS5536_PM_PWRBTN (1 << 8) |
98 | + | |
99 | +/* CS5536_PM_GPE0_STS bits */ | |
100 | +#define CS5536_GPIOM7_PME_FLAG (1 << 31) | |
101 | +#define CS5536_GPIOM6_PME_FLAG (1 << 30) | |
102 | + | |
103 | +/* CS5536_PM_GPE0_EN bits */ | |
104 | +#define CS5536_GPIOM7_PME_EN (1 << 31) | |
83 | 105 | |
84 | 106 | /* VSA2 magic values */ |
85 | 107 | #define VSA_VRC_INDEX 0xAC1C |