1
Return-Path: <andy.ross@windriver.com>
2
X-Original-To: arjan@linux.intel.com
3
Delivered-To: arjan@linux.intel.com
4
Received: from orsmga001.jf.intel.com (orsmga001.jf.intel.com [10.7.209.18])
5
	by linux.intel.com (Postfix) with ESMTP id 440A56A4482
6
	for <arjan@linux.intel.com>; Thu, 24 Mar 2011 15:16:22 -0700 (PDT)
7
X-ExtLoop1: 1
8
X-IronPort-AV: E=Sophos;i="4.63,239,1299484800"; 
9
   d="scan'208";a="724825425"
10
Received: from aross1x-wrs.jf.intel.com (HELO plausible.org) ([10.7.202.151])
11
  by orsmga001.jf.intel.com with ESMTP; 24 Mar 2011 15:16:22 -0700
12
Received: from localhost.localdomain (unknown [192.102.209.1])
13
	(Authenticated sender: andy-wrs)
14
	by plausible.org (Postfix) with ESMTPSA id 04F9B1194DC;
15
	Thu, 24 Mar 2011 15:16:21 -0700 (PDT)
16
From: Andy Ross <andy.ross@windriver.com>
17
To: Arjan van de Ven <arjan@linux.intel.com>,
18
	Prajwal Karur Mohan <prajwal.karur.mohan@intel.com>
19
Subject: [PATCH 3/4] asus-laptop: Pegatron Lucid accelerometer
20
Date: Thu, 24 Mar 2011 15:16:14 -0700
21
Message-Id: <1301004975-3656-4-git-send-email-andy.ross@windriver.com>
22
X-Mailer: git-send-email 1.7.1
23
In-Reply-To: <1301004975-3656-1-git-send-email-andy.ross@windriver.com>
24
References: <1301004975-3656-1-git-send-email-andy.ross@windriver.com>
25
26
Support the built-in accelerometer on the Lucid tablets as a standard
27
3-axis input device.
28
29
Signed-off-by: Andy Ross <andy.ross@windriver.com>
30
---
31
 drivers/platform/x86/Kconfig       |    9 ++-
32
 drivers/platform/x86/asus-laptop.c |  137 +++++++++++++++++++++++++++++++++++-
33
 2 files changed, 141 insertions(+), 5 deletions(-)
34
35
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
36
index b6f983e..43906f5 100644
37
--- a/drivers/platform/x86/Kconfig
38
+++ b/drivers/platform/x86/Kconfig
39
@@ -67,10 +67,11 @@ config ASUS_LAPTOP
40
 	  This is a driver for Asus laptops and the Pegatron Lucid
41
 	  tablet. It may also support some MEDION, JVC or VICTOR
42
 	  laptops. It makes all the extra buttons generate standard
43
-	  ACPI events and input events. It also adds support for video
44
-	  output switching, LCD backlight control, Bluetooth and Wlan
45
-	  control, and most importantly, allows you to blink those
46
-	  fancy LEDs.
47
+	  ACPI events and input events, and on the Lucid the built-in
48
+	  accelerometer appears as an input device.  It also adds
49
+	  support for video output switching, LCD backlight control,
50
+	  Bluetooth and Wlan control, and most importantly, allows you
51
+	  to blink those fancy LEDs.
52
 
53
 	  For more information and a userspace daemon for handling the extra
54
 	  buttons see <http://acpi4asus.sf.net>.
55
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
56
index 6651d8c..9b07368 100644
57
--- a/drivers/platform/x86/asus-laptop.c
58
+++ b/drivers/platform/x86/asus-laptop.c
59
@@ -224,6 +224,14 @@ static char *display_get_paths[] = {
60
 #define PEGA_READ_ALS_H	0x02
61
 #define PEGA_READ_ALS_L	0x03
62
 
63
+#define PEGA_ACCEL_NAME "pega_accel"
64
+#define PEGA_ACCEL_DESC "Pegatron Lucid Tablet Accelerometer"
65
+#define METHOD_XLRX "XLRX"
66
+#define METHOD_XLRY "XLRY"
67
+#define METHOD_XLRZ "XLRZ"
68
+#define PEGA_ACC_CLAMP 512 /* 1G accel is reported as ~256, so clamp to 2G */
69
+#define PEGA_ACC_RETRIES 3
70
+
71
 /*
72
  * Define a specific led structure to keep the main structure clean
73
  */
74
@@ -249,6 +257,7 @@ struct asus_laptop {
75
 
76
 	struct input_dev *inputdev;
77
 	struct key_entry *keymap;
78
+	struct input_polled_dev *pega_accel_poll;
79
 
80
 	struct asus_led mled;
81
 	struct asus_led tled;
82
@@ -262,6 +271,10 @@ struct asus_laptop {
83
 	bool have_rsts;
84
 	bool have_pega_lucid;
85
 	int lcd_state;
86
+	bool pega_acc_live;
87
+	int pega_acc_x;
88
+	int pega_acc_y;
89
+	int pega_acc_z;
90
 
91
 	struct rfkill *gps_rfkill;
92
 
93
@@ -390,6 +403,99 @@ static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable)
94
 	return write_acpi_int(asus->handle, method, unit);
95
 }
96
 
97
+static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method)
98
+{
99
+	int i, delta;
100
+	unsigned long long val;
101
+	for (i = 0; i < PEGA_ACC_RETRIES; i++) {
102
+		acpi_evaluate_integer(asus->handle, method, NULL, &val);
103
+
104
+		/* The output is noisy.  From reading the ASL
105
+		 * dissassembly, timeout errors are returned with 1's
106
+		 * in the high word, and the lack of locking around
107
+		 * thei hi/lo byte reads means that a transition
108
+		 * between (for example) -1 and 0 could be read as
109
+		 * 0xff00 or 0x00ff. */
110
+		delta = abs(curr - (short)val);
111
+		if (delta < 128 && !(val & ~0xffff))
112
+			break;
113
+	}
114
+	return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP);
115
+}
116
+
117
+static void pega_accel_poll(struct input_polled_dev *ipd)
118
+{
119
+	struct device *parent = ipd->input->dev.parent;
120
+	struct asus_laptop *asus = dev_get_drvdata(parent);
121
+
122
+	/* In some cases, the very first call to poll causes a
123
+	 * recursive fault under the polldev worker.  This is
124
+	 * apparently related to very early userspace access to the
125
+	 * device, and perhaps a firmware bug.  See related comments
126
+	 * in asus_platform_probe regarding the fragility of these
127
+	 * methods early in the boot.  Fake the first report. */
128
+	if (!asus->pega_acc_live) {
129
+		asus->pega_acc_live = true;
130
+		input_report_abs(ipd->input, ABS_X, 0);
131
+		input_report_abs(ipd->input, ABS_Y, 0);
132
+		input_report_abs(ipd->input, ABS_Z, 0);
133
+		input_sync(ipd->input);
134
+		return;
135
+	}
136
+
137
+	asus->pega_acc_x = pega_acc_axis(asus, asus->pega_acc_x, METHOD_XLRX);
138
+	asus->pega_acc_y = pega_acc_axis(asus, asus->pega_acc_y, METHOD_XLRY);
139
+	asus->pega_acc_z = pega_acc_axis(asus, asus->pega_acc_z, METHOD_XLRZ);
140
+
141
+	/* Note transform, convert to "right/up/out" in the native
142
+	 * landscape orientation (i.e. the vector is the direction of
143
+	 * "real up" in the device's cartiesian coordinates). */
144
+	input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x);
145
+	input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y);
146
+	input_report_abs(ipd->input, ABS_Z,  asus->pega_acc_z);
147
+	input_sync(ipd->input);
148
+}
149
+
150
+static void pega_accel_probe(struct asus_laptop *asus)
151
+{
152
+	int err;
153
+	struct input_polled_dev *ipd;
154
+
155
+	if (!asus->have_pega_lucid ||
156
+	    acpi_check_handle(asus->handle, METHOD_XLRX, NULL) ||
157
+	    acpi_check_handle(asus->handle, METHOD_XLRY, NULL) ||
158
+	    acpi_check_handle(asus->handle, METHOD_XLRZ, NULL))
159
+		return;
160
+
161
+	ipd = input_allocate_polled_device();
162
+	if (!ipd)
163
+		return;
164
+
165
+	ipd->poll = pega_accel_poll;
166
+	ipd->poll_interval = 125;
167
+	ipd->poll_interval_min = 50;
168
+	ipd->poll_interval_max = 2000;
169
+
170
+	ipd->input->name = PEGA_ACCEL_DESC;
171
+	ipd->input->phys = PEGA_ACCEL_NAME "/input0";
172
+	ipd->input->dev.parent = &asus->platform_device->dev;
173
+	ipd->input->id.bustype = BUS_HOST;
174
+
175
+	set_bit(EV_ABS, ipd->input->evbit);
176
+	input_set_abs_params(ipd->input, ABS_X,
177
+			     -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
178
+	input_set_abs_params(ipd->input, ABS_Y,
179
+			     -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
180
+	input_set_abs_params(ipd->input, ABS_Z,
181
+			     -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
182
+
183
+	err = input_register_polled_device(ipd);
184
+	if (err)
185
+		input_free_polled_device(ipd);
186
+	else
187
+		asus->pega_accel_poll = ipd;
188
+}
189
+
190
 /* Generic LED function */
191
 static int asus_led_set(struct asus_laptop *asus, const char *method,
192
 			 int value)
193
@@ -1459,11 +1565,40 @@ static void asus_platform_exit(struct asus_laptop *asus)
194
 	platform_device_unregister(asus->platform_device);
195
 }
196
 
197
+static int asus_platform_probe(struct platform_device *pd)
198
+{
199
+	struct asus_laptop *asus = dev_get_drvdata(&pd->dev);
200
+
201
+	/* This is instantiated during platform driver initialization
202
+	 * becuase if it's done from underneath asus_acpi_add(), the
203
+	 * resulting input device can be grabbed by an early userspace
204
+	 * reader before ACPI initialization is finished and something
205
+	 * oopses underneath the acpi_evaluate_integer() call out of
206
+	 * pega_accel_poll().  Firmware bug? */
207
+	pega_accel_probe(asus);
208
+
209
+	return 0;
210
+}
211
+
212
+static int __devexit asus_platform_remove(struct platform_device *pd)
213
+{
214
+	struct asus_laptop *asus = dev_get_drvdata(&pd->dev);
215
+	if (asus->pega_accel_poll) {
216
+		input_unregister_polled_device(asus->pega_accel_poll);
217
+		input_free_polled_device(asus->pega_accel_poll);
218
+	}
219
+	asus->pega_accel_poll = NULL;
220
+	return 0;
221
+}
222
+
223
+
224
 static struct platform_driver platform_driver = {
225
 	.driver = {
226
 		.name = ASUS_LAPTOP_FILE,
227
 		.owner = THIS_MODULE,
228
-	}
229
+	},
230
+	.probe  = asus_platform_probe,
231
+	.remove = asus_platform_remove,
232
 };
233
 
234
 static int asus_handle_init(char *name, acpi_handle * handle,
235
-- 
236
1.7.1