diff options
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 8327d06b6e8..613762d825f 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 " #define PEGA_READ_ALS_H 0x02 #define PEGA_READ_ALS_L 0x03 +#define PEGA_ACCEL_NAME "pega_accel" +#define PEGA_ACCEL_DESC "Pegatron Lucid Tablet Accelerometer" +#define METHOD_XLRX "XLRX" +#define METHOD_XLRY "XLRY" +#define METHOD_XLRZ "XLRZ" +#define PEGA_ACC_CLAMP 512 /* 1G accel is reported as ~256, so clamp to 2G */ +#define PEGA_ACC_RETRIES 3 + /* * Define a specific led structure to keep the main structure clean */ @@ -218,6 +226,7 @@ struct asus_laptop { struct input_dev *inputdev; struct key_entry *keymap; + struct input_polled_dev *pega_accel_poll; struct asus_led mled; struct asus_led tled; @@ -230,6 +239,10 @@ struct asus_laptop { int wireless_status; bool have_rsts; bool is_pega_lucid; + bool pega_acc_live; + int pega_acc_x; + int pega_acc_y; + int pega_acc_z; struct rfkill *gps_rfkill; @@ -358,6 +371,113 @@ static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable) return write_acpi_int(asus->handle, method, unit); } +static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method) +{ + int i, delta; + unsigned long long val; + for (i = 0; i < PEGA_ACC_RETRIES; i++) { + acpi_evaluate_integer(asus->handle, method, NULL, &val); + + /* The output is noisy. From reading the ASL + * dissassembly, timeout errors are returned with 1's + * in the high word, and the lack of locking around + * thei hi/lo byte reads means that a transition + * between (for example) -1 and 0 could be read as + * 0xff00 or 0x00ff. */ + delta = abs(curr - (short)val); + if (delta < 128 && !(val & ~0xffff)) + break; + } + return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP); +} + +static void pega_accel_poll(struct input_polled_dev *ipd) +{ + struct device *parent = ipd->input->dev.parent; + struct asus_laptop *asus = dev_get_drvdata(parent); + + /* In some cases, the very first call to poll causes a + * recursive fault under the polldev worker. This is + * apparently related to very early userspace access to the + * device, and perhaps a firmware bug. Fake the first report. */ + if (!asus->pega_acc_live) { + asus->pega_acc_live = true; + input_report_abs(ipd->input, ABS_X, 0); + input_report_abs(ipd->input, ABS_Y, 0); + input_report_abs(ipd->input, ABS_Z, 0); + input_sync(ipd->input); + return; + } + + asus->pega_acc_x = pega_acc_axis(asus, asus->pega_acc_x, METHOD_XLRX); + asus->pega_acc_y = pega_acc_axis(asus, asus->pega_acc_y, METHOD_XLRY); + asus->pega_acc_z = pega_acc_axis(asus, asus->pega_acc_z, METHOD_XLRZ); + + /* Note transform, convert to "right/up/out" in the native + * landscape orientation (i.e. the vector is the direction of + * "real up" in the device's cartiesian coordinates). */ + input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x); + input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y); + input_report_abs(ipd->input, ABS_Z, asus->pega_acc_z); + input_sync(ipd->input); +} + +static void pega_accel_exit(struct asus_laptop *asus) +{ + if (asus->pega_accel_poll) { + input_unregister_polled_device(asus->pega_accel_poll); + input_free_polled_device(asus->pega_accel_poll); + } + asus->pega_accel_poll = NULL; +} + +static int pega_accel_init(struct asus_laptop *asus) +{ + int err; + struct input_polled_dev *ipd; + + if (!asus->is_pega_lucid) + return -ENODEV; + + if (acpi_check_handle(asus->handle, METHOD_XLRX, NULL) || + acpi_check_handle(asus->handle, METHOD_XLRY, NULL) || + acpi_check_handle(asus->handle, METHOD_XLRZ, NULL)) + return -ENODEV; + + ipd = input_allocate_polled_device(); + if (!ipd) + return -ENOMEM; + + ipd->poll = pega_accel_poll; + ipd->poll_interval = 125; + ipd->poll_interval_min = 50; + ipd->poll_interval_max = 2000; + + ipd->input->name = PEGA_ACCEL_DESC; + ipd->input->phys = PEGA_ACCEL_NAME "/input0"; + ipd->input->dev.parent = &asus->platform_device->dev; + ipd->input->id.bustype = BUS_HOST; + + set_bit(EV_ABS, ipd->input->evbit); + input_set_abs_params(ipd->input, ABS_X, + -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); + input_set_abs_params(ipd->input, ABS_Y, + -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); + input_set_abs_params(ipd->input, ABS_Z, + -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); + + err = input_register_polled_device(ipd); + if (err) + goto exit; + + asus->pega_accel_poll = ipd; + return 0; + +exit: + input_free_polled_device(ipd); + return err; +} + /* Generic LED function */ static int asus_led_set(struct asus_laptop *asus, const char *method, int value) @@ -1348,7 +1468,7 @@ static struct platform_driver platform_driver = { .driver = { .name = ASUS_LAPTOP_FILE, .owner = THIS_MODULE, - } + }, }; /* @@ -1558,9 +1678,15 @@ static int __devinit asus_acpi_add(struct acpi_device *device) if (result) goto fail_rfkill; + result = pega_accel_init(asus); + if (result && result != -ENODEV) + goto fail_pega_accel; + asus_device_present = true; return 0; +fail_pega_accel: + asus_rfkill_exit(asus); fail_rfkill: asus_led_exit(asus); fail_led: @@ -1584,6 +1710,7 @@ static int asus_acpi_remove(struct acpi_device *device, int type) asus_rfkill_exit(asus); asus_led_exit(asus); asus_input_exit(asus); + pega_accel_exit(asus); asus_platform_exit(asus); kfree(asus->name); |