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