diff options
author | Daniel Drake <dsd@laptop.org> | 2011-06-25 12:34:15 -0400 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-07-06 17:44:39 -0400 |
commit | 2cf2baea103f0a3d68b0f989d28df66f16dbc834 (patch) | |
tree | 2d9e8f2a57e5ff11a0f95ddeb8b1134a8136a835 | |
parent | 7bc74b3df73776fe06f3df9fafd2d2698e6ca28a (diff) |
x86, olpc-xo1-sci: Add lid switch functionality
Configure the XO-1's lid switch GPIO to trigger an SCI interrupt,
and correctly expose this input device which can be used as a wakeup
source.
Signed-off-by: Daniel Drake <dsd@laptop.org>
Link: http://lkml.kernel.org/r/1309019658-1712-9-git-send-email-dsd@laptop.org
Acked-by: Andres Salomon <dilinger@queued.net>
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
-rw-r--r-- | arch/x86/Kconfig | 1 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-sci.c | 207 | ||||
-rw-r--r-- | include/linux/cs5535.h | 1 |
3 files changed, 208 insertions, 1 deletions
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 88889106ac9b..350ccbb4d650 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig | |||
@@ -2090,6 +2090,7 @@ config OLPC_XO1_SCI | |||
2090 | - EC-driven system wakeups | 2090 | - EC-driven system wakeups |
2091 | - Power button | 2091 | - Power button |
2092 | - Ebook switch | 2092 | - Ebook switch |
2093 | - Lid switch | ||
2093 | 2094 | ||
2094 | endif # X86_32 | 2095 | endif # X86_32 |
2095 | 2096 | ||
diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c index 63f50506078b..ad0670bca833 100644 --- a/arch/x86/platform/olpc/olpc-xo1-sci.c +++ b/arch/x86/platform/olpc/olpc-xo1-sci.c | |||
@@ -32,9 +32,26 @@ | |||
32 | static unsigned long acpi_base; | 32 | static unsigned long acpi_base; |
33 | static struct input_dev *power_button_idev; | 33 | static struct input_dev *power_button_idev; |
34 | static struct input_dev *ebook_switch_idev; | 34 | static struct input_dev *ebook_switch_idev; |
35 | static struct input_dev *lid_switch_idev; | ||
35 | 36 | ||
36 | static int sci_irq; | 37 | static int sci_irq; |
37 | 38 | ||
39 | static bool lid_open; | ||
40 | static bool lid_inverted; | ||
41 | static int lid_wake_mode; | ||
42 | |||
43 | enum lid_wake_modes { | ||
44 | LID_WAKE_ALWAYS, | ||
45 | LID_WAKE_OPEN, | ||
46 | LID_WAKE_CLOSE, | ||
47 | }; | ||
48 | |||
49 | static const char * const lid_wake_mode_names[] = { | ||
50 | [LID_WAKE_ALWAYS] = "always", | ||
51 | [LID_WAKE_OPEN] = "open", | ||
52 | [LID_WAKE_CLOSE] = "close", | ||
53 | }; | ||
54 | |||
38 | /* Report current ebook switch state through input layer */ | 55 | /* Report current ebook switch state through input layer */ |
39 | static void send_ebook_state(void) | 56 | static void send_ebook_state(void) |
40 | { | 57 | { |
@@ -49,6 +66,70 @@ static void send_ebook_state(void) | |||
49 | input_sync(ebook_switch_idev); | 66 | input_sync(ebook_switch_idev); |
50 | } | 67 | } |
51 | 68 | ||
69 | static void flip_lid_inverter(void) | ||
70 | { | ||
71 | /* gpio is high; invert so we'll get l->h event interrupt */ | ||
72 | if (lid_inverted) | ||
73 | cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); | ||
74 | else | ||
75 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_INVERT); | ||
76 | lid_inverted = !lid_inverted; | ||
77 | } | ||
78 | |||
79 | static void detect_lid_state(void) | ||
80 | { | ||
81 | /* | ||
82 | * the edge detector hookup on the gpio inputs on the geode is | ||
83 | * odd, to say the least. See http://dev.laptop.org/ticket/5703 | ||
84 | * for details, but in a nutshell: we don't use the edge | ||
85 | * detectors. instead, we make use of an anomoly: with the both | ||
86 | * edge detectors turned off, we still get an edge event on a | ||
87 | * positive edge transition. to take advantage of this, we use the | ||
88 | * front-end inverter to ensure that that's the edge we're always | ||
89 | * going to see next. | ||
90 | */ | ||
91 | |||
92 | int state; | ||
93 | |||
94 | state = cs5535_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK); | ||
95 | lid_open = !state ^ !lid_inverted; /* x ^^ y */ | ||
96 | if (!state) | ||
97 | return; | ||
98 | |||
99 | flip_lid_inverter(); | ||
100 | } | ||
101 | |||
102 | /* Report current lid switch state through input layer */ | ||
103 | static void send_lid_state(void) | ||
104 | { | ||
105 | input_report_switch(lid_switch_idev, SW_LID, !lid_open); | ||
106 | input_sync(lid_switch_idev); | ||
107 | } | ||
108 | |||
109 | static ssize_t lid_wake_mode_show(struct device *dev, | ||
110 | struct device_attribute *attr, char *buf) | ||
111 | { | ||
112 | const char *mode = lid_wake_mode_names[lid_wake_mode]; | ||
113 | return sprintf(buf, "%s\n", mode); | ||
114 | } | ||
115 | static ssize_t lid_wake_mode_set(struct device *dev, | ||
116 | struct device_attribute *attr, | ||
117 | const char *buf, size_t count) | ||
118 | { | ||
119 | int i; | ||
120 | for (i = 0; i < ARRAY_SIZE(lid_wake_mode_names); i++) { | ||
121 | const char *mode = lid_wake_mode_names[i]; | ||
122 | if (strlen(mode) != count || strncasecmp(mode, buf, count)) | ||
123 | continue; | ||
124 | |||
125 | lid_wake_mode = i; | ||
126 | return count; | ||
127 | } | ||
128 | return -EINVAL; | ||
129 | } | ||
130 | static DEVICE_ATTR(lid_wake_mode, S_IWUSR | S_IRUGO, lid_wake_mode_show, | ||
131 | lid_wake_mode_set); | ||
132 | |||
52 | /* | 133 | /* |
53 | * Process all items in the EC's SCI queue. | 134 | * Process all items in the EC's SCI queue. |
54 | * | 135 | * |
@@ -111,6 +192,11 @@ static irqreturn_t xo1_sci_intr(int irq, void *dev_id) | |||
111 | schedule_work(&sci_work); | 192 | schedule_work(&sci_work); |
112 | } | 193 | } |
113 | 194 | ||
195 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); | ||
196 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); | ||
197 | detect_lid_state(); | ||
198 | send_lid_state(); | ||
199 | |||
114 | return IRQ_HANDLED; | 200 | return IRQ_HANDLED; |
115 | } | 201 | } |
116 | 202 | ||
@@ -126,11 +212,32 @@ static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state) | |||
126 | else | 212 | else |
127 | olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); | 213 | olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); |
128 | 214 | ||
215 | if (!device_may_wakeup(&lid_switch_idev->dev)) { | ||
216 | cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | ||
217 | } else if ((lid_open && lid_wake_mode == LID_WAKE_OPEN) || | ||
218 | (!lid_open && lid_wake_mode == LID_WAKE_CLOSE)) { | ||
219 | flip_lid_inverter(); | ||
220 | |||
221 | /* we may have just caused an event */ | ||
222 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); | ||
223 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); | ||
224 | |||
225 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | ||
226 | } | ||
227 | |||
129 | return 0; | 228 | return 0; |
130 | } | 229 | } |
131 | 230 | ||
132 | static int xo1_sci_resume(struct platform_device *pdev) | 231 | static int xo1_sci_resume(struct platform_device *pdev) |
133 | { | 232 | { |
233 | /* | ||
234 | * We don't know what may have happened while we were asleep. | ||
235 | * Reestablish our lid setup so we're sure to catch all transitions. | ||
236 | */ | ||
237 | detect_lid_state(); | ||
238 | send_lid_state(); | ||
239 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | ||
240 | |||
134 | /* Enable all EC events */ | 241 | /* Enable all EC events */ |
135 | olpc_ec_mask_write(EC_SCI_SRC_ALL); | 242 | olpc_ec_mask_write(EC_SCI_SRC_ALL); |
136 | return 0; | 243 | return 0; |
@@ -221,6 +328,43 @@ static void free_ec_sci(void) | |||
221 | gpio_free(OLPC_GPIO_ECSCI); | 328 | gpio_free(OLPC_GPIO_ECSCI); |
222 | } | 329 | } |
223 | 330 | ||
331 | static int __devinit setup_lid_events(void) | ||
332 | { | ||
333 | int r; | ||
334 | |||
335 | r = gpio_request(OLPC_GPIO_LID, "OLPC-LID"); | ||
336 | if (r) | ||
337 | return r; | ||
338 | |||
339 | gpio_direction_input(OLPC_GPIO_LID); | ||
340 | |||
341 | cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); | ||
342 | lid_inverted = 0; | ||
343 | |||
344 | /* Clear edge detection and event enable for now */ | ||
345 | cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | ||
346 | cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN); | ||
347 | cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN); | ||
348 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); | ||
349 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); | ||
350 | |||
351 | /* Set the LID to cause an PME event on group 6 */ | ||
352 | cs5535_gpio_setup_event(OLPC_GPIO_LID, 6, 1); | ||
353 | |||
354 | /* Set PME group 6 to fire the SCI interrupt */ | ||
355 | cs5535_gpio_set_irq(6, sci_irq); | ||
356 | |||
357 | /* Enable the event */ | ||
358 | cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); | ||
359 | |||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static void free_lid_events(void) | ||
364 | { | ||
365 | gpio_free(OLPC_GPIO_LID); | ||
366 | } | ||
367 | |||
224 | static int __devinit setup_power_button(struct platform_device *pdev) | 368 | static int __devinit setup_power_button(struct platform_device *pdev) |
225 | { | 369 | { |
226 | int r; | 370 | int r; |
@@ -283,6 +427,50 @@ static void free_ebook_switch(void) | |||
283 | input_free_device(ebook_switch_idev); | 427 | input_free_device(ebook_switch_idev); |
284 | } | 428 | } |
285 | 429 | ||
430 | static int __devinit setup_lid_switch(struct platform_device *pdev) | ||
431 | { | ||
432 | int r; | ||
433 | |||
434 | lid_switch_idev = input_allocate_device(); | ||
435 | if (!lid_switch_idev) | ||
436 | return -ENOMEM; | ||
437 | |||
438 | lid_switch_idev->name = "Lid Switch"; | ||
439 | lid_switch_idev->phys = DRV_NAME "/input2"; | ||
440 | set_bit(EV_SW, lid_switch_idev->evbit); | ||
441 | set_bit(SW_LID, lid_switch_idev->swbit); | ||
442 | |||
443 | lid_switch_idev->dev.parent = &pdev->dev; | ||
444 | device_set_wakeup_capable(&lid_switch_idev->dev, true); | ||
445 | |||
446 | r = input_register_device(lid_switch_idev); | ||
447 | if (r) { | ||
448 | dev_err(&pdev->dev, "failed to register lid switch: %d\n", r); | ||
449 | goto err_register; | ||
450 | } | ||
451 | |||
452 | r = device_create_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); | ||
453 | if (r) { | ||
454 | dev_err(&pdev->dev, "failed to create wake mode attr: %d\n", r); | ||
455 | goto err_create_attr; | ||
456 | } | ||
457 | |||
458 | return 0; | ||
459 | |||
460 | err_create_attr: | ||
461 | input_unregister_device(lid_switch_idev); | ||
462 | err_register: | ||
463 | input_free_device(lid_switch_idev); | ||
464 | return r; | ||
465 | } | ||
466 | |||
467 | static void free_lid_switch(void) | ||
468 | { | ||
469 | device_remove_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); | ||
470 | input_unregister_device(lid_switch_idev); | ||
471 | input_free_device(lid_switch_idev); | ||
472 | } | ||
473 | |||
286 | static int __devinit xo1_sci_probe(struct platform_device *pdev) | 474 | static int __devinit xo1_sci_probe(struct platform_device *pdev) |
287 | { | 475 | { |
288 | struct resource *res; | 476 | struct resource *res; |
@@ -311,12 +499,21 @@ static int __devinit xo1_sci_probe(struct platform_device *pdev) | |||
311 | if (r) | 499 | if (r) |
312 | goto err_ebook; | 500 | goto err_ebook; |
313 | 501 | ||
502 | r = setup_lid_switch(pdev); | ||
503 | if (r) | ||
504 | goto err_lid; | ||
505 | |||
506 | r = setup_lid_events(); | ||
507 | if (r) | ||
508 | goto err_lidevt; | ||
509 | |||
314 | r = setup_ec_sci(); | 510 | r = setup_ec_sci(); |
315 | if (r) | 511 | if (r) |
316 | goto err_ecsci; | 512 | goto err_ecsci; |
317 | 513 | ||
318 | /* Enable PME generation for EC-generated events */ | 514 | /* Enable PME generation for EC-generated events */ |
319 | outl(CS5536_GPIOM7_PME_EN, acpi_base + CS5536_PM_GPE0_EN); | 515 | outl(CS5536_GPIOM6_PME_EN | CS5536_GPIOM7_PME_EN, |
516 | acpi_base + CS5536_PM_GPE0_EN); | ||
320 | 517 | ||
321 | /* Clear pending events */ | 518 | /* Clear pending events */ |
322 | outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); | 519 | outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); |
@@ -324,6 +521,8 @@ static int __devinit xo1_sci_probe(struct platform_device *pdev) | |||
324 | 521 | ||
325 | /* Initial sync */ | 522 | /* Initial sync */ |
326 | send_ebook_state(); | 523 | send_ebook_state(); |
524 | detect_lid_state(); | ||
525 | send_lid_state(); | ||
327 | 526 | ||
328 | r = setup_sci_interrupt(pdev); | 527 | r = setup_sci_interrupt(pdev); |
329 | if (r) | 528 | if (r) |
@@ -337,6 +536,10 @@ static int __devinit xo1_sci_probe(struct platform_device *pdev) | |||
337 | err_sci: | 536 | err_sci: |
338 | free_ec_sci(); | 537 | free_ec_sci(); |
339 | err_ecsci: | 538 | err_ecsci: |
539 | free_lid_events(); | ||
540 | err_lidevt: | ||
541 | free_lid_switch(); | ||
542 | err_lid: | ||
340 | free_ebook_switch(); | 543 | free_ebook_switch(); |
341 | err_ebook: | 544 | err_ebook: |
342 | free_power_button(); | 545 | free_power_button(); |
@@ -349,6 +552,8 @@ static int __devexit xo1_sci_remove(struct platform_device *pdev) | |||
349 | free_irq(sci_irq, pdev); | 552 | free_irq(sci_irq, pdev); |
350 | cancel_work_sync(&sci_work); | 553 | cancel_work_sync(&sci_work); |
351 | free_ec_sci(); | 554 | free_ec_sci(); |
555 | free_lid_events(); | ||
556 | free_lid_switch(); | ||
352 | free_ebook_switch(); | 557 | free_ebook_switch(); |
353 | free_power_button(); | 558 | free_power_button(); |
354 | acpi_base = 0; | 559 | acpi_base = 0; |
diff --git a/include/linux/cs5535.h b/include/linux/cs5535.h index d7e9a7f6ddb0..72954c6bf5d6 100644 --- a/include/linux/cs5535.h +++ b/include/linux/cs5535.h | |||
@@ -102,6 +102,7 @@ static inline int cs5535_pic_unreqz_select_high(unsigned int group, | |||
102 | 102 | ||
103 | /* CS5536_PM_GPE0_EN bits */ | 103 | /* CS5536_PM_GPE0_EN bits */ |
104 | #define CS5536_GPIOM7_PME_EN (1 << 31) | 104 | #define CS5536_GPIOM7_PME_EN (1 << 31) |
105 | #define CS5536_GPIOM6_PME_EN (1 << 30) | ||
105 | 106 | ||
106 | /* VSA2 magic values */ | 107 | /* VSA2 magic values */ |
107 | #define VSA_VRC_INDEX 0xAC1C | 108 | #define VSA_VRC_INDEX 0xAC1C |