diff options
author | Daniel Drake <dsd@laptop.org> | 2011-06-25 12:34:14 -0400 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-07-06 17:44:38 -0400 |
commit | 7bc74b3df73776fe06f3df9fafd2d2698e6ca28a (patch) | |
tree | 166bd6e0b0de947dd278acac03480eb359bff93e /arch/x86/platform | |
parent | bc4ecd5a5efc2435e6debfb7b279a15ae96697fd (diff) |
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>
Diffstat (limited to 'arch/x86/platform')
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-sci.c | 183 |
1 files changed, 182 insertions, 1 deletions
diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c index 8fbf961dae89..63f50506078b 100644 --- a/arch/x86/platform/olpc/olpc-xo1-sci.c +++ b/arch/x86/platform/olpc/olpc-xo1-sci.c | |||
@@ -12,12 +12,15 @@ | |||
12 | */ | 12 | */ |
13 | 13 | ||
14 | #include <linux/cs5535.h> | 14 | #include <linux/cs5535.h> |
15 | #include <linux/device.h> | ||
16 | #include <linux/gpio.h> | ||
15 | #include <linux/input.h> | 17 | #include <linux/input.h> |
16 | #include <linux/interrupt.h> | 18 | #include <linux/interrupt.h> |
17 | #include <linux/platform_device.h> | 19 | #include <linux/platform_device.h> |
18 | #include <linux/pm.h> | 20 | #include <linux/pm.h> |
19 | #include <linux/mfd/core.h> | 21 | #include <linux/mfd/core.h> |
20 | #include <linux/suspend.h> | 22 | #include <linux/suspend.h> |
23 | #include <linux/workqueue.h> | ||
21 | 24 | ||
22 | #include <asm/io.h> | 25 | #include <asm/io.h> |
23 | #include <asm/msr.h> | 26 | #include <asm/msr.h> |
@@ -28,8 +31,60 @@ | |||
28 | 31 | ||
29 | static unsigned long acpi_base; | 32 | static unsigned long acpi_base; |
30 | static struct input_dev *power_button_idev; | 33 | static struct input_dev *power_button_idev; |
34 | static struct input_dev *ebook_switch_idev; | ||
35 | |||
31 | static int sci_irq; | 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 | static irqreturn_t xo1_sci_intr(int irq, void *dev_id) | 88 | static irqreturn_t xo1_sci_intr(int irq, void *dev_id) |
34 | { | 89 | { |
35 | struct platform_device *pdev = dev_id; | 90 | struct platform_device *pdev = dev_id; |
@@ -51,6 +106,11 @@ static irqreturn_t xo1_sci_intr(int irq, void *dev_id) | |||
51 | input_sync(power_button_idev); | 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 | return IRQ_HANDLED; | 114 | return IRQ_HANDLED; |
55 | } | 115 | } |
56 | 116 | ||
@@ -60,6 +120,19 @@ static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state) | |||
60 | olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); | 120 | olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); |
61 | else | 121 | else |
62 | olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN); | 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 | |||
129 | return 0; | ||
130 | } | ||
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); | ||
63 | return 0; | 136 | return 0; |
64 | } | 137 | } |
65 | 138 | ||
@@ -104,6 +177,50 @@ static int __devinit setup_sci_interrupt(struct platform_device *pdev) | |||
104 | return r; | 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 | static int __devinit setup_power_button(struct platform_device *pdev) | 224 | static int __devinit setup_power_button(struct platform_device *pdev) |
108 | { | 225 | { |
109 | int r; | 226 | int r; |
@@ -135,6 +252,37 @@ static void free_power_button(void) | |||
135 | input_free_device(power_button_idev); | 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 | static int __devinit xo1_sci_probe(struct platform_device *pdev) | 286 | static int __devinit xo1_sci_probe(struct platform_device *pdev) |
139 | { | 287 | { |
140 | struct resource *res; | 288 | struct resource *res; |
@@ -159,10 +307,39 @@ static int __devinit xo1_sci_probe(struct platform_device *pdev) | |||
159 | if (r) | 307 | if (r) |
160 | return r; | 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 | r = setup_sci_interrupt(pdev); | 328 | r = setup_sci_interrupt(pdev); |
163 | if (r) | 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 | |||
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(); | ||
166 | return r; | 343 | return r; |
167 | } | 344 | } |
168 | 345 | ||
@@ -170,6 +347,9 @@ static int __devexit xo1_sci_remove(struct platform_device *pdev) | |||
170 | { | 347 | { |
171 | mfd_cell_disable(pdev); | 348 | mfd_cell_disable(pdev); |
172 | free_irq(sci_irq, pdev); | 349 | free_irq(sci_irq, pdev); |
350 | cancel_work_sync(&sci_work); | ||
351 | free_ec_sci(); | ||
352 | free_ebook_switch(); | ||
173 | free_power_button(); | 353 | free_power_button(); |
174 | acpi_base = 0; | 354 | acpi_base = 0; |
175 | return 0; | 355 | return 0; |
@@ -182,6 +362,7 @@ static struct platform_driver xo1_sci_driver = { | |||
182 | .probe = xo1_sci_probe, | 362 | .probe = xo1_sci_probe, |
183 | .remove = __devexit_p(xo1_sci_remove), | 363 | .remove = __devexit_p(xo1_sci_remove), |
184 | .suspend = xo1_sci_suspend, | 364 | .suspend = xo1_sci_suspend, |
365 | .resume = xo1_sci_resume, | ||
185 | }; | 366 | }; |
186 | 367 | ||
187 | static int __init xo1_sci_init(void) | 368 | static int __init xo1_sci_init(void) |