|
@@ -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);
|