classmate-laptop: add support for Classmate PC ACPI devices
[deliverable/linux.git] / drivers / platform / x86 / classmate-laptop.c
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 = &param;
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 = &param;
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);
This page took 0.069248 seconds and 5 git commands to generate.