diff options
Diffstat (limited to 'drivers/platform/x86/classmate-laptop.c')
-rw-r--r-- | drivers/platform/x86/classmate-laptop.c | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c new file mode 100644 index 000000000000..7f9e5ddc9498 --- /dev/null +++ b/drivers/platform/x86/classmate-laptop.c | |||
@@ -0,0 +1,629 @@ | |||
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> | ||
22 | #include <linux/slab.h> | ||
23 | #include <linux/workqueue.h> | ||
24 | #include <acpi/acpi_drivers.h> | ||
25 | #include <linux/backlight.h> | ||
26 | #include <linux/input.h> | ||
27 | |||
28 | MODULE_LICENSE("GPL"); | ||
29 | |||
30 | |||
31 | struct cmpc_accel { | ||
32 | int sensitivity; | ||
33 | }; | ||
34 | |||
35 | #define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 | ||
36 | |||
37 | |||
38 | #define CMPC_ACCEL_HID "ACCE0000" | ||
39 | #define CMPC_TABLET_HID "TBLT0000" | ||
40 | #define CMPC_BL_HID "IPML200" | ||
41 | #define CMPC_KEYS_HID "FnBT0000" | ||
42 | |||
43 | /* | ||
44 | * Generic input device code. | ||
45 | */ | ||
46 | |||
47 | typedef void (*input_device_init)(struct input_dev *dev); | ||
48 | |||
49 | static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, | ||
50 | input_device_init idev_init) | ||
51 | { | ||
52 | struct input_dev *inputdev; | ||
53 | int error; | ||
54 | |||
55 | inputdev = input_allocate_device(); | ||
56 | if (!inputdev) | ||
57 | return -ENOMEM; | ||
58 | inputdev->name = name; | ||
59 | inputdev->dev.parent = &acpi->dev; | ||
60 | idev_init(inputdev); | ||
61 | error = input_register_device(inputdev); | ||
62 | if (error) { | ||
63 | input_free_device(inputdev); | ||
64 | return error; | ||
65 | } | ||
66 | dev_set_drvdata(&acpi->dev, inputdev); | ||
67 | return 0; | ||
68 | } | ||
69 | |||
70 | static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) | ||
71 | { | ||
72 | struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); | ||
73 | input_unregister_device(inputdev); | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | /* | ||
78 | * Accelerometer code. | ||
79 | */ | ||
80 | static acpi_status cmpc_start_accel(acpi_handle handle) | ||
81 | { | ||
82 | union acpi_object param[2]; | ||
83 | struct acpi_object_list input; | ||
84 | acpi_status status; | ||
85 | |||
86 | param[0].type = ACPI_TYPE_INTEGER; | ||
87 | param[0].integer.value = 0x3; | ||
88 | param[1].type = ACPI_TYPE_INTEGER; | ||
89 | input.count = 2; | ||
90 | input.pointer = param; | ||
91 | status = acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
92 | return status; | ||
93 | } | ||
94 | |||
95 | static acpi_status cmpc_stop_accel(acpi_handle handle) | ||
96 | { | ||
97 | union acpi_object param[2]; | ||
98 | struct acpi_object_list input; | ||
99 | acpi_status status; | ||
100 | |||
101 | param[0].type = ACPI_TYPE_INTEGER; | ||
102 | param[0].integer.value = 0x4; | ||
103 | param[1].type = ACPI_TYPE_INTEGER; | ||
104 | input.count = 2; | ||
105 | input.pointer = param; | ||
106 | status = acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
107 | return status; | ||
108 | } | ||
109 | |||
110 | static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val) | ||
111 | { | ||
112 | union acpi_object param[2]; | ||
113 | struct acpi_object_list input; | ||
114 | |||
115 | param[0].type = ACPI_TYPE_INTEGER; | ||
116 | param[0].integer.value = 0x02; | ||
117 | param[1].type = ACPI_TYPE_INTEGER; | ||
118 | param[1].integer.value = val; | ||
119 | input.count = 2; | ||
120 | input.pointer = param; | ||
121 | return acpi_evaluate_object(handle, "ACMD", &input, NULL); | ||
122 | } | ||
123 | |||
124 | static acpi_status cmpc_get_accel(acpi_handle handle, | ||
125 | unsigned char *x, | ||
126 | unsigned char *y, | ||
127 | unsigned char *z) | ||
128 | { | ||
129 | union acpi_object param[2]; | ||
130 | struct acpi_object_list input; | ||
131 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, 0 }; | ||
132 | unsigned char *locs; | ||
133 | acpi_status status; | ||
134 | |||
135 | param[0].type = ACPI_TYPE_INTEGER; | ||
136 | param[0].integer.value = 0x01; | ||
137 | param[1].type = ACPI_TYPE_INTEGER; | ||
138 | input.count = 2; | ||
139 | input.pointer = param; | ||
140 | status = acpi_evaluate_object(handle, "ACMD", &input, &output); | ||
141 | if (ACPI_SUCCESS(status)) { | ||
142 | union acpi_object *obj; | ||
143 | obj = output.pointer; | ||
144 | locs = obj->buffer.pointer; | ||
145 | *x = locs[0]; | ||
146 | *y = locs[1]; | ||
147 | *z = locs[2]; | ||
148 | kfree(output.pointer); | ||
149 | } | ||
150 | return status; | ||
151 | } | ||
152 | |||
153 | static void cmpc_accel_handler(struct acpi_device *dev, u32 event) | ||
154 | { | ||
155 | if (event == 0x81) { | ||
156 | unsigned char x, y, z; | ||
157 | acpi_status status; | ||
158 | |||
159 | status = cmpc_get_accel(dev->handle, &x, &y, &z); | ||
160 | if (ACPI_SUCCESS(status)) { | ||
161 | struct input_dev *inputdev = dev_get_drvdata(&dev->dev); | ||
162 | |||
163 | input_report_abs(inputdev, ABS_X, x); | ||
164 | input_report_abs(inputdev, ABS_Y, y); | ||
165 | input_report_abs(inputdev, ABS_Z, z); | ||
166 | input_sync(inputdev); | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | |||
171 | static ssize_t cmpc_accel_sensitivity_show(struct device *dev, | ||
172 | struct device_attribute *attr, | ||
173 | char *buf) | ||
174 | { | ||
175 | struct acpi_device *acpi; | ||
176 | struct input_dev *inputdev; | ||
177 | struct cmpc_accel *accel; | ||
178 | |||
179 | acpi = to_acpi_device(dev); | ||
180 | inputdev = dev_get_drvdata(&acpi->dev); | ||
181 | accel = dev_get_drvdata(&inputdev->dev); | ||
182 | |||
183 | return sprintf(buf, "%d\n", accel->sensitivity); | ||
184 | } | ||
185 | |||
186 | static ssize_t cmpc_accel_sensitivity_store(struct device *dev, | ||
187 | struct device_attribute *attr, | ||
188 | const char *buf, size_t count) | ||
189 | { | ||
190 | struct acpi_device *acpi; | ||
191 | struct input_dev *inputdev; | ||
192 | struct cmpc_accel *accel; | ||
193 | unsigned long sensitivity; | ||
194 | int r; | ||
195 | |||
196 | acpi = to_acpi_device(dev); | ||
197 | inputdev = dev_get_drvdata(&acpi->dev); | ||
198 | accel = dev_get_drvdata(&inputdev->dev); | ||
199 | |||
200 | r = strict_strtoul(buf, 0, &sensitivity); | ||
201 | if (r) | ||
202 | return r; | ||
203 | |||
204 | accel->sensitivity = sensitivity; | ||
205 | cmpc_accel_set_sensitivity(acpi->handle, sensitivity); | ||
206 | |||
207 | return strnlen(buf, count); | ||
208 | } | ||
209 | |||
210 | struct device_attribute cmpc_accel_sensitivity_attr = { | ||
211 | .attr = { .name = "sensitivity", .mode = 0660 }, | ||
212 | .show = cmpc_accel_sensitivity_show, | ||
213 | .store = cmpc_accel_sensitivity_store | ||
214 | }; | ||
215 | |||
216 | static int cmpc_accel_open(struct input_dev *input) | ||
217 | { | ||
218 | struct acpi_device *acpi; | ||
219 | |||
220 | acpi = to_acpi_device(input->dev.parent); | ||
221 | if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle))) | ||
222 | return 0; | ||
223 | return -EIO; | ||
224 | } | ||
225 | |||
226 | static void cmpc_accel_close(struct input_dev *input) | ||
227 | { | ||
228 | struct acpi_device *acpi; | ||
229 | |||
230 | acpi = to_acpi_device(input->dev.parent); | ||
231 | cmpc_stop_accel(acpi->handle); | ||
232 | } | ||
233 | |||
234 | static void cmpc_accel_idev_init(struct input_dev *inputdev) | ||
235 | { | ||
236 | set_bit(EV_ABS, inputdev->evbit); | ||
237 | input_set_abs_params(inputdev, ABS_X, 0, 255, 8, 0); | ||
238 | input_set_abs_params(inputdev, ABS_Y, 0, 255, 8, 0); | ||
239 | input_set_abs_params(inputdev, ABS_Z, 0, 255, 8, 0); | ||
240 | inputdev->open = cmpc_accel_open; | ||
241 | inputdev->close = cmpc_accel_close; | ||
242 | } | ||
243 | |||
244 | static int cmpc_accel_add(struct acpi_device *acpi) | ||
245 | { | ||
246 | int error; | ||
247 | struct input_dev *inputdev; | ||
248 | struct cmpc_accel *accel; | ||
249 | |||
250 | accel = kmalloc(sizeof(*accel), GFP_KERNEL); | ||
251 | if (!accel) | ||
252 | return -ENOMEM; | ||
253 | |||
254 | accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; | ||
255 | cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity); | ||
256 | |||
257 | error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr); | ||
258 | if (error) | ||
259 | goto failed_file; | ||
260 | |||
261 | error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel", | ||
262 | cmpc_accel_idev_init); | ||
263 | if (error) | ||
264 | goto failed_input; | ||
265 | |||
266 | inputdev = dev_get_drvdata(&acpi->dev); | ||
267 | dev_set_drvdata(&inputdev->dev, accel); | ||
268 | |||
269 | return 0; | ||
270 | |||
271 | failed_input: | ||
272 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); | ||
273 | failed_file: | ||
274 | kfree(accel); | ||
275 | return error; | ||
276 | } | ||
277 | |||
278 | static int cmpc_accel_remove(struct acpi_device *acpi, int type) | ||
279 | { | ||
280 | struct input_dev *inputdev; | ||
281 | struct cmpc_accel *accel; | ||
282 | |||
283 | inputdev = dev_get_drvdata(&acpi->dev); | ||
284 | accel = dev_get_drvdata(&inputdev->dev); | ||
285 | |||
286 | device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); | ||
287 | return cmpc_remove_acpi_notify_device(acpi); | ||
288 | } | ||
289 | |||
290 | static const struct acpi_device_id cmpc_accel_device_ids[] = { | ||
291 | {CMPC_ACCEL_HID, 0}, | ||
292 | {"", 0} | ||
293 | }; | ||
294 | |||
295 | static struct acpi_driver cmpc_accel_acpi_driver = { | ||
296 | .owner = THIS_MODULE, | ||
297 | .name = "cmpc_accel", | ||
298 | .class = "cmpc_accel", | ||
299 | .ids = cmpc_accel_device_ids, | ||
300 | .ops = { | ||
301 | .add = cmpc_accel_add, | ||
302 | .remove = cmpc_accel_remove, | ||
303 | .notify = cmpc_accel_handler, | ||
304 | } | ||
305 | }; | ||
306 | |||
307 | |||
308 | /* | ||
309 | * Tablet mode code. | ||
310 | */ | ||
311 | static acpi_status cmpc_get_tablet(acpi_handle handle, | ||
312 | unsigned long long *value) | ||
313 | { | ||
314 | union acpi_object param; | ||
315 | struct acpi_object_list input; | ||
316 | unsigned long long output; | ||
317 | acpi_status status; | ||
318 | |||
319 | param.type = ACPI_TYPE_INTEGER; | ||
320 | param.integer.value = 0x01; | ||
321 | input.count = 1; | ||
322 | input.pointer = ¶m; | ||
323 | status = acpi_evaluate_integer(handle, "TCMD", &input, &output); | ||
324 | if (ACPI_SUCCESS(status)) | ||
325 | *value = output; | ||
326 | return status; | ||
327 | } | ||
328 | |||
329 | static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) | ||
330 | { | ||
331 | unsigned long long val = 0; | ||
332 | struct input_dev *inputdev = dev_get_drvdata(&dev->dev); | ||
333 | |||
334 | if (event == 0x81) { | ||
335 | if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) | ||
336 | input_report_switch(inputdev, SW_TABLET_MODE, !val); | ||
337 | } | ||
338 | } | ||
339 | |||
340 | static void cmpc_tablet_idev_init(struct input_dev *inputdev) | ||
341 | { | ||
342 | unsigned long long val = 0; | ||
343 | struct acpi_device *acpi; | ||
344 | |||
345 | set_bit(EV_SW, inputdev->evbit); | ||
346 | set_bit(SW_TABLET_MODE, inputdev->swbit); | ||
347 | |||
348 | acpi = to_acpi_device(inputdev->dev.parent); | ||
349 | if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) | ||
350 | input_report_switch(inputdev, SW_TABLET_MODE, !val); | ||
351 | } | ||
352 | |||
353 | static int cmpc_tablet_add(struct acpi_device *acpi) | ||
354 | { | ||
355 | return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet", | ||
356 | cmpc_tablet_idev_init); | ||
357 | } | ||
358 | |||
359 | static int cmpc_tablet_remove(struct acpi_device *acpi, int type) | ||
360 | { | ||
361 | return cmpc_remove_acpi_notify_device(acpi); | ||
362 | } | ||
363 | |||
364 | static int cmpc_tablet_resume(struct acpi_device *acpi) | ||
365 | { | ||
366 | struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); | ||
367 | unsigned long long val = 0; | ||
368 | if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) | ||
369 | input_report_switch(inputdev, SW_TABLET_MODE, !val); | ||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | static const struct acpi_device_id cmpc_tablet_device_ids[] = { | ||
374 | {CMPC_TABLET_HID, 0}, | ||
375 | {"", 0} | ||
376 | }; | ||
377 | |||
378 | static struct acpi_driver cmpc_tablet_acpi_driver = { | ||
379 | .owner = THIS_MODULE, | ||
380 | .name = "cmpc_tablet", | ||
381 | .class = "cmpc_tablet", | ||
382 | .ids = cmpc_tablet_device_ids, | ||
383 | .ops = { | ||
384 | .add = cmpc_tablet_add, | ||
385 | .remove = cmpc_tablet_remove, | ||
386 | .resume = cmpc_tablet_resume, | ||
387 | .notify = cmpc_tablet_handler, | ||
388 | } | ||
389 | }; | ||
390 | |||
391 | |||
392 | /* | ||
393 | * Backlight code. | ||
394 | */ | ||
395 | |||
396 | static acpi_status cmpc_get_brightness(acpi_handle handle, | ||
397 | unsigned long long *value) | ||
398 | { | ||
399 | union acpi_object param; | ||
400 | struct acpi_object_list input; | ||
401 | unsigned long long output; | ||
402 | acpi_status status; | ||
403 | |||
404 | param.type = ACPI_TYPE_INTEGER; | ||
405 | param.integer.value = 0xC0; | ||
406 | input.count = 1; | ||
407 | input.pointer = ¶m; | ||
408 | status = acpi_evaluate_integer(handle, "GRDI", &input, &output); | ||
409 | if (ACPI_SUCCESS(status)) | ||
410 | *value = output; | ||
411 | return status; | ||
412 | } | ||
413 | |||
414 | static acpi_status cmpc_set_brightness(acpi_handle handle, | ||
415 | unsigned long long value) | ||
416 | { | ||
417 | union acpi_object param[2]; | ||
418 | struct acpi_object_list input; | ||
419 | acpi_status status; | ||
420 | unsigned long long output; | ||
421 | |||
422 | param[0].type = ACPI_TYPE_INTEGER; | ||
423 | param[0].integer.value = 0xC0; | ||
424 | param[1].type = ACPI_TYPE_INTEGER; | ||
425 | param[1].integer.value = value; | ||
426 | input.count = 2; | ||
427 | input.pointer = param; | ||
428 | status = acpi_evaluate_integer(handle, "GWRI", &input, &output); | ||
429 | return status; | ||
430 | } | ||
431 | |||
432 | static int cmpc_bl_get_brightness(struct backlight_device *bd) | ||
433 | { | ||
434 | acpi_status status; | ||
435 | acpi_handle handle; | ||
436 | unsigned long long brightness; | ||
437 | |||
438 | handle = bl_get_data(bd); | ||
439 | status = cmpc_get_brightness(handle, &brightness); | ||
440 | if (ACPI_SUCCESS(status)) | ||
441 | return brightness; | ||
442 | else | ||
443 | return -1; | ||
444 | } | ||
445 | |||
446 | static int cmpc_bl_update_status(struct backlight_device *bd) | ||
447 | { | ||
448 | acpi_status status; | ||
449 | acpi_handle handle; | ||
450 | |||
451 | handle = bl_get_data(bd); | ||
452 | status = cmpc_set_brightness(handle, bd->props.brightness); | ||
453 | if (ACPI_SUCCESS(status)) | ||
454 | return 0; | ||
455 | else | ||
456 | return -1; | ||
457 | } | ||
458 | |||
459 | static const struct backlight_ops cmpc_bl_ops = { | ||
460 | .get_brightness = cmpc_bl_get_brightness, | ||
461 | .update_status = cmpc_bl_update_status | ||
462 | }; | ||
463 | |||
464 | static int cmpc_bl_add(struct acpi_device *acpi) | ||
465 | { | ||
466 | struct backlight_properties props; | ||
467 | struct backlight_device *bd; | ||
468 | |||
469 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
470 | props.max_brightness = 7; | ||
471 | bd = backlight_device_register("cmpc_bl", &acpi->dev, acpi->handle, | ||
472 | &cmpc_bl_ops, &props); | ||
473 | if (IS_ERR(bd)) | ||
474 | return PTR_ERR(bd); | ||
475 | dev_set_drvdata(&acpi->dev, bd); | ||
476 | return 0; | ||
477 | } | ||
478 | |||
479 | static int cmpc_bl_remove(struct acpi_device *acpi, int type) | ||
480 | { | ||
481 | struct backlight_device *bd; | ||
482 | |||
483 | bd = dev_get_drvdata(&acpi->dev); | ||
484 | backlight_device_unregister(bd); | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | static const struct acpi_device_id cmpc_bl_device_ids[] = { | ||
489 | {CMPC_BL_HID, 0}, | ||
490 | {"", 0} | ||
491 | }; | ||
492 | |||
493 | static struct acpi_driver cmpc_bl_acpi_driver = { | ||
494 | .owner = THIS_MODULE, | ||
495 | .name = "cmpc", | ||
496 | .class = "cmpc", | ||
497 | .ids = cmpc_bl_device_ids, | ||
498 | .ops = { | ||
499 | .add = cmpc_bl_add, | ||
500 | .remove = cmpc_bl_remove | ||
501 | } | ||
502 | }; | ||
503 | |||
504 | |||
505 | /* | ||
506 | * Extra keys code. | ||
507 | */ | ||
508 | static int cmpc_keys_codes[] = { | ||
509 | KEY_UNKNOWN, | ||
510 | KEY_WLAN, | ||
511 | KEY_SWITCHVIDEOMODE, | ||
512 | KEY_BRIGHTNESSDOWN, | ||
513 | KEY_BRIGHTNESSUP, | ||
514 | KEY_VENDOR, | ||
515 | KEY_UNKNOWN, | ||
516 | KEY_CAMERA, | ||
517 | KEY_BACK, | ||
518 | KEY_FORWARD, | ||
519 | KEY_MAX | ||
520 | }; | ||
521 | |||
522 | static void cmpc_keys_handler(struct acpi_device *dev, u32 event) | ||
523 | { | ||
524 | struct input_dev *inputdev; | ||
525 | int code = KEY_MAX; | ||
526 | |||
527 | if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) | ||
528 | code = cmpc_keys_codes[event & 0x0F]; | ||
529 | inputdev = dev_get_drvdata(&dev->dev);; | ||
530 | input_report_key(inputdev, code, !(event & 0x10)); | ||
531 | } | ||
532 | |||
533 | static void cmpc_keys_idev_init(struct input_dev *inputdev) | ||
534 | { | ||
535 | int i; | ||
536 | |||
537 | set_bit(EV_KEY, inputdev->evbit); | ||
538 | for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++) | ||
539 | set_bit(cmpc_keys_codes[i], inputdev->keybit); | ||
540 | } | ||
541 | |||
542 | static int cmpc_keys_add(struct acpi_device *acpi) | ||
543 | { | ||
544 | return cmpc_add_acpi_notify_device(acpi, "cmpc_keys", | ||
545 | cmpc_keys_idev_init); | ||
546 | } | ||
547 | |||
548 | static int cmpc_keys_remove(struct acpi_device *acpi, int type) | ||
549 | { | ||
550 | return cmpc_remove_acpi_notify_device(acpi); | ||
551 | } | ||
552 | |||
553 | static const struct acpi_device_id cmpc_keys_device_ids[] = { | ||
554 | {CMPC_KEYS_HID, 0}, | ||
555 | {"", 0} | ||
556 | }; | ||
557 | |||
558 | static struct acpi_driver cmpc_keys_acpi_driver = { | ||
559 | .owner = THIS_MODULE, | ||
560 | .name = "cmpc_keys", | ||
561 | .class = "cmpc_keys", | ||
562 | .ids = cmpc_keys_device_ids, | ||
563 | .ops = { | ||
564 | .add = cmpc_keys_add, | ||
565 | .remove = cmpc_keys_remove, | ||
566 | .notify = cmpc_keys_handler, | ||
567 | } | ||
568 | }; | ||
569 | |||
570 | |||
571 | /* | ||
572 | * General init/exit code. | ||
573 | */ | ||
574 | |||
575 | static int cmpc_init(void) | ||
576 | { | ||
577 | int r; | ||
578 | |||
579 | r = acpi_bus_register_driver(&cmpc_keys_acpi_driver); | ||
580 | if (r) | ||
581 | goto failed_keys; | ||
582 | |||
583 | r = acpi_bus_register_driver(&cmpc_bl_acpi_driver); | ||
584 | if (r) | ||
585 | goto failed_bl; | ||
586 | |||
587 | r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver); | ||
588 | if (r) | ||
589 | goto failed_tablet; | ||
590 | |||
591 | r = acpi_bus_register_driver(&cmpc_accel_acpi_driver); | ||
592 | if (r) | ||
593 | goto failed_accel; | ||
594 | |||
595 | return r; | ||
596 | |||
597 | failed_accel: | ||
598 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); | ||
599 | |||
600 | failed_tablet: | ||
601 | acpi_bus_unregister_driver(&cmpc_bl_acpi_driver); | ||
602 | |||
603 | failed_bl: | ||
604 | acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); | ||
605 | |||
606 | failed_keys: | ||
607 | return r; | ||
608 | } | ||
609 | |||
610 | static void cmpc_exit(void) | ||
611 | { | ||
612 | acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); | ||
613 | acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); | ||
614 | acpi_bus_unregister_driver(&cmpc_bl_acpi_driver); | ||
615 | acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); | ||
616 | } | ||
617 | |||
618 | module_init(cmpc_init); | ||
619 | module_exit(cmpc_exit); | ||
620 | |||
621 | static const struct acpi_device_id cmpc_device_ids[] = { | ||
622 | {CMPC_ACCEL_HID, 0}, | ||
623 | {CMPC_TABLET_HID, 0}, | ||
624 | {CMPC_BL_HID, 0}, | ||
625 | {CMPC_KEYS_HID, 0}, | ||
626 | {"", 0} | ||
627 | }; | ||
628 | |||
629 | MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); | ||