diff options
author | Andy Ross <andy.ross@windriver.com> | 2011-10-14 05:13:38 -0400 |
---|---|---|
committer | Matthew Garrett <mjg@redhat.com> | 2011-10-24 10:52:41 -0400 |
commit | b23910c2194e0e0ee43e585788085f8e6dd4877e (patch) | |
tree | 5610789264266ee5235e9b03e1b4bc442e4cbaa3 /drivers/platform/x86/asus-laptop.c | |
parent | abec04dbc3dbe7577ccd9d5d6e188aa153d464eb (diff) |
asus-laptop: Pegatron Lucid accelerometer
Support the built-in accelerometer on the Lucid tablets as a standard
3-axis input device.
Signed-off-by: Andy Ross <andy.ross@windriver.com>
Signed-off-by: Corentin Chary <corentin.chary@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
Diffstat (limited to 'drivers/platform/x86/asus-laptop.c')
-rw-r--r-- | drivers/platform/x86/asus-laptop.c | 129 |
1 files changed, 128 insertions, 1 deletions
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); |