aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/asus-laptop.c
diff options
context:
space:
mode:
authorAndy Ross <andy.ross@windriver.com>2011-10-14 05:13:38 -0400
committerMatthew Garrett <mjg@redhat.com>2011-10-24 10:52:41 -0400
commitb23910c2194e0e0ee43e585788085f8e6dd4877e (patch)
tree5610789264266ee5235e9b03e1b4bc442e4cbaa3 /drivers/platform/x86/asus-laptop.c
parentabec04dbc3dbe7577ccd9d5d6e188aa153d464eb (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.c129
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
374static 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
394static 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
425static 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
434static 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
476exit:
477 input_free_polled_device(ipd);
478 return err;
479}
480
361/* Generic LED function */ 481/* Generic LED function */
362static int asus_led_set(struct asus_laptop *asus, const char *method, 482static 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
1688fail_pega_accel:
1689 asus_rfkill_exit(asus);
1564fail_rfkill: 1690fail_rfkill:
1565 asus_led_exit(asus); 1691 asus_led_exit(asus);
1566fail_led: 1692fail_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);