Skip to content

Commit b23910c

Browse files
Andy RossMatthew Garrett
authored andcommitted
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 <[email protected]> Signed-off-by: Corentin Chary <[email protected]> Signed-off-by: Matthew Garrett <[email protected]>
1 parent abec04d commit b23910c

File tree

2 files changed

+133
-5
lines changed

2 files changed

+133
-5
lines changed

drivers/platform/x86/Kconfig

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ config ASUS_LAPTOP
6969
This is a driver for Asus laptops, Lenovo SL and the Pegatron
7070
Lucid tablet. It may also support some MEDION, JVC or VICTOR
7171
laptops. It makes all the extra buttons generate standard
72-
ACPI events and input events. It also adds support for video
73-
output switching, LCD backlight control, Bluetooth and Wlan
74-
control, and most importantly, allows you to blink those
75-
fancy LEDs.
72+
ACPI events and input events, and on the Lucid the built-in
73+
accelerometer appears as an input device. It also adds
74+
support for video output switching, LCD backlight control,
75+
Bluetooth and Wlan control, and most importantly, allows you
76+
to blink those fancy LEDs.
7677

7778
For more information see <http://acpi4asus.sf.net>.
7879

drivers/platform/x86/asus-laptop.c

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ MODULE_PARM_DESC(als_status, "Set the ALS status on boot "
193193
#define PEGA_READ_ALS_H 0x02
194194
#define PEGA_READ_ALS_L 0x03
195195

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+
196204
/*
197205
* Define a specific led structure to keep the main structure clean
198206
*/
@@ -218,6 +226,7 @@ struct asus_laptop {
218226

219227
struct input_dev *inputdev;
220228
struct key_entry *keymap;
229+
struct input_polled_dev *pega_accel_poll;
221230

222231
struct asus_led mled;
223232
struct asus_led tled;
@@ -230,6 +239,10 @@ struct asus_laptop {
230239
int wireless_status;
231240
bool have_rsts;
232241
bool is_pega_lucid;
242+
bool pega_acc_live;
243+
int pega_acc_x;
244+
int pega_acc_y;
245+
int pega_acc_z;
233246

234247
struct rfkill *gps_rfkill;
235248

@@ -358,6 +371,113 @@ static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable)
358371
return write_acpi_int(asus->handle, method, unit);
359372
}
360373

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+
361481
/* Generic LED function */
362482
static int asus_led_set(struct asus_laptop *asus, const char *method,
363483
int value)
@@ -1348,7 +1468,7 @@ static struct platform_driver platform_driver = {
13481468
.driver = {
13491469
.name = ASUS_LAPTOP_FILE,
13501470
.owner = THIS_MODULE,
1351-
}
1471+
},
13521472
};
13531473

13541474
/*
@@ -1558,9 +1678,15 @@ static int __devinit asus_acpi_add(struct acpi_device *device)
15581678
if (result)
15591679
goto fail_rfkill;
15601680

1681+
result = pega_accel_init(asus);
1682+
if (result && result != -ENODEV)
1683+
goto fail_pega_accel;
1684+
15611685
asus_device_present = true;
15621686
return 0;
15631687

1688+
fail_pega_accel:
1689+
asus_rfkill_exit(asus);
15641690
fail_rfkill:
15651691
asus_led_exit(asus);
15661692
fail_led:
@@ -1584,6 +1710,7 @@ static int asus_acpi_remove(struct acpi_device *device, int type)
15841710
asus_rfkill_exit(asus);
15851711
asus_led_exit(asus);
15861712
asus_input_exit(asus);
1713+
pega_accel_exit(asus);
15871714
asus_platform_exit(asus);
15881715

15891716
kfree(asus->name);

0 commit comments

Comments
 (0)