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