diff options
author | Miguel Gómez <magomez@igalia.com> | 2012-06-29 09:39:48 -0400 |
---|---|---|
committer | Matthew Garrett <mjg@redhat.com> | 2012-07-28 00:28:27 -0400 |
commit | 7125587df4e87224dbd3b90ddf6f23e83044ae30 (patch) | |
tree | dac9ab910929ef0904eb4b16bd5f811b00b1039e /drivers/platform | |
parent | c0b91b6d5226247fa4fe894eb592bcc56bc7e9fd (diff) |
classmate-laptop: Add support for Classmate V4 accelerometer.
Classmate V4 laptop includes a new accelerometer that can't be handled by
previous driver. This patch adds a new driver to handle it.
[mjg: Fixed up the driver pm stuff]
Signed-off-by: Miguel Gómez <magomez@igalia.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/classmate-laptop.c | 403 |
1 files changed, 401 insertions, 2 deletions
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index e2230a2b2f8e..33f4d2c72c03 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c | |||
@@ -31,12 +31,18 @@ MODULE_LICENSE("GPL"); | |||
31 | 31 | ||
32 | struct cmpc_accel { | 32 | struct cmpc_accel { |
33 | int sensitivity; | 33 | int sensitivity; |
34 | int g_select; | ||
35 | int inputdev_state; | ||
34 | }; | 36 | }; |
35 | 37 | ||
36 | #define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 | 38 | #define CMPC_ACCEL_DEV_STATE_CLOSED 0 |
39 | #define CMPC_ACCEL_DEV_STATE_OPEN 1 | ||
37 | 40 | ||
41 | #define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 | ||
42 | #define CMPC_ACCEL_G_SELECT_DEFAULT 0 | ||
38 | 43 | ||
39 | #define CMPC_ACCEL_HID "ACCE0000" | 44 | #define CMPC_ACCEL_HID "ACCE0000" |
45 | #define CMPC_ACCEL_HID_V4 "ACCE0001" | ||
40 | #define CMPC_TABLET_HID "TBLT0000" | 46 | #define CMPC_TABLET_HID "TBLT0000" |
41 | #define CMPC_IPML_HID "IPML200" | 47 | #define CMPC_IPML_HID "IPML200" |
42 | #define CMPC_KEYS_HID "FnBT0000" | 48 | #define CMPC_KEYS_HID "FnBT0000" |
@@ -76,7 +82,391 @@ static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) | |||
76 | } | 82 | } |
77 | 83 | ||
78 | /* | 84 | /* |
79 | * Accelerometer code. | 85 | * Accelerometer code for Classmate V4 |
86 | */ | ||
87 | static acpi_status cmpc_start_accel_v4(acpi_handle handle) | ||
88 | { | ||
89 | union acpi_object param[4]; | ||
90 | struct acpi_object_list input; | ||
91 | acpi_status status; | ||
92 | |||
93 | param[0].type = ACPI_TYPE_INTEGER; | ||
94 | param[0].integer.value = 0x3; | ||
95 | param[1].type = ACPI_TYPE_INTEGER; | ||
96 | param[1].integer.value = 0; | ||
97 | param[2].type = ACPI_TYPE_INTEGER; | ||
98 | param[2].integer.value = 0; | ||
99 | param[3].type = ACPI_TYPE_INTEGER; | ||
100 | param[3].integer.value = 0; | ||
101 | input.count = 4; | ||
102 | input.pointer = param; | ||
103 | status = acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
104 | return status; | ||
105 | } | ||
106 | |||
107 | static acpi_status cmpc_stop_accel_v4(acpi_handle handle) | ||
108 | { | ||
109 | union acpi_object param[4]; | ||
110 | struct acpi_object_list input; | ||
111 | acpi_status status; | ||
112 | |||
113 | param[0].type = ACPI_TYPE_INTEGER; | ||
114 | param[0].integer.value = 0x4; | ||
115 | param[1].type = ACPI_TYPE_INTEGER; | ||
116 | param[1].integer.value = 0; | ||
117 | param[2].type = ACPI_TYPE_INTEGER; | ||
118 | param[2].integer.value = 0; | ||
119 | param[3].type = ACPI_TYPE_INTEGER; | ||
120 | param[3].integer.value = 0; | ||
121 | input.count = 4; | ||
122 | input.pointer = param; | ||
123 | status = acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
124 | return status; | ||
125 | } | ||
126 | |||
127 | static acpi_status cmpc_accel_set_sensitivity_v4(acpi_handle handle, int val) | ||
128 | { | ||
129 | union acpi_object param[4]; | ||
130 | struct acpi_object_list input; | ||
131 | |||
132 | param[0].type = ACPI_TYPE_INTEGER; | ||
133 | param[0].integer.value = 0x02; | ||
134 | param[1].type = ACPI_TYPE_INTEGER; | ||
135 | param[1].integer.value = val; | ||
136 | param[2].type = ACPI_TYPE_INTEGER; | ||
137 | param[2].integer.value = 0; | ||
138 | param[3].type = ACPI_TYPE_INTEGER; | ||
139 | param[3].integer.value = 0; | ||
140 | input.count = 4; | ||
141 | input.pointer = param; | ||
142 | return acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
143 | } | ||
144 | |||
145 | static acpi_status cmpc_accel_set_g_select_v4(acpi_handle handle, int val) | ||
146 | { | ||
147 | union acpi_object param[4]; | ||
148 | struct acpi_object_list input; | ||
149 | |||
150 | param[0].type = ACPI_TYPE_INTEGER; | ||
151 | param[0].integer.value = 0x05; | ||
152 | param[1].type = ACPI_TYPE_INTEGER; | ||
153 | param[1].integer.value = val; | ||
154 | param[2].type = ACPI_TYPE_INTEGER; | ||
155 | param[2].integer.value = 0; | ||
156 | param[3].type = ACPI_TYPE_INTEGER; | ||
157 | param[3].integer.value = 0; | ||
158 | input.count = 4; | ||
159 | input.pointer = param; | ||
160 | return acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
161 | } | ||
162 | |||
163 | static acpi_status cmpc_get_accel_v4(acpi_handle handle, | ||
164 | int16_t *x, | ||
165 | int16_t *y, | ||
166 | int16_t *z) | ||
167 | { | ||
168 | union acpi_object param[4]; | ||
169 | struct acpi_object_list input; | ||
170 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
171 | int16_t *locs; | ||
172 | acpi_status status; | ||
173 | |||
174 | param[0].type = ACPI_TYPE_INTEGER; | ||
175 | param[0].integer.value = 0x01; | ||
176 | param[1].type = ACPI_TYPE_INTEGER; | ||
177 | param[1].integer.value = 0; | ||
178 | param[2].type = ACPI_TYPE_INTEGER; | ||
179 | param[2].integer.value = 0; | ||
180 | param[3].type = ACPI_TYPE_INTEGER; | ||
181 | param[3].integer.value = 0; | ||
182 | input.count = 4; | ||
183 | input.pointer = param; | ||
184 | status = acpi_evaluate_object(handle, "ACMD", &input, &output); | ||
185 | if (ACPI_SUCCESS(status)) { | ||
186 | union acpi_object *obj; | ||
187 | obj = output.pointer; | ||
188 | locs = (int16_t *) obj->buffer.pointer; | ||
189 | *x = locs[0]; | ||
190 | *y = locs[1]; | ||
191 | *z = locs[2]; | ||
192 | kfree(output.pointer); | ||
193 | } | ||
194 | return status; | ||
195 | } | ||
196 | |||
197 | static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event) | ||
198 | { | ||
199 | if (event == 0x81) { | ||
200 | int16_t x, y, z; | ||
201 | acpi_status status; | ||
202 | |||
203 | status = cmpc_get_accel_v4(dev->handle, &x, &y, &z); | ||
204 | if (ACPI_SUCCESS(status)) { | ||
205 | struct input_dev *inputdev = dev_get_drvdata(&dev->dev); | ||
206 | |||
207 | input_report_abs(inputdev, ABS_X, x); | ||
208 | input_report_abs(inputdev, ABS_Y, y); | ||
209 | input_report_abs(inputdev, ABS_Z, z); | ||
210 | input_sync(inputdev); | ||
211 | } | ||
212 | } | ||
213 | } | ||
214 | |||
215 | static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev, | ||
216 | struct device_attribute *attr, | ||
217 | char *buf) | ||
218 | { | ||
219 | struct acpi_device *acpi; | ||
220 | struct input_dev *inputdev; | ||
221 | struct cmpc_accel *accel; | ||
222 | |||
223 | acpi = to_acpi_device(dev); | ||
224 | inputdev = dev_get_drvdata(&acpi->dev); | ||
225 | accel = dev_get_drvdata(&inputdev->dev); | ||
226 | |||
227 | return sprintf(buf, "%d\n", accel->sensitivity); | ||
228 | } | ||
229 | |||
230 | static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev, | ||
231 | struct device_attribute *attr, | ||
232 | const char *buf, size_t count) | ||
233 | { | ||
234 | struct acpi_device *acpi; | ||
235 | struct input_dev *inputdev; | ||
236 | struct cmpc_accel *accel; | ||
237 | unsigned long sensitivity; | ||
238 | int r; | ||
239 | |||
240 | acpi = to_acpi_device(dev); | ||
241 | inputdev = dev_get_drvdata(&acpi->dev); | ||
242 | accel = dev_get_drvdata(&inputdev->dev); | ||
243 | |||
244 | r = kstrtoul(buf, 0, &sensitivity); | ||
245 | if (r) | ||
246 | return r; | ||
247 | |||
248 | /* sensitivity must be between 1 and 127 */ | ||
249 | if (sensitivity < 1 || sensitivity > 127) | ||
250 | return -EINVAL; | ||
251 | |||
252 | accel->sensitivity = sensitivity; | ||
253 | cmpc_accel_set_sensitivity_v4(acpi->handle, sensitivity); | ||
254 | |||
255 | return strnlen(buf, count); | ||
256 | } | ||
257 | |||
258 | static struct device_attribute cmpc_accel_sensitivity_attr_v4 = { | ||
259 | .attr = { .name = "sensitivity", .mode = 0660 }, | ||
260 | .show = cmpc_accel_sensitivity_show_v4, | ||
261 | .store = cmpc_accel_sensitivity_store_v4 | ||
262 | }; | ||
263 | |||
264 | static ssize_t cmpc_accel_g_select_show_v4(struct device *dev, | ||
265 | struct device_attribute *attr, | ||
266 | char *buf) | ||
267 | { | ||
268 | struct acpi_device *acpi; | ||
269 | struct input_dev *inputdev; | ||
270 | struct cmpc_accel *accel; | ||
271 | |||
272 | acpi = to_acpi_device(dev); | ||
273 | inputdev = dev_get_drvdata(&acpi->dev); | ||
274 | accel = dev_get_drvdata(&inputdev->dev); | ||
275 | |||
276 | return sprintf(buf, "%d\n", accel->g_select); | ||
277 | } | ||
278 | |||
279 | static ssize_t cmpc_accel_g_select_store_v4(struct device *dev, | ||
280 | struct device_attribute *attr, | ||
281 | const char *buf, size_t count) | ||
282 | { | ||
283 | struct acpi_device *acpi; | ||
284 | struct input_dev *inputdev; | ||
285 | struct cmpc_accel *accel; | ||
286 | unsigned long g_select; | ||
287 | int r; | ||
288 | |||
289 | acpi = to_acpi_device(dev); | ||
290 | inputdev = dev_get_drvdata(&acpi->dev); | ||
291 | accel = dev_get_drvdata(&inputdev->dev); | ||
292 | |||
293 | r = kstrtoul(buf, 0, &g_select); | ||
294 | if (r) | ||
295 | return r; | ||
296 | |||
297 | /* 0 means 1.5g, 1 means 6g, everything else is wrong */ | ||
298 | if (g_select != 0 && g_select != 1) | ||
299 | return -EINVAL; | ||
300 | |||
301 | accel->g_select = g_select; | ||
302 | cmpc_accel_set_g_select_v4(acpi->handle, g_select); | ||
303 | |||
304 | return strnlen(buf, count); | ||
305 | } | ||
306 | |||
307 | static struct device_attribute cmpc_accel_g_select_attr_v4 = { | ||
308 | .attr = { .name = "g_select", .mode = 0660 }, | ||
309 | .show = cmpc_accel_g_select_show_v4, | ||
310 | .store = cmpc_accel_g_select_store_v4 | ||
311 | }; | ||
312 | |||
313 | static int cmpc_accel_open_v4(struct input_dev *input) | ||
314 | { | ||
315 | struct acpi_device *acpi; | ||
316 | struct cmpc_accel *accel; | ||
317 | |||
318 | acpi = to_acpi_device(input->dev.parent); | ||
319 | accel = dev_get_drvdata(&input->dev); | ||
320 | |||
321 | cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); | ||
322 | cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); | ||
323 | |||
324 | if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) { | ||
325 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN; | ||
326 | return 0; | ||
327 | } | ||
328 | return -EIO; | ||
329 | } | ||
330 | |||
331 | static void cmpc_accel_close_v4(struct input_dev *input) | ||
332 | { | ||
333 | struct acpi_device *acpi; | ||
334 | struct cmpc_accel *accel; | ||
335 | |||
336 | acpi = to_acpi_device(input->dev.parent); | ||
337 | accel = dev_get_drvdata(&input->dev); | ||
338 | |||
339 | cmpc_stop_accel_v4(acpi->handle); | ||
340 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; | ||
341 | } | ||
342 | |||
343 | static void cmpc_accel_idev_init_v4(struct input_dev *inputdev) | ||
344 | { | ||
345 | set_bit(EV_ABS, inputdev->evbit); | ||
346 | input_set_abs_params(inputdev, ABS_X, -255, 255, 16, 0); | ||
347 | input_set_abs_params(inputdev, ABS_Y, -255, 255, 16, 0); | ||
348 | input_set_abs_params(inputdev, ABS_Z, -255, 255, 16, 0); | ||
349 | inputdev->open = cmpc_accel_open_v4; | ||
350 | inputdev->close = cmpc_accel_close_v4; | ||
351 | } | ||
352 | |||
353 | static int cmpc_accel_suspend_v4(struct device *dev) | ||
354 | { | ||
355 | struct input_dev *inputdev; | ||
356 | struct cmpc_accel *accel; | ||
357 | |||
358 | inputdev = dev_get_drvdata(dev); | ||
359 | accel = dev_get_drvdata(&inputdev->dev); | ||
360 | |||
361 | if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) | ||
362 | return cmpc_stop_accel_v4(to_acpi_device(dev)->handle); | ||
363 | |||
364 | return 0; | ||
365 | } | ||
366 | |||
367 | static int cmpc_accel_resume_v4(struct device *dev) | ||
368 | { | ||
369 | struct input_dev *inputdev; | ||
370 | struct cmpc_accel *accel; | ||
371 | |||
372 | inputdev = dev_get_drvdata(dev); | ||
373 | accel = dev_get_drvdata(&inputdev->dev); | ||
374 | |||
375 | if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) { | ||
376 | cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle, | ||
377 | accel->sensitivity); | ||
378 | cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle, | ||
379 | accel->g_select); | ||
380 | |||
381 | if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle))) | ||
382 | return -EIO; | ||
383 | } | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | static int cmpc_accel_add_v4(struct acpi_device *acpi) | ||
389 | { | ||
390 | int error; | ||
391 | struct input_dev *inputdev; | ||
392 | struct cmpc_accel *accel; | ||
393 | |||
394 | accel = kmalloc(sizeof(*accel), GFP_KERNEL); | ||
395 | if (!accel) | ||
396 | return -ENOMEM; | ||
397 | |||
398 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; | ||
399 | |||
400 | accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; | ||
401 | cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); | ||
402 | |||
403 | error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); | ||
404 | if (error) | ||
405 | goto failed_sensitivity; | ||
406 | |||
407 | accel->g_select = CMPC_ACCEL_G_SELECT_DEFAULT; | ||
408 | cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); | ||
409 | |||
410 | error = device_create_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); | ||
411 | if (error) | ||
412 | goto failed_g_select; | ||
413 | |||
414 | error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4", | ||
415 | cmpc_accel_idev_init_v4); | ||
416 | if (error) | ||
417 | goto failed_input; | ||
418 | |||
419 | inputdev = dev_get_drvdata(&acpi->dev); | ||
420 | dev_set_drvdata(&inputdev->dev, accel); | ||
421 | |||
422 | return 0; | ||
423 | |||
424 | failed_input: | ||
425 | device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); | ||
426 | failed_g_select: | ||
427 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); | ||
428 | failed_sensitivity: | ||
429 | kfree(accel); | ||
430 | return error; | ||
431 | } | ||
432 | |||
433 | static int cmpc_accel_remove_v4(struct acpi_device *acpi, int type) | ||
434 | { | ||
435 | struct input_dev *inputdev; | ||
436 | struct cmpc_accel *accel; | ||
437 | |||
438 | inputdev = dev_get_drvdata(&acpi->dev); | ||
439 | accel = dev_get_drvdata(&inputdev->dev); | ||
440 | |||
441 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); | ||
442 | device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); | ||
443 | return cmpc_remove_acpi_notify_device(acpi); | ||
444 | } | ||
445 | |||
446 | static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4, | ||
447 | cmpc_accel_resume_v4); | ||
448 | |||
449 | static const struct acpi_device_id cmpc_accel_device_ids_v4[] = { | ||
450 | {CMPC_ACCEL_HID_V4, 0}, | ||
451 | {"", 0} | ||
452 | }; | ||
453 | |||
454 | static struct acpi_driver cmpc_accel_acpi_driver_v4 = { | ||
455 | .owner = THIS_MODULE, | ||
456 | .name = "cmpc_accel_v4", | ||
457 | .class = "cmpc_accel_v4", | ||
458 | .ids = cmpc_accel_device_ids_v4, | ||
459 | .ops = { | ||
460 | .add = cmpc_accel_add_v4, | ||
461 | .remove = cmpc_accel_remove_v4, | ||
462 | .notify = cmpc_accel_handler_v4, | ||
463 | }, | ||
464 | .drv.pm = &cmpc_accel_pm, | ||
465 | }; | ||
466 | |||
467 | |||
468 | /* | ||
469 | * Accelerometer code for Classmate versions prior to V4 | ||
80 | */ | 470 | */ |
81 | static acpi_status cmpc_start_accel(acpi_handle handle) | 471 | static acpi_status cmpc_start_accel(acpi_handle handle) |
82 | { | 472 | { |
@@ -726,8 +1116,15 @@ static int cmpc_init(void) | |||
726 | if (r) | 1116 | if (r) |
727 | goto failed_accel; | 1117 | goto failed_accel; |
728 | 1118 | ||
1119 | r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4); | ||
1120 | if (r) | ||
1121 | goto failed_accel_v4; | ||
1122 | |||
729 | return r; | 1123 | return r; |
730 | 1124 | ||
1125 | failed_accel_v4: | ||
1126 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); | ||
1127 | |||
731 | failed_accel: | 1128 | failed_accel: |
732 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); | 1129 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); |
733 | 1130 | ||
@@ -743,6 +1140,7 @@ failed_keys: | |||
743 | 1140 | ||
744 | static void cmpc_exit(void) | 1141 | static void cmpc_exit(void) |
745 | { | 1142 | { |
1143 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4); | ||
746 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); | 1144 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); |
747 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); | 1145 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); |
748 | acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); | 1146 | acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); |
@@ -754,6 +1152,7 @@ module_exit(cmpc_exit); | |||
754 | 1152 | ||
755 | static const struct acpi_device_id cmpc_device_ids[] = { | 1153 | static const struct acpi_device_id cmpc_device_ids[] = { |
756 | {CMPC_ACCEL_HID, 0}, | 1154 | {CMPC_ACCEL_HID, 0}, |
1155 | {CMPC_ACCEL_HID_V4, 0}, | ||
757 | {CMPC_TABLET_HID, 0}, | 1156 | {CMPC_TABLET_HID, 0}, |
758 | {CMPC_IPML_HID, 0}, | 1157 | {CMPC_IPML_HID, 0}, |
759 | {CMPC_KEYS_HID, 0}, | 1158 | {CMPC_KEYS_HID, 0}, |