Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Daniel Drake and committed by
H. Peter Anvin
2cf2baea 7bc74b3d

+208 -1
+1
arch/x86/Kconfig
··· 2090 2090 - EC-driven system wakeups 2091 2091 - Power button 2092 2092 - Ebook switch 2093 + - Lid switch 2093 2094 2094 2095 endif # X86_32 2095 2096
+206 -1
arch/x86/platform/olpc/olpc-xo1-sci.c
··· 32 32 static unsigned long acpi_base; 33 33 static struct input_dev *power_button_idev; 34 34 static struct input_dev *ebook_switch_idev; 35 + static struct input_dev *lid_switch_idev; 35 36 36 37 static int sci_irq; 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 + }; 37 54 38 55 /* Report current ebook switch state through input layer */ 39 56 static void send_ebook_state(void) ··· 65 48 input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state); 66 49 input_sync(ebook_switch_idev); 67 50 } 51 + 52 + static void flip_lid_inverter(void) 53 + { 54 + /* gpio is high; invert so we'll get l->h event interrupt */ 55 + if (lid_inverted) 56 + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); 57 + else 58 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_INVERT); 59 + lid_inverted = !lid_inverted; 60 + } 61 + 62 + static void detect_lid_state(void) 63 + { 64 + /* 65 + * the edge detector hookup on the gpio inputs on the geode is 66 + * odd, to say the least. See http://dev.laptop.org/ticket/5703 67 + * for details, but in a nutshell: we don't use the edge 68 + * detectors. instead, we make use of an anomoly: with the both 69 + * edge detectors turned off, we still get an edge event on a 70 + * positive edge transition. to take advantage of this, we use the 71 + * front-end inverter to ensure that that's the edge we're always 72 + * going to see next. 73 + */ 74 + 75 + int state; 76 + 77 + state = cs5535_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK); 78 + lid_open = !state ^ !lid_inverted; /* x ^^ y */ 79 + if (!state) 80 + return; 81 + 82 + flip_lid_inverter(); 83 + } 84 + 85 + /* Report current lid switch state through input layer */ 86 + static void send_lid_state(void) 87 + { 88 + input_report_switch(lid_switch_idev, SW_LID, !lid_open); 89 + input_sync(lid_switch_idev); 90 + } 91 + 92 + static ssize_t lid_wake_mode_show(struct device *dev, 93 + struct device_attribute *attr, char *buf) 94 + { 95 + const char *mode = lid_wake_mode_names[lid_wake_mode]; 96 + return sprintf(buf, "%s\n", mode); 97 + } 98 + static ssize_t lid_wake_mode_set(struct device *dev, 99 + struct device_attribute *attr, 100 + const char *buf, size_t count) 101 + { 102 + int i; 103 + for (i = 0; i < ARRAY_SIZE(lid_wake_mode_names); i++) { 104 + const char *mode = lid_wake_mode_names[i]; 105 + if (strlen(mode) != count || strncasecmp(mode, buf, count)) 106 + continue; 107 + 108 + lid_wake_mode = i; 109 + return count; 110 + } 111 + return -EINVAL; 112 + } 113 + static DEVICE_ATTR(lid_wake_mode, S_IWUSR | S_IRUGO, lid_wake_mode_show, 114 + lid_wake_mode_set); 68 115 69 116 /* 70 117 * Process all items in the EC's SCI queue. ··· 192 111 schedule_work(&sci_work); 193 112 } 194 113 114 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); 115 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); 116 + detect_lid_state(); 117 + send_lid_state(); 118 + 195 119 return IRQ_HANDLED; 196 120 } 197 121 ··· 212 126 else 213 127 olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); 214 128 129 + if (!device_may_wakeup(&lid_switch_idev->dev)) { 130 + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); 131 + } else if ((lid_open && lid_wake_mode == LID_WAKE_OPEN) || 132 + (!lid_open && lid_wake_mode == LID_WAKE_CLOSE)) { 133 + flip_lid_inverter(); 134 + 135 + /* we may have just caused an event */ 136 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); 137 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); 138 + 139 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); 140 + } 141 + 215 142 return 0; 216 143 } 217 144 218 145 static int xo1_sci_resume(struct platform_device *pdev) 219 146 { 147 + /* 148 + * We don't know what may have happened while we were asleep. 149 + * Reestablish our lid setup so we're sure to catch all transitions. 150 + */ 151 + detect_lid_state(); 152 + send_lid_state(); 153 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); 154 + 220 155 /* Enable all EC events */ 221 156 olpc_ec_mask_write(EC_SCI_SRC_ALL); 222 157 return 0; ··· 328 221 gpio_free(OLPC_GPIO_ECSCI); 329 222 } 330 223 224 + static int __devinit setup_lid_events(void) 225 + { 226 + int r; 227 + 228 + r = gpio_request(OLPC_GPIO_LID, "OLPC-LID"); 229 + if (r) 230 + return r; 231 + 232 + gpio_direction_input(OLPC_GPIO_LID); 233 + 234 + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); 235 + lid_inverted = 0; 236 + 237 + /* Clear edge detection and event enable for now */ 238 + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); 239 + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN); 240 + cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN); 241 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); 242 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); 243 + 244 + /* Set the LID to cause an PME event on group 6 */ 245 + cs5535_gpio_setup_event(OLPC_GPIO_LID, 6, 1); 246 + 247 + /* Set PME group 6 to fire the SCI interrupt */ 248 + cs5535_gpio_set_irq(6, sci_irq); 249 + 250 + /* Enable the event */ 251 + cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); 252 + 253 + return 0; 254 + } 255 + 256 + static void free_lid_events(void) 257 + { 258 + gpio_free(OLPC_GPIO_LID); 259 + } 260 + 331 261 static int __devinit setup_power_button(struct platform_device *pdev) 332 262 { 333 263 int r; ··· 427 283 input_free_device(ebook_switch_idev); 428 284 } 429 285 286 + static int __devinit setup_lid_switch(struct platform_device *pdev) 287 + { 288 + int r; 289 + 290 + lid_switch_idev = input_allocate_device(); 291 + if (!lid_switch_idev) 292 + return -ENOMEM; 293 + 294 + lid_switch_idev->name = "Lid Switch"; 295 + lid_switch_idev->phys = DRV_NAME "/input2"; 296 + set_bit(EV_SW, lid_switch_idev->evbit); 297 + set_bit(SW_LID, lid_switch_idev->swbit); 298 + 299 + lid_switch_idev->dev.parent = &pdev->dev; 300 + device_set_wakeup_capable(&lid_switch_idev->dev, true); 301 + 302 + r = input_register_device(lid_switch_idev); 303 + if (r) { 304 + dev_err(&pdev->dev, "failed to register lid switch: %d\n", r); 305 + goto err_register; 306 + } 307 + 308 + r = device_create_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); 309 + if (r) { 310 + dev_err(&pdev->dev, "failed to create wake mode attr: %d\n", r); 311 + goto err_create_attr; 312 + } 313 + 314 + return 0; 315 + 316 + err_create_attr: 317 + input_unregister_device(lid_switch_idev); 318 + err_register: 319 + input_free_device(lid_switch_idev); 320 + return r; 321 + } 322 + 323 + static void free_lid_switch(void) 324 + { 325 + device_remove_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); 326 + input_unregister_device(lid_switch_idev); 327 + input_free_device(lid_switch_idev); 328 + } 329 + 430 330 static int __devinit xo1_sci_probe(struct platform_device *pdev) 431 331 { 432 332 struct resource *res; ··· 499 311 if (r) 500 312 goto err_ebook; 501 313 314 + r = setup_lid_switch(pdev); 315 + if (r) 316 + goto err_lid; 317 + 318 + r = setup_lid_events(); 319 + if (r) 320 + goto err_lidevt; 321 + 502 322 r = setup_ec_sci(); 503 323 if (r) 504 324 goto err_ecsci; 505 325 506 326 /* Enable PME generation for EC-generated events */ 507 - outl(CS5536_GPIOM7_PME_EN, acpi_base + CS5536_PM_GPE0_EN); 327 + outl(CS5536_GPIOM6_PME_EN | CS5536_GPIOM7_PME_EN, 328 + acpi_base + CS5536_PM_GPE0_EN); 508 329 509 330 /* Clear pending events */ 510 331 outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); ··· 521 324 522 325 /* Initial sync */ 523 326 send_ebook_state(); 327 + detect_lid_state(); 328 + send_lid_state(); 524 329 525 330 r = setup_sci_interrupt(pdev); 526 331 if (r) ··· 536 337 err_sci: 537 338 free_ec_sci(); 538 339 err_ecsci: 340 + free_lid_events(); 341 + err_lidevt: 342 + free_lid_switch(); 343 + err_lid: 539 344 free_ebook_switch(); 540 345 err_ebook: 541 346 free_power_button(); ··· 552 349 free_irq(sci_irq, pdev); 553 350 cancel_work_sync(&sci_work); 554 351 free_ec_sci(); 352 + free_lid_events(); 353 + free_lid_switch(); 555 354 free_ebook_switch(); 556 355 free_power_button(); 557 356 acpi_base = 0;
+1
include/linux/cs5535.h
··· 102 102 103 103 /* CS5536_PM_GPE0_EN bits */ 104 104 #define CS5536_GPIOM7_PME_EN (1 << 31) 105 + #define CS5536_GPIOM6_PME_EN (1 << 30) 105 106 106 107 /* VSA2 magic values */ 107 108 #define VSA_VRC_INDEX 0xAC1C