diff options
| -rw-r--r-- | drivers/platform/x86/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/platform/x86/asus-laptop.c | 129 |
2 files changed, 133 insertions, 5 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4e5623e4fa0b..0d4146ca011f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig | |||
| @@ -69,10 +69,11 @@ config ASUS_LAPTOP | |||
| 69 | This is a driver for Asus laptops, Lenovo SL and the Pegatron | 69 | This is a driver for Asus laptops, Lenovo SL and the Pegatron |
| 70 | Lucid tablet. It may also support some MEDION, JVC or VICTOR | 70 | Lucid tablet. It may also support some MEDION, JVC or VICTOR |
| 71 | laptops. It makes all the extra buttons generate standard | 71 | laptops. It makes all the extra buttons generate standard |
| 72 | ACPI events and input events. It also adds support for video | 72 | ACPI events and input events, and on the Lucid the built-in |
| 73 | output switching, LCD backlight control, Bluetooth and Wlan | 73 | accelerometer appears as an input device. It also adds |
| 74 | control, and most importantly, allows you to blink those | 74 | support for video output switching, LCD backlight control, |
| 75 | fancy LEDs. | 75 | Bluetooth and Wlan control, and most importantly, allows you |
| 76 | to blink those fancy LEDs. | ||
| 76 | 77 | ||
| 77 | For more information see <http://acpi4asus.sf.net>. | 78 | For more information see <http://acpi4asus.sf.net>. |
| 78 | 79 | ||
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 8327d06b6e8a..613762d825f9 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c | |||
| @@ -193,6 +193,14 @@ MODULE_PARM_DESC(als_status, "Set the ALS status on boot " | |||
| 193 | #define PEGA_READ_ALS_H 0x02 | 193 | #define PEGA_READ_ALS_H 0x02 |
| 194 | #define PEGA_READ_ALS_L 0x03 | 194 | #define PEGA_READ_ALS_L 0x03 |
| 195 | 195 | ||
| 196 | #define PEGA_ACCEL_NAME "pega_accel" | ||
| 197 | #define PEGA_ACCEL_DESC "Pegatron Lucid Tablet Accelerometer" | ||
| 198 | #define METHOD_XLRX "XLRX" | ||
| 199 | #define METHOD_XLRY "XLRY" | ||
| 200 | #define METHOD_XLRZ "XLRZ" | ||
| 201 | #define PEGA_ACC_CLAMP 512 /* 1G accel is reported as ~256, so clamp to 2G */ | ||
| 202 | #define PEGA_ACC_RETRIES 3 | ||
| 203 | |||
| 196 | /* | 204 | /* |
| 197 | * Define a specific led structure to keep the main structure clean | 205 | * Define a specific led structure to keep the main structure clean |
| 198 | */ | 206 | */ |
| @@ -218,6 +226,7 @@ struct asus_laptop { | |||
| 218 | 226 | ||
| 219 | struct input_dev *inputdev; | 227 | struct input_dev *inputdev; |
| 220 | struct key_entry *keymap; | 228 | struct key_entry *keymap; |
| 229 | struct input_polled_dev *pega_accel_poll; | ||
| 221 | 230 | ||
| 222 | struct asus_led mled; | 231 | struct asus_led mled; |
| 223 | struct asus_led tled; | 232 | struct asus_led tled; |
| @@ -230,6 +239,10 @@ struct asus_laptop { | |||
| 230 | int wireless_status; | 239 | int wireless_status; |
| 231 | bool have_rsts; | 240 | bool have_rsts; |
| 232 | bool is_pega_lucid; | 241 | bool is_pega_lucid; |
| 242 | bool pega_acc_live; | ||
| 243 | int pega_acc_x; | ||
| 244 | int pega_acc_y; | ||
| 245 | int pega_acc_z; | ||
| 233 | 246 | ||
| 234 | struct rfkill *gps_rfkill; | 247 | struct rfkill *gps_rfkill; |
| 235 | 248 | ||
| @@ -358,6 +371,113 @@ static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable) | |||
| 358 | return write_acpi_int(asus->handle, method, unit); | 371 | return write_acpi_int(asus->handle, method, unit); |
| 359 | } | 372 | } |
| 360 | 373 | ||
| 374 | static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method) | ||
| 375 | { | ||
| 376 | int i, delta; | ||
| 377 | unsigned long long val; | ||
| 378 | for (i = 0; i < PEGA_ACC_RETRIES; i++) { | ||
| 379 | acpi_evaluate_integer(asus->handle, method, NULL, &val); | ||
| 380 | |||
| 381 | /* The output is noisy. From reading the ASL | ||
| 382 | * dissassembly, timeout errors are returned with 1's | ||
| 383 | * in the high word, and the lack of locking around | ||
| 384 | * thei hi/lo byte reads means that a transition | ||
| 385 | * between (for example) -1 and 0 could be read as | ||
| 386 | * 0xff00 or 0x00ff. */ | ||
| 387 | delta = abs(curr - (short)val); | ||
| 388 | if (delta < 128 && !(val & ~0xffff)) | ||
| 389 | break; | ||
| 390 | } | ||
| 391 | return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP); | ||
| 392 | } | ||
| 393 | |||
| 394 | static void pega_accel_poll(struct input_polled_dev *ipd) | ||
| 395 | { | ||
| 396 | struct device *parent = ipd->input->dev.parent; | ||
| 397 | struct asus_laptop *asus = dev_get_drvdata(parent); | ||
| 398 | |||
| 399 | /* In some cases, the very first call to poll causes a | ||
| 400 | * recursive fault under the polldev worker. This is | ||
| 401 | * apparently related to very early userspace access to the | ||
| 402 | * device, and perhaps a firmware bug. Fake the first report. */ | ||
| 403 | if (!asus->pega_acc_live) { | ||
| 404 | asus->pega_acc_live = true; | ||
| 405 | input_report_abs(ipd->input, ABS_X, 0); | ||
| 406 | input_report_abs(ipd->input, ABS_Y, 0); | ||
| 407 | input_report_abs(ipd->input, ABS_Z, 0); | ||
| 408 | input_sync(ipd->input); | ||
| 409 | return; | ||
| 410 | } | ||
| 411 | |||
| 412 | asus->pega_acc_x = pega_acc_axis(asus, asus->pega_acc_x, METHOD_XLRX); | ||
| 413 | asus->pega_acc_y = pega_acc_axis(asus, asus->pega_acc_y, METHOD_XLRY); | ||
| 414 | asus->pega_acc_z = pega_acc_axis(asus, asus->pega_acc_z, METHOD_XLRZ); | ||
| 415 | |||
| 416 | /* Note transform, convert to "right/up/out" in the native | ||
| 417 | * landscape orientation (i.e. the vector is the direction of | ||
| 418 | * "real up" in the device's cartiesian coordinates). */ | ||
| 419 | input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x); | ||
| 420 | input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y); | ||
| 421 | input_report_abs(ipd->input, ABS_Z, asus->pega_acc_z); | ||
| 422 | input_sync(ipd->input); | ||
| 423 | } | ||
| 424 | |||
| 425 | static void pega_accel_exit(struct asus_laptop *asus) | ||
| 426 | { | ||
| 427 | if (asus->pega_accel_poll) { | ||
| 428 | input_unregister_polled_device(asus->pega_accel_poll); | ||
| 429 | input_free_polled_device(asus->pega_accel_poll); | ||
| 430 | } | ||
| 431 | asus->pega_accel_poll = NULL; | ||
| 432 | } | ||
| 433 | |||
| 434 | static int pega_accel_init(struct asus_laptop *asus) | ||
| 435 | { | ||
| 436 | int err; | ||
| 437 | struct input_polled_dev *ipd; | ||
| 438 | |||
| 439 | if (!asus->is_pega_lucid) | ||
| 440 | return -ENODEV; | ||
| 441 | |||
| 442 | if (acpi_check_handle(asus->handle, METHOD_XLRX, NULL) || | ||
| 443 | acpi_check_handle(asus->handle, METHOD_XLRY, NULL) || | ||
| 444 | acpi_check_handle(asus->handle, METHOD_XLRZ, NULL)) | ||
| 445 | return -ENODEV; | ||
| 446 | |||
| 447 | ipd = input_allocate_polled_device(); | ||
| 448 | if (!ipd) | ||
| 449 | return -ENOMEM; | ||
| 450 | |||
| 451 | ipd->poll = pega_accel_poll; | ||
| 452 | ipd->poll_interval = 125; | ||
| 453 | ipd->poll_interval_min = 50; | ||
| 454 | ipd->poll_interval_max = 2000; | ||
| 455 | |||
| 456 | ipd->input->name = PEGA_ACCEL_DESC; | ||
| 457 | ipd->input->phys = PEGA_ACCEL_NAME "/input0"; | ||
| 458 | ipd->input->dev.parent = &asus->platform_device->dev; | ||
| 459 | ipd->input->id.bustype = BUS_HOST; | ||
| 460 | |||
| 461 | set_bit(EV_ABS, ipd->input->evbit); | ||
| 462 | input_set_abs_params(ipd->input, ABS_X, | ||
| 463 | -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); | ||
| 464 | input_set_abs_params(ipd->input, ABS_Y, | ||
| 465 | -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); | ||
| 466 | input_set_abs_params(ipd->input, ABS_Z, | ||
| 467 | -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); | ||
| 468 | |||
| 469 | err = input_register_polled_device(ipd); | ||
| 470 | if (err) | ||
| 471 | goto exit; | ||
| 472 | |||
| 473 | asus->pega_accel_poll = ipd; | ||
| 474 | return 0; | ||
| 475 | |||
| 476 | exit: | ||
| 477 | input_free_polled_device(ipd); | ||
| 478 | return err; | ||
| 479 | } | ||
| 480 | |||
| 361 | /* Generic LED function */ | 481 | /* Generic LED function */ |
| 362 | static int asus_led_set(struct asus_laptop *asus, const char *method, | 482 | static int asus_led_set(struct asus_laptop *asus, const char *method, |
| 363 | int value) | 483 | int value) |
| @@ -1348,7 +1468,7 @@ static struct platform_driver platform_driver = { | |||
| 1348 | .driver = { | 1468 | .driver = { |
| 1349 | .name = ASUS_LAPTOP_FILE, | 1469 | .name = ASUS_LAPTOP_FILE, |
| 1350 | .owner = THIS_MODULE, | 1470 | .owner = THIS_MODULE, |
| 1351 | } | 1471 | }, |
| 1352 | }; | 1472 | }; |
| 1353 | 1473 | ||
| 1354 | /* | 1474 | /* |
| @@ -1558,9 +1678,15 @@ static int __devinit asus_acpi_add(struct acpi_device *device) | |||
| 1558 | if (result) | 1678 | if (result) |
| 1559 | goto fail_rfkill; | 1679 | goto fail_rfkill; |
| 1560 | 1680 | ||
| 1681 | result = pega_accel_init(asus); | ||
| 1682 | if (result && result != -ENODEV) | ||
| 1683 | goto fail_pega_accel; | ||
| 1684 | |||
| 1561 | asus_device_present = true; | 1685 | asus_device_present = true; |
| 1562 | return 0; | 1686 | return 0; |
| 1563 | 1687 | ||
| 1688 | fail_pega_accel: | ||
| 1689 | asus_rfkill_exit(asus); | ||
| 1564 | fail_rfkill: | 1690 | fail_rfkill: |
| 1565 | asus_led_exit(asus); | 1691 | asus_led_exit(asus); |
| 1566 | fail_led: | 1692 | fail_led: |
| @@ -1584,6 +1710,7 @@ static int asus_acpi_remove(struct acpi_device *device, int type) | |||
| 1584 | asus_rfkill_exit(asus); | 1710 | asus_rfkill_exit(asus); |
| 1585 | asus_led_exit(asus); | 1711 | asus_led_exit(asus); |
| 1586 | asus_input_exit(asus); | 1712 | asus_input_exit(asus); |
| 1713 | pega_accel_exit(asus); | ||
| 1587 | asus_platform_exit(asus); | 1714 | asus_platform_exit(asus); |
| 1588 | 1715 | ||
| 1589 | kfree(asus->name); | 1716 | kfree(asus->name); |
