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>
This commit is contained in:
Andy Ross 2011-10-14 11:13:38 +02:00 committed by Matthew Garrett
parent abec04dbc3
commit b23910c219
2 changed files with 133 additions and 5 deletions

View file

@ -69,10 +69,11 @@ config ASUS_LAPTOP
This is a driver for Asus laptops, Lenovo SL and the Pegatron
Lucid tablet. It may also support some MEDION, JVC or VICTOR
laptops. It makes all the extra buttons generate standard
ACPI events and input events. It also adds support for video
output switching, LCD backlight control, Bluetooth and Wlan
control, and most importantly, allows you to blink those
fancy LEDs.
ACPI events and input events, and on the Lucid the built-in
accelerometer appears as an input device. It also adds
support for video output switching, LCD backlight control,
Bluetooth and Wlan control, and most importantly, allows you
to blink those fancy LEDs.
For more information see <http://acpi4asus.sf.net>.

View file

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