Commit | Line | Data |
---|---|---|
529aa8cb TLSC |
1 | /* |
2 | * Copyright (C) 2009 Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along | |
15 | * with this program; if not, write to the Free Software Foundation, Inc., | |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
17 | */ | |
18 | ||
19 | ||
20 | #include <linux/init.h> | |
21 | #include <linux/module.h> | |
5a0e3ad6 | 22 | #include <linux/slab.h> |
529aa8cb TLSC |
23 | #include <linux/workqueue.h> |
24 | #include <acpi/acpi_drivers.h> | |
25 | #include <linux/backlight.h> | |
26 | #include <linux/input.h> | |
d5c051f1 | 27 | #include <linux/rfkill.h> |
529aa8cb TLSC |
28 | |
29 | MODULE_LICENSE("GPL"); | |
30 | ||
31 | ||
32 | struct cmpc_accel { | |
33 | int sensitivity; | |
7125587d MG |
34 | int g_select; |
35 | int inputdev_state; | |
529aa8cb TLSC |
36 | }; |
37 | ||
7125587d MG |
38 | #define CMPC_ACCEL_DEV_STATE_CLOSED 0 |
39 | #define CMPC_ACCEL_DEV_STATE_OPEN 1 | |
529aa8cb | 40 | |
7125587d MG |
41 | #define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 |
42 | #define CMPC_ACCEL_G_SELECT_DEFAULT 0 | |
529aa8cb | 43 | |
02e77a55 | 44 | #define CMPC_ACCEL_HID "ACCE0000" |
7125587d | 45 | #define CMPC_ACCEL_HID_V4 "ACCE0001" |
02e77a55 | 46 | #define CMPC_TABLET_HID "TBLT0000" |
d5c051f1 | 47 | #define CMPC_IPML_HID "IPML200" |
0ece8d51 | 48 | #define CMPC_KEYS_HID "FNBT0000" |
02e77a55 | 49 | |
529aa8cb TLSC |
50 | /* |
51 | * Generic input device code. | |
52 | */ | |
53 | ||
54 | typedef void (*input_device_init)(struct input_dev *dev); | |
55 | ||
56 | static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, | |
57 | input_device_init idev_init) | |
58 | { | |
59 | struct input_dev *inputdev; | |
60 | int error; | |
61 | ||
62 | inputdev = input_allocate_device(); | |
63 | if (!inputdev) | |
64 | return -ENOMEM; | |
65 | inputdev->name = name; | |
66 | inputdev->dev.parent = &acpi->dev; | |
67 | idev_init(inputdev); | |
68 | error = input_register_device(inputdev); | |
69 | if (error) { | |
70 | input_free_device(inputdev); | |
71 | return error; | |
72 | } | |
73 | dev_set_drvdata(&acpi->dev, inputdev); | |
74 | return 0; | |
75 | } | |
76 | ||
77 | static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) | |
78 | { | |
79 | struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); | |
80 | input_unregister_device(inputdev); | |
81 | return 0; | |
82 | } | |
83 | ||
84 | /* | |
7125587d MG |
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 | ||
3567a4e2 | 353 | #ifdef CONFIG_PM_SLEEP |
7125587d MG |
354 | static int cmpc_accel_suspend_v4(struct device *dev) |
355 | { | |
356 | struct input_dev *inputdev; | |
357 | struct cmpc_accel *accel; | |
358 | ||
359 | inputdev = dev_get_drvdata(dev); | |
360 | accel = dev_get_drvdata(&inputdev->dev); | |
361 | ||
362 | if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) | |
363 | return cmpc_stop_accel_v4(to_acpi_device(dev)->handle); | |
364 | ||
365 | return 0; | |
366 | } | |
367 | ||
368 | static int cmpc_accel_resume_v4(struct device *dev) | |
369 | { | |
370 | struct input_dev *inputdev; | |
371 | struct cmpc_accel *accel; | |
372 | ||
373 | inputdev = dev_get_drvdata(dev); | |
374 | accel = dev_get_drvdata(&inputdev->dev); | |
375 | ||
376 | if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) { | |
377 | cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle, | |
378 | accel->sensitivity); | |
379 | cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle, | |
380 | accel->g_select); | |
381 | ||
382 | if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle))) | |
383 | return -EIO; | |
384 | } | |
385 | ||
386 | return 0; | |
387 | } | |
3567a4e2 | 388 | #endif |
7125587d MG |
389 | |
390 | static int cmpc_accel_add_v4(struct acpi_device *acpi) | |
391 | { | |
392 | int error; | |
393 | struct input_dev *inputdev; | |
394 | struct cmpc_accel *accel; | |
395 | ||
396 | accel = kmalloc(sizeof(*accel), GFP_KERNEL); | |
397 | if (!accel) | |
398 | return -ENOMEM; | |
399 | ||
400 | accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; | |
401 | ||
402 | accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; | |
403 | cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); | |
404 | ||
405 | error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); | |
406 | if (error) | |
407 | goto failed_sensitivity; | |
408 | ||
409 | accel->g_select = CMPC_ACCEL_G_SELECT_DEFAULT; | |
410 | cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); | |
411 | ||
412 | error = device_create_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); | |
413 | if (error) | |
414 | goto failed_g_select; | |
415 | ||
416 | error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4", | |
417 | cmpc_accel_idev_init_v4); | |
418 | if (error) | |
419 | goto failed_input; | |
420 | ||
421 | inputdev = dev_get_drvdata(&acpi->dev); | |
422 | dev_set_drvdata(&inputdev->dev, accel); | |
423 | ||
424 | return 0; | |
425 | ||
426 | failed_input: | |
427 | device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); | |
428 | failed_g_select: | |
429 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); | |
430 | failed_sensitivity: | |
431 | kfree(accel); | |
432 | return error; | |
433 | } | |
434 | ||
51fac838 | 435 | static int cmpc_accel_remove_v4(struct acpi_device *acpi) |
7125587d MG |
436 | { |
437 | struct input_dev *inputdev; | |
438 | struct cmpc_accel *accel; | |
439 | ||
440 | inputdev = dev_get_drvdata(&acpi->dev); | |
441 | accel = dev_get_drvdata(&inputdev->dev); | |
442 | ||
443 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); | |
444 | device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); | |
445 | return cmpc_remove_acpi_notify_device(acpi); | |
446 | } | |
447 | ||
448 | static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4, | |
449 | cmpc_accel_resume_v4); | |
450 | ||
451 | static const struct acpi_device_id cmpc_accel_device_ids_v4[] = { | |
452 | {CMPC_ACCEL_HID_V4, 0}, | |
453 | {"", 0} | |
454 | }; | |
455 | ||
456 | static struct acpi_driver cmpc_accel_acpi_driver_v4 = { | |
457 | .owner = THIS_MODULE, | |
458 | .name = "cmpc_accel_v4", | |
459 | .class = "cmpc_accel_v4", | |
460 | .ids = cmpc_accel_device_ids_v4, | |
461 | .ops = { | |
462 | .add = cmpc_accel_add_v4, | |
463 | .remove = cmpc_accel_remove_v4, | |
464 | .notify = cmpc_accel_handler_v4, | |
465 | }, | |
466 | .drv.pm = &cmpc_accel_pm, | |
467 | }; | |
468 | ||
469 | ||
470 | /* | |
471 | * Accelerometer code for Classmate versions prior to V4 | |
529aa8cb TLSC |
472 | */ |
473 | static acpi_status cmpc_start_accel(acpi_handle handle) | |
474 | { | |
475 | union acpi_object param[2]; | |
476 | struct acpi_object_list input; | |
477 | acpi_status status; | |
478 | ||
479 | param[0].type = ACPI_TYPE_INTEGER; | |
480 | param[0].integer.value = 0x3; | |
481 | param[1].type = ACPI_TYPE_INTEGER; | |
482 | input.count = 2; | |
483 | input.pointer = param; | |
484 | status = acpi_evaluate_object(handle, "ACMD", &input, NULL); | |
485 | return status; | |
486 | } | |
487 | ||
488 | static acpi_status cmpc_stop_accel(acpi_handle handle) | |
489 | { | |
490 | union acpi_object param[2]; | |
491 | struct acpi_object_list input; | |
492 | acpi_status status; | |
493 | ||
494 | param[0].type = ACPI_TYPE_INTEGER; | |
495 | param[0].integer.value = 0x4; | |
496 | param[1].type = ACPI_TYPE_INTEGER; | |
497 | input.count = 2; | |
498 | input.pointer = param; | |
499 | status = acpi_evaluate_object(handle, "ACMD", &input, NULL); | |
500 | return status; | |
501 | } | |
502 | ||
503 | static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val) | |
504 | { | |
505 | union acpi_object param[2]; | |
506 | struct acpi_object_list input; | |
507 | ||
508 | param[0].type = ACPI_TYPE_INTEGER; | |
509 | param[0].integer.value = 0x02; | |
510 | param[1].type = ACPI_TYPE_INTEGER; | |
511 | param[1].integer.value = val; | |
512 | input.count = 2; | |
513 | input.pointer = param; | |
514 | return acpi_evaluate_object(handle, "ACMD", &input, NULL); | |
515 | } | |
516 | ||
517 | static acpi_status cmpc_get_accel(acpi_handle handle, | |
518 | unsigned char *x, | |
519 | unsigned char *y, | |
520 | unsigned char *z) | |
521 | { | |
522 | union acpi_object param[2]; | |
523 | struct acpi_object_list input; | |
524 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, 0 }; | |
525 | unsigned char *locs; | |
526 | acpi_status status; | |
527 | ||
528 | param[0].type = ACPI_TYPE_INTEGER; | |
529 | param[0].integer.value = 0x01; | |
530 | param[1].type = ACPI_TYPE_INTEGER; | |
531 | input.count = 2; | |
532 | input.pointer = param; | |
533 | status = acpi_evaluate_object(handle, "ACMD", &input, &output); | |
534 | if (ACPI_SUCCESS(status)) { | |
535 | union acpi_object *obj; | |
536 | obj = output.pointer; | |
537 | locs = obj->buffer.pointer; | |
538 | *x = locs[0]; | |
539 | *y = locs[1]; | |
540 | *z = locs[2]; | |
541 | kfree(output.pointer); | |
542 | } | |
543 | return status; | |
544 | } | |
545 | ||
546 | static void cmpc_accel_handler(struct acpi_device *dev, u32 event) | |
547 | { | |
548 | if (event == 0x81) { | |
549 | unsigned char x, y, z; | |
550 | acpi_status status; | |
551 | ||
552 | status = cmpc_get_accel(dev->handle, &x, &y, &z); | |
553 | if (ACPI_SUCCESS(status)) { | |
554 | struct input_dev *inputdev = dev_get_drvdata(&dev->dev); | |
555 | ||
556 | input_report_abs(inputdev, ABS_X, x); | |
557 | input_report_abs(inputdev, ABS_Y, y); | |
558 | input_report_abs(inputdev, ABS_Z, z); | |
559 | input_sync(inputdev); | |
560 | } | |
561 | } | |
562 | } | |
563 | ||
564 | static ssize_t cmpc_accel_sensitivity_show(struct device *dev, | |
565 | struct device_attribute *attr, | |
566 | char *buf) | |
567 | { | |
568 | struct acpi_device *acpi; | |
569 | struct input_dev *inputdev; | |
570 | struct cmpc_accel *accel; | |
571 | ||
572 | acpi = to_acpi_device(dev); | |
573 | inputdev = dev_get_drvdata(&acpi->dev); | |
574 | accel = dev_get_drvdata(&inputdev->dev); | |
575 | ||
576 | return sprintf(buf, "%d\n", accel->sensitivity); | |
577 | } | |
578 | ||
579 | static ssize_t cmpc_accel_sensitivity_store(struct device *dev, | |
580 | struct device_attribute *attr, | |
581 | const char *buf, size_t count) | |
582 | { | |
583 | struct acpi_device *acpi; | |
584 | struct input_dev *inputdev; | |
585 | struct cmpc_accel *accel; | |
586 | unsigned long sensitivity; | |
587 | int r; | |
588 | ||
589 | acpi = to_acpi_device(dev); | |
590 | inputdev = dev_get_drvdata(&acpi->dev); | |
591 | accel = dev_get_drvdata(&inputdev->dev); | |
592 | ||
593 | r = strict_strtoul(buf, 0, &sensitivity); | |
594 | if (r) | |
595 | return r; | |
596 | ||
597 | accel->sensitivity = sensitivity; | |
598 | cmpc_accel_set_sensitivity(acpi->handle, sensitivity); | |
599 | ||
600 | return strnlen(buf, count); | |
601 | } | |
602 | ||
df92754d | 603 | static struct device_attribute cmpc_accel_sensitivity_attr = { |
529aa8cb TLSC |
604 | .attr = { .name = "sensitivity", .mode = 0660 }, |
605 | .show = cmpc_accel_sensitivity_show, | |
606 | .store = cmpc_accel_sensitivity_store | |
607 | }; | |
608 | ||
609 | static int cmpc_accel_open(struct input_dev *input) | |
610 | { | |
611 | struct acpi_device *acpi; | |
612 | ||
613 | acpi = to_acpi_device(input->dev.parent); | |
614 | if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle))) | |
615 | return 0; | |
616 | return -EIO; | |
617 | } | |
618 | ||
619 | static void cmpc_accel_close(struct input_dev *input) | |
620 | { | |
621 | struct acpi_device *acpi; | |
622 | ||
623 | acpi = to_acpi_device(input->dev.parent); | |
624 | cmpc_stop_accel(acpi->handle); | |
625 | } | |
626 | ||
627 | static void cmpc_accel_idev_init(struct input_dev *inputdev) | |
628 | { | |
629 | set_bit(EV_ABS, inputdev->evbit); | |
630 | input_set_abs_params(inputdev, ABS_X, 0, 255, 8, 0); | |
631 | input_set_abs_params(inputdev, ABS_Y, 0, 255, 8, 0); | |
632 | input_set_abs_params(inputdev, ABS_Z, 0, 255, 8, 0); | |
633 | inputdev->open = cmpc_accel_open; | |
634 | inputdev->close = cmpc_accel_close; | |
635 | } | |
636 | ||
637 | static int cmpc_accel_add(struct acpi_device *acpi) | |
638 | { | |
639 | int error; | |
640 | struct input_dev *inputdev; | |
641 | struct cmpc_accel *accel; | |
642 | ||
643 | accel = kmalloc(sizeof(*accel), GFP_KERNEL); | |
644 | if (!accel) | |
645 | return -ENOMEM; | |
646 | ||
647 | accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; | |
648 | cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity); | |
649 | ||
650 | error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr); | |
651 | if (error) | |
652 | goto failed_file; | |
653 | ||
654 | error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel", | |
655 | cmpc_accel_idev_init); | |
656 | if (error) | |
657 | goto failed_input; | |
658 | ||
659 | inputdev = dev_get_drvdata(&acpi->dev); | |
660 | dev_set_drvdata(&inputdev->dev, accel); | |
661 | ||
662 | return 0; | |
663 | ||
664 | failed_input: | |
665 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); | |
666 | failed_file: | |
667 | kfree(accel); | |
668 | return error; | |
669 | } | |
670 | ||
51fac838 | 671 | static int cmpc_accel_remove(struct acpi_device *acpi) |
529aa8cb TLSC |
672 | { |
673 | struct input_dev *inputdev; | |
674 | struct cmpc_accel *accel; | |
675 | ||
676 | inputdev = dev_get_drvdata(&acpi->dev); | |
677 | accel = dev_get_drvdata(&inputdev->dev); | |
678 | ||
679 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); | |
680 | return cmpc_remove_acpi_notify_device(acpi); | |
681 | } | |
682 | ||
683 | static const struct acpi_device_id cmpc_accel_device_ids[] = { | |
02e77a55 | 684 | {CMPC_ACCEL_HID, 0}, |
529aa8cb TLSC |
685 | {"", 0} |
686 | }; | |
529aa8cb TLSC |
687 | |
688 | static struct acpi_driver cmpc_accel_acpi_driver = { | |
689 | .owner = THIS_MODULE, | |
690 | .name = "cmpc_accel", | |
691 | .class = "cmpc_accel", | |
692 | .ids = cmpc_accel_device_ids, | |
693 | .ops = { | |
694 | .add = cmpc_accel_add, | |
695 | .remove = cmpc_accel_remove, | |
696 | .notify = cmpc_accel_handler, | |
697 | } | |
698 | }; | |
699 | ||
700 | ||
701 | /* | |
702 | * Tablet mode code. | |
703 | */ | |
704 | static acpi_status cmpc_get_tablet(acpi_handle handle, | |
705 | unsigned long long *value) | |
706 | { | |
707 | union acpi_object param; | |
708 | struct acpi_object_list input; | |
709 | unsigned long long output; | |
710 | acpi_status status; | |
711 | ||
712 | param.type = ACPI_TYPE_INTEGER; | |
713 | param.integer.value = 0x01; | |
714 | input.count = 1; | |
715 | input.pointer = ¶m; | |
716 | status = acpi_evaluate_integer(handle, "TCMD", &input, &output); | |
717 | if (ACPI_SUCCESS(status)) | |
718 | *value = output; | |
719 | return status; | |
720 | } | |
721 | ||
722 | static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) | |
723 | { | |
724 | unsigned long long val = 0; | |
725 | struct input_dev *inputdev = dev_get_drvdata(&dev->dev); | |
726 | ||
727 | if (event == 0x81) { | |
ad20c73b | 728 | if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) { |
529aa8cb | 729 | input_report_switch(inputdev, SW_TABLET_MODE, !val); |
ad20c73b CALP |
730 | input_sync(inputdev); |
731 | } | |
529aa8cb TLSC |
732 | } |
733 | } | |
734 | ||
735 | static void cmpc_tablet_idev_init(struct input_dev *inputdev) | |
736 | { | |
737 | unsigned long long val = 0; | |
738 | struct acpi_device *acpi; | |
739 | ||
740 | set_bit(EV_SW, inputdev->evbit); | |
741 | set_bit(SW_TABLET_MODE, inputdev->swbit); | |
742 | ||
743 | acpi = to_acpi_device(inputdev->dev.parent); | |
ad20c73b | 744 | if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) { |
529aa8cb | 745 | input_report_switch(inputdev, SW_TABLET_MODE, !val); |
ad20c73b CALP |
746 | input_sync(inputdev); |
747 | } | |
529aa8cb TLSC |
748 | } |
749 | ||
750 | static int cmpc_tablet_add(struct acpi_device *acpi) | |
751 | { | |
752 | return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet", | |
753 | cmpc_tablet_idev_init); | |
754 | } | |
755 | ||
51fac838 | 756 | static int cmpc_tablet_remove(struct acpi_device *acpi) |
529aa8cb TLSC |
757 | { |
758 | return cmpc_remove_acpi_notify_device(acpi); | |
759 | } | |
760 | ||
3567a4e2 | 761 | #ifdef CONFIG_PM_SLEEP |
81bc4954 | 762 | static int cmpc_tablet_resume(struct device *dev) |
529aa8cb | 763 | { |
81bc4954 RW |
764 | struct input_dev *inputdev = dev_get_drvdata(dev); |
765 | ||
529aa8cb | 766 | unsigned long long val = 0; |
ad20c73b | 767 | if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) { |
529aa8cb | 768 | input_report_switch(inputdev, SW_TABLET_MODE, !val); |
ad20c73b CALP |
769 | input_sync(inputdev); |
770 | } | |
529aa8cb TLSC |
771 | return 0; |
772 | } | |
3567a4e2 | 773 | #endif |
529aa8cb | 774 | |
81bc4954 RW |
775 | static SIMPLE_DEV_PM_OPS(cmpc_tablet_pm, NULL, cmpc_tablet_resume); |
776 | ||
529aa8cb | 777 | static const struct acpi_device_id cmpc_tablet_device_ids[] = { |
02e77a55 | 778 | {CMPC_TABLET_HID, 0}, |
529aa8cb TLSC |
779 | {"", 0} |
780 | }; | |
529aa8cb TLSC |
781 | |
782 | static struct acpi_driver cmpc_tablet_acpi_driver = { | |
783 | .owner = THIS_MODULE, | |
784 | .name = "cmpc_tablet", | |
785 | .class = "cmpc_tablet", | |
786 | .ids = cmpc_tablet_device_ids, | |
787 | .ops = { | |
788 | .add = cmpc_tablet_add, | |
789 | .remove = cmpc_tablet_remove, | |
529aa8cb | 790 | .notify = cmpc_tablet_handler, |
81bc4954 RW |
791 | }, |
792 | .drv.pm = &cmpc_tablet_pm, | |
529aa8cb TLSC |
793 | }; |
794 | ||
795 | ||
796 | /* | |
797 | * Backlight code. | |
798 | */ | |
799 | ||
800 | static acpi_status cmpc_get_brightness(acpi_handle handle, | |
801 | unsigned long long *value) | |
802 | { | |
803 | union acpi_object param; | |
804 | struct acpi_object_list input; | |
805 | unsigned long long output; | |
806 | acpi_status status; | |
807 | ||
808 | param.type = ACPI_TYPE_INTEGER; | |
809 | param.integer.value = 0xC0; | |
810 | input.count = 1; | |
811 | input.pointer = ¶m; | |
812 | status = acpi_evaluate_integer(handle, "GRDI", &input, &output); | |
813 | if (ACPI_SUCCESS(status)) | |
814 | *value = output; | |
815 | return status; | |
816 | } | |
817 | ||
818 | static acpi_status cmpc_set_brightness(acpi_handle handle, | |
819 | unsigned long long value) | |
820 | { | |
821 | union acpi_object param[2]; | |
822 | struct acpi_object_list input; | |
823 | acpi_status status; | |
824 | unsigned long long output; | |
825 | ||
826 | param[0].type = ACPI_TYPE_INTEGER; | |
827 | param[0].integer.value = 0xC0; | |
828 | param[1].type = ACPI_TYPE_INTEGER; | |
829 | param[1].integer.value = value; | |
830 | input.count = 2; | |
831 | input.pointer = param; | |
832 | status = acpi_evaluate_integer(handle, "GWRI", &input, &output); | |
833 | return status; | |
834 | } | |
835 | ||
836 | static int cmpc_bl_get_brightness(struct backlight_device *bd) | |
837 | { | |
838 | acpi_status status; | |
839 | acpi_handle handle; | |
840 | unsigned long long brightness; | |
841 | ||
842 | handle = bl_get_data(bd); | |
843 | status = cmpc_get_brightness(handle, &brightness); | |
844 | if (ACPI_SUCCESS(status)) | |
845 | return brightness; | |
846 | else | |
847 | return -1; | |
848 | } | |
849 | ||
850 | static int cmpc_bl_update_status(struct backlight_device *bd) | |
851 | { | |
852 | acpi_status status; | |
853 | acpi_handle handle; | |
854 | ||
855 | handle = bl_get_data(bd); | |
856 | status = cmpc_set_brightness(handle, bd->props.brightness); | |
857 | if (ACPI_SUCCESS(status)) | |
858 | return 0; | |
859 | else | |
860 | return -1; | |
861 | } | |
862 | ||
f0af7899 | 863 | static const struct backlight_ops cmpc_bl_ops = { |
529aa8cb TLSC |
864 | .get_brightness = cmpc_bl_get_brightness, |
865 | .update_status = cmpc_bl_update_status | |
866 | }; | |
867 | ||
d5c051f1 TLSC |
868 | /* |
869 | * RFKILL code. | |
870 | */ | |
871 | ||
872 | static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle, | |
873 | unsigned long long *value) | |
529aa8cb | 874 | { |
d5c051f1 TLSC |
875 | union acpi_object param; |
876 | struct acpi_object_list input; | |
877 | unsigned long long output; | |
878 | acpi_status status; | |
879 | ||
880 | param.type = ACPI_TYPE_INTEGER; | |
881 | param.integer.value = 0xC1; | |
882 | input.count = 1; | |
883 | input.pointer = ¶m; | |
884 | status = acpi_evaluate_integer(handle, "GRDI", &input, &output); | |
885 | if (ACPI_SUCCESS(status)) | |
886 | *value = output; | |
887 | return status; | |
888 | } | |
889 | ||
890 | static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle, | |
891 | unsigned long long value) | |
892 | { | |
893 | union acpi_object param[2]; | |
894 | struct acpi_object_list input; | |
895 | acpi_status status; | |
896 | unsigned long long output; | |
897 | ||
898 | param[0].type = ACPI_TYPE_INTEGER; | |
899 | param[0].integer.value = 0xC1; | |
900 | param[1].type = ACPI_TYPE_INTEGER; | |
901 | param[1].integer.value = value; | |
902 | input.count = 2; | |
903 | input.pointer = param; | |
904 | status = acpi_evaluate_integer(handle, "GWRI", &input, &output); | |
905 | return status; | |
906 | } | |
907 | ||
908 | static void cmpc_rfkill_query(struct rfkill *rfkill, void *data) | |
909 | { | |
910 | acpi_status status; | |
911 | acpi_handle handle; | |
912 | unsigned long long state; | |
913 | bool blocked; | |
914 | ||
915 | handle = data; | |
916 | status = cmpc_get_rfkill_wlan(handle, &state); | |
917 | if (ACPI_SUCCESS(status)) { | |
918 | blocked = state & 1 ? false : true; | |
919 | rfkill_set_sw_state(rfkill, blocked); | |
920 | } | |
921 | } | |
922 | ||
923 | static int cmpc_rfkill_block(void *data, bool blocked) | |
924 | { | |
925 | acpi_status status; | |
926 | acpi_handle handle; | |
927 | unsigned long long state; | |
698e1641 | 928 | bool is_blocked; |
d5c051f1 TLSC |
929 | |
930 | handle = data; | |
931 | status = cmpc_get_rfkill_wlan(handle, &state); | |
932 | if (ACPI_FAILURE(status)) | |
933 | return -ENODEV; | |
698e1641 HRK |
934 | /* Check if we really need to call cmpc_set_rfkill_wlan */ |
935 | is_blocked = state & 1 ? false : true; | |
936 | if (is_blocked != blocked) { | |
937 | state = blocked ? 0 : 1; | |
938 | status = cmpc_set_rfkill_wlan(handle, state); | |
939 | if (ACPI_FAILURE(status)) | |
940 | return -ENODEV; | |
941 | } | |
d5c051f1 TLSC |
942 | return 0; |
943 | } | |
944 | ||
945 | static const struct rfkill_ops cmpc_rfkill_ops = { | |
946 | .query = cmpc_rfkill_query, | |
947 | .set_block = cmpc_rfkill_block, | |
948 | }; | |
949 | ||
950 | /* | |
951 | * Common backlight and rfkill code. | |
952 | */ | |
953 | ||
954 | struct ipml200_dev { | |
529aa8cb | 955 | struct backlight_device *bd; |
d5c051f1 TLSC |
956 | struct rfkill *rf; |
957 | }; | |
958 | ||
959 | static int cmpc_ipml_add(struct acpi_device *acpi) | |
960 | { | |
961 | int retval; | |
962 | struct ipml200_dev *ipml; | |
963 | struct backlight_properties props; | |
964 | ||
965 | ipml = kmalloc(sizeof(*ipml), GFP_KERNEL); | |
966 | if (ipml == NULL) | |
967 | return -ENOMEM; | |
529aa8cb | 968 | |
a19a6ee6 | 969 | memset(&props, 0, sizeof(struct backlight_properties)); |
bb7ca747 | 970 | props.type = BACKLIGHT_PLATFORM; |
a19a6ee6 | 971 | props.max_brightness = 7; |
d5c051f1 TLSC |
972 | ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev, |
973 | acpi->handle, &cmpc_bl_ops, | |
974 | &props); | |
975 | if (IS_ERR(ipml->bd)) { | |
976 | retval = PTR_ERR(ipml->bd); | |
977 | goto out_bd; | |
978 | } | |
979 | ||
980 | ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN, | |
981 | &cmpc_rfkill_ops, acpi->handle); | |
b95d13ea TLSC |
982 | /* |
983 | * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV). | |
984 | * This is OK, however, since all other uses of the device will not | |
985 | * derefence it. | |
986 | */ | |
987 | if (ipml->rf) { | |
d5c051f1 TLSC |
988 | retval = rfkill_register(ipml->rf); |
989 | if (retval) { | |
990 | rfkill_destroy(ipml->rf); | |
991 | ipml->rf = NULL; | |
992 | } | |
d5c051f1 TLSC |
993 | } |
994 | ||
995 | dev_set_drvdata(&acpi->dev, ipml); | |
529aa8cb | 996 | return 0; |
d5c051f1 TLSC |
997 | |
998 | out_bd: | |
999 | kfree(ipml); | |
1000 | return retval; | |
529aa8cb TLSC |
1001 | } |
1002 | ||
51fac838 | 1003 | static int cmpc_ipml_remove(struct acpi_device *acpi) |
529aa8cb | 1004 | { |
d5c051f1 TLSC |
1005 | struct ipml200_dev *ipml; |
1006 | ||
1007 | ipml = dev_get_drvdata(&acpi->dev); | |
1008 | ||
1009 | backlight_device_unregister(ipml->bd); | |
1010 | ||
1011 | if (ipml->rf) { | |
1012 | rfkill_unregister(ipml->rf); | |
1013 | rfkill_destroy(ipml->rf); | |
1014 | } | |
1015 | ||
1016 | kfree(ipml); | |
529aa8cb | 1017 | |
529aa8cb TLSC |
1018 | return 0; |
1019 | } | |
1020 | ||
d5c051f1 TLSC |
1021 | static const struct acpi_device_id cmpc_ipml_device_ids[] = { |
1022 | {CMPC_IPML_HID, 0}, | |
529aa8cb TLSC |
1023 | {"", 0} |
1024 | }; | |
529aa8cb | 1025 | |
d5c051f1 | 1026 | static struct acpi_driver cmpc_ipml_acpi_driver = { |
529aa8cb TLSC |
1027 | .owner = THIS_MODULE, |
1028 | .name = "cmpc", | |
1029 | .class = "cmpc", | |
d5c051f1 | 1030 | .ids = cmpc_ipml_device_ids, |
529aa8cb | 1031 | .ops = { |
d5c051f1 TLSC |
1032 | .add = cmpc_ipml_add, |
1033 | .remove = cmpc_ipml_remove | |
529aa8cb TLSC |
1034 | } |
1035 | }; | |
1036 | ||
1037 | ||
1038 | /* | |
1039 | * Extra keys code. | |
1040 | */ | |
1041 | static int cmpc_keys_codes[] = { | |
1042 | KEY_UNKNOWN, | |
1043 | KEY_WLAN, | |
1044 | KEY_SWITCHVIDEOMODE, | |
1045 | KEY_BRIGHTNESSDOWN, | |
1046 | KEY_BRIGHTNESSUP, | |
1047 | KEY_VENDOR, | |
881a6c25 TLSC |
1048 | KEY_UNKNOWN, |
1049 | KEY_CAMERA, | |
1050 | KEY_BACK, | |
1051 | KEY_FORWARD, | |
529aa8cb TLSC |
1052 | KEY_MAX |
1053 | }; | |
1054 | ||
1055 | static void cmpc_keys_handler(struct acpi_device *dev, u32 event) | |
1056 | { | |
1057 | struct input_dev *inputdev; | |
1058 | int code = KEY_MAX; | |
1059 | ||
1060 | if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) | |
1061 | code = cmpc_keys_codes[event & 0x0F]; | |
3098064d | 1062 | inputdev = dev_get_drvdata(&dev->dev); |
529aa8cb | 1063 | input_report_key(inputdev, code, !(event & 0x10)); |
72135d21 | 1064 | input_sync(inputdev); |
529aa8cb TLSC |
1065 | } |
1066 | ||
1067 | static void cmpc_keys_idev_init(struct input_dev *inputdev) | |
1068 | { | |
1069 | int i; | |
1070 | ||
1071 | set_bit(EV_KEY, inputdev->evbit); | |
1072 | for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++) | |
1073 | set_bit(cmpc_keys_codes[i], inputdev->keybit); | |
1074 | } | |
1075 | ||
1076 | static int cmpc_keys_add(struct acpi_device *acpi) | |
1077 | { | |
1078 | return cmpc_add_acpi_notify_device(acpi, "cmpc_keys", | |
1079 | cmpc_keys_idev_init); | |
1080 | } | |
1081 | ||
51fac838 | 1082 | static int cmpc_keys_remove(struct acpi_device *acpi) |
529aa8cb TLSC |
1083 | { |
1084 | return cmpc_remove_acpi_notify_device(acpi); | |
1085 | } | |
1086 | ||
1087 | static const struct acpi_device_id cmpc_keys_device_ids[] = { | |
02e77a55 | 1088 | {CMPC_KEYS_HID, 0}, |
529aa8cb TLSC |
1089 | {"", 0} |
1090 | }; | |
529aa8cb TLSC |
1091 | |
1092 | static struct acpi_driver cmpc_keys_acpi_driver = { | |
1093 | .owner = THIS_MODULE, | |
1094 | .name = "cmpc_keys", | |
1095 | .class = "cmpc_keys", | |
1096 | .ids = cmpc_keys_device_ids, | |
1097 | .ops = { | |
1098 | .add = cmpc_keys_add, | |
1099 | .remove = cmpc_keys_remove, | |
1100 | .notify = cmpc_keys_handler, | |
1101 | } | |
1102 | }; | |
1103 | ||
1104 | ||
1105 | /* | |
1106 | * General init/exit code. | |
1107 | */ | |
1108 | ||
1109 | static int cmpc_init(void) | |
1110 | { | |
1111 | int r; | |
1112 | ||
1113 | r = acpi_bus_register_driver(&cmpc_keys_acpi_driver); | |
1114 | if (r) | |
1115 | goto failed_keys; | |
1116 | ||
d5c051f1 | 1117 | r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver); |
529aa8cb TLSC |
1118 | if (r) |
1119 | goto failed_bl; | |
1120 | ||
1121 | r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver); | |
1122 | if (r) | |
1123 | goto failed_tablet; | |
1124 | ||
1125 | r = acpi_bus_register_driver(&cmpc_accel_acpi_driver); | |
1126 | if (r) | |
1127 | goto failed_accel; | |
1128 | ||
7125587d MG |
1129 | r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4); |
1130 | if (r) | |
1131 | goto failed_accel_v4; | |
1132 | ||
529aa8cb TLSC |
1133 | return r; |
1134 | ||
7125587d MG |
1135 | failed_accel_v4: |
1136 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); | |
1137 | ||
529aa8cb TLSC |
1138 | failed_accel: |
1139 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); | |
1140 | ||
1141 | failed_tablet: | |
d5c051f1 | 1142 | acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); |
529aa8cb TLSC |
1143 | |
1144 | failed_bl: | |
1145 | acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); | |
1146 | ||
1147 | failed_keys: | |
1148 | return r; | |
1149 | } | |
1150 | ||
1151 | static void cmpc_exit(void) | |
1152 | { | |
7125587d | 1153 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4); |
529aa8cb TLSC |
1154 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); |
1155 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); | |
d5c051f1 | 1156 | acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); |
529aa8cb TLSC |
1157 | acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); |
1158 | } | |
1159 | ||
1160 | module_init(cmpc_init); | |
1161 | module_exit(cmpc_exit); | |
02e77a55 TLSC |
1162 | |
1163 | static const struct acpi_device_id cmpc_device_ids[] = { | |
1164 | {CMPC_ACCEL_HID, 0}, | |
7125587d | 1165 | {CMPC_ACCEL_HID_V4, 0}, |
02e77a55 | 1166 | {CMPC_TABLET_HID, 0}, |
d5c051f1 | 1167 | {CMPC_IPML_HID, 0}, |
02e77a55 TLSC |
1168 | {CMPC_KEYS_HID, 0}, |
1169 | {"", 0} | |
1170 | }; | |
1171 | ||
1172 | MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); |