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#include <linux/rfkill.h> 28 29MODULE_LICENSE("GPL"); 30 31 32struct cmpc_accel { 33 int sensitivity; 34}; 35 36#define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 37 38 39#define CMPC_ACCEL_HID "ACCE0000" 40#define CMPC_TABLET_HID "TBLT0000" 41#define CMPC_IPML_HID "IPML200" 42#define CMPC_KEYS_HID "FnBT0000" 43 44/* 45 * Generic input device code. 46 */ 47 48typedef void (*input_device_init)(struct input_dev *dev); 49 50static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, 51 input_device_init idev_init) 52{ 53 struct input_dev *inputdev; 54 int error; 55 56 inputdev = input_allocate_device(); 57 if (!inputdev) 58 return -ENOMEM; 59 inputdev->name = name; 60 inputdev->dev.parent = &acpi->dev; 61 idev_init(inputdev); 62 error = input_register_device(inputdev); 63 if (error) { 64 input_free_device(inputdev); 65 return error; 66 } 67 dev_set_drvdata(&acpi->dev, inputdev); 68 return 0; 69} 70 71static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) 72{ 73 struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); 74 input_unregister_device(inputdev); 75 return 0; 76} 77 78/* 79 * Accelerometer code. 80 */ 81static acpi_status cmpc_start_accel(acpi_handle handle) 82{ 83 union acpi_object param[2]; 84 struct acpi_object_list input; 85 acpi_status status; 86 87 param[0].type = ACPI_TYPE_INTEGER; 88 param[0].integer.value = 0x3; 89 param[1].type = ACPI_TYPE_INTEGER; 90 input.count = 2; 91 input.pointer = param; 92 status = acpi_evaluate_object(handle, "ACMD", &input, NULL); 93 return status; 94} 95 96static acpi_status cmpc_stop_accel(acpi_handle handle) 97{ 98 union acpi_object param[2]; 99 struct acpi_object_list input; 100 acpi_status status; 101 102 param[0].type = ACPI_TYPE_INTEGER; 103 param[0].integer.value = 0x4; 104 param[1].type = ACPI_TYPE_INTEGER; 105 input.count = 2; 106 input.pointer = param; 107 status = acpi_evaluate_object(handle, "ACMD", &input, NULL); 108 return status; 109} 110 111static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val) 112{ 113 union acpi_object param[2]; 114 struct acpi_object_list input; 115 116 param[0].type = ACPI_TYPE_INTEGER; 117 param[0].integer.value = 0x02; 118 param[1].type = ACPI_TYPE_INTEGER; 119 param[1].integer.value = val; 120 input.count = 2; 121 input.pointer = param; 122 return acpi_evaluate_object(handle, "ACMD", &input, NULL); 123} 124 125static acpi_status cmpc_get_accel(acpi_handle handle, 126 unsigned char *x, 127 unsigned char *y, 128 unsigned char *z) 129{ 130 union acpi_object param[2]; 131 struct acpi_object_list input; 132 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, 0 }; 133 unsigned char *locs; 134 acpi_status status; 135 136 param[0].type = ACPI_TYPE_INTEGER; 137 param[0].integer.value = 0x01; 138 param[1].type = ACPI_TYPE_INTEGER; 139 input.count = 2; 140 input.pointer = param; 141 status = acpi_evaluate_object(handle, "ACMD", &input, &output); 142 if (ACPI_SUCCESS(status)) { 143 union acpi_object *obj; 144 obj = output.pointer; 145 locs = obj->buffer.pointer; 146 *x = locs[0]; 147 *y = locs[1]; 148 *z = locs[2]; 149 kfree(output.pointer); 150 } 151 return status; 152} 153 154static void cmpc_accel_handler(struct acpi_device *dev, u32 event) 155{ 156 if (event == 0x81) { 157 unsigned char x, y, z; 158 acpi_status status; 159 160 status = cmpc_get_accel(dev->handle, &x, &y, &z); 161 if (ACPI_SUCCESS(status)) { 162 struct input_dev *inputdev = dev_get_drvdata(&dev->dev); 163 164 input_report_abs(inputdev, ABS_X, x); 165 input_report_abs(inputdev, ABS_Y, y); 166 input_report_abs(inputdev, ABS_Z, z); 167 input_sync(inputdev); 168 } 169 } 170} 171 172static ssize_t cmpc_accel_sensitivity_show(struct device *dev, 173 struct device_attribute *attr, 174 char *buf) 175{ 176 struct acpi_device *acpi; 177 struct input_dev *inputdev; 178 struct cmpc_accel *accel; 179 180 acpi = to_acpi_device(dev); 181 inputdev = dev_get_drvdata(&acpi->dev); 182 accel = dev_get_drvdata(&inputdev->dev); 183 184 return sprintf(buf, "%d\n", accel->sensitivity); 185} 186 187static ssize_t cmpc_accel_sensitivity_store(struct device *dev, 188 struct device_attribute *attr, 189 const char *buf, size_t count) 190{ 191 struct acpi_device *acpi; 192 struct input_dev *inputdev; 193 struct cmpc_accel *accel; 194 unsigned long sensitivity; 195 int r; 196 197 acpi = to_acpi_device(dev); 198 inputdev = dev_get_drvdata(&acpi->dev); 199 accel = dev_get_drvdata(&inputdev->dev); 200 201 r = strict_strtoul(buf, 0, &sensitivity); 202 if (r) 203 return r; 204 205 accel->sensitivity = sensitivity; 206 cmpc_accel_set_sensitivity(acpi->handle, sensitivity); 207 208 return strnlen(buf, count); 209} 210 211static struct device_attribute cmpc_accel_sensitivity_attr = { 212 .attr = { .name = "sensitivity", .mode = 0660 }, 213 .show = cmpc_accel_sensitivity_show, 214 .store = cmpc_accel_sensitivity_store 215}; 216 217static int cmpc_accel_open(struct input_dev *input) 218{ 219 struct acpi_device *acpi; 220 221 acpi = to_acpi_device(input->dev.parent); 222 if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle))) 223 return 0; 224 return -EIO; 225} 226 227static void cmpc_accel_close(struct input_dev *input) 228{ 229 struct acpi_device *acpi; 230 231 acpi = to_acpi_device(input->dev.parent); 232 cmpc_stop_accel(acpi->handle); 233} 234 235static void cmpc_accel_idev_init(struct input_dev *inputdev) 236{ 237 set_bit(EV_ABS, inputdev->evbit); 238 input_set_abs_params(inputdev, ABS_X, 0, 255, 8, 0); 239 input_set_abs_params(inputdev, ABS_Y, 0, 255, 8, 0); 240 input_set_abs_params(inputdev, ABS_Z, 0, 255, 8, 0); 241 inputdev->open = cmpc_accel_open; 242 inputdev->close = cmpc_accel_close; 243} 244 245static int cmpc_accel_add(struct acpi_device *acpi) 246{ 247 int error; 248 struct input_dev *inputdev; 249 struct cmpc_accel *accel; 250 251 accel = kmalloc(sizeof(*accel), GFP_KERNEL); 252 if (!accel) 253 return -ENOMEM; 254 255 accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; 256 cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity); 257 258 error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr); 259 if (error) 260 goto failed_file; 261 262 error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel", 263 cmpc_accel_idev_init); 264 if (error) 265 goto failed_input; 266 267 inputdev = dev_get_drvdata(&acpi->dev); 268 dev_set_drvdata(&inputdev->dev, accel); 269 270 return 0; 271 272failed_input: 273 device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); 274failed_file: 275 kfree(accel); 276 return error; 277} 278 279static int cmpc_accel_remove(struct acpi_device *acpi, int type) 280{ 281 struct input_dev *inputdev; 282 struct cmpc_accel *accel; 283 284 inputdev = dev_get_drvdata(&acpi->dev); 285 accel = dev_get_drvdata(&inputdev->dev); 286 287 device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); 288 return cmpc_remove_acpi_notify_device(acpi); 289} 290 291static const struct acpi_device_id cmpc_accel_device_ids[] = { 292 {CMPC_ACCEL_HID, 0}, 293 {"", 0} 294}; 295 296static struct acpi_driver cmpc_accel_acpi_driver = { 297 .owner = THIS_MODULE, 298 .name = "cmpc_accel", 299 .class = "cmpc_accel", 300 .ids = cmpc_accel_device_ids, 301 .ops = { 302 .add = cmpc_accel_add, 303 .remove = cmpc_accel_remove, 304 .notify = cmpc_accel_handler, 305 } 306}; 307 308 309/* 310 * Tablet mode code. 311 */ 312static acpi_status cmpc_get_tablet(acpi_handle handle, 313 unsigned long long *value) 314{ 315 union acpi_object param; 316 struct acpi_object_list input; 317 unsigned long long output; 318 acpi_status status; 319 320 param.type = ACPI_TYPE_INTEGER; 321 param.integer.value = 0x01; 322 input.count = 1; 323 input.pointer = ¶m; 324 status = acpi_evaluate_integer(handle, "TCMD", &input, &output); 325 if (ACPI_SUCCESS(status)) 326 *value = output; 327 return status; 328} 329 330static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) 331{ 332 unsigned long long val = 0; 333 struct input_dev *inputdev = dev_get_drvdata(&dev->dev); 334 335 if (event == 0x81) { 336 if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) 337 input_report_switch(inputdev, SW_TABLET_MODE, !val); 338 } 339} 340 341static void cmpc_tablet_idev_init(struct input_dev *inputdev) 342{ 343 unsigned long long val = 0; 344 struct acpi_device *acpi; 345 346 set_bit(EV_SW, inputdev->evbit); 347 set_bit(SW_TABLET_MODE, inputdev->swbit); 348 349 acpi = to_acpi_device(inputdev->dev.parent); 350 if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) 351 input_report_switch(inputdev, SW_TABLET_MODE, !val); 352} 353 354static int cmpc_tablet_add(struct acpi_device *acpi) 355{ 356 return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet", 357 cmpc_tablet_idev_init); 358} 359 360static int cmpc_tablet_remove(struct acpi_device *acpi, int type) 361{ 362 return cmpc_remove_acpi_notify_device(acpi); 363} 364 365static int cmpc_tablet_resume(struct acpi_device *acpi) 366{ 367 struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); 368 unsigned long long val = 0; 369 if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) 370 input_report_switch(inputdev, SW_TABLET_MODE, !val); 371 return 0; 372} 373 374static const struct acpi_device_id cmpc_tablet_device_ids[] = { 375 {CMPC_TABLET_HID, 0}, 376 {"", 0} 377}; 378 379static struct acpi_driver cmpc_tablet_acpi_driver = { 380 .owner = THIS_MODULE, 381 .name = "cmpc_tablet", 382 .class = "cmpc_tablet", 383 .ids = cmpc_tablet_device_ids, 384 .ops = { 385 .add = cmpc_tablet_add, 386 .remove = cmpc_tablet_remove, 387 .resume = cmpc_tablet_resume, 388 .notify = cmpc_tablet_handler, 389 } 390}; 391 392 393/* 394 * Backlight code. 395 */ 396 397static acpi_status cmpc_get_brightness(acpi_handle handle, 398 unsigned long long *value) 399{ 400 union acpi_object param; 401 struct acpi_object_list input; 402 unsigned long long output; 403 acpi_status status; 404 405 param.type = ACPI_TYPE_INTEGER; 406 param.integer.value = 0xC0; 407 input.count = 1; 408 input.pointer = ¶m; 409 status = acpi_evaluate_integer(handle, "GRDI", &input, &output); 410 if (ACPI_SUCCESS(status)) 411 *value = output; 412 return status; 413} 414 415static acpi_status cmpc_set_brightness(acpi_handle handle, 416 unsigned long long value) 417{ 418 union acpi_object param[2]; 419 struct acpi_object_list input; 420 acpi_status status; 421 unsigned long long output; 422 423 param[0].type = ACPI_TYPE_INTEGER; 424 param[0].integer.value = 0xC0; 425 param[1].type = ACPI_TYPE_INTEGER; 426 param[1].integer.value = value; 427 input.count = 2; 428 input.pointer = param; 429 status = acpi_evaluate_integer(handle, "GWRI", &input, &output); 430 return status; 431} 432 433static int cmpc_bl_get_brightness(struct backlight_device *bd) 434{ 435 acpi_status status; 436 acpi_handle handle; 437 unsigned long long brightness; 438 439 handle = bl_get_data(bd); 440 status = cmpc_get_brightness(handle, &brightness); 441 if (ACPI_SUCCESS(status)) 442 return brightness; 443 else 444 return -1; 445} 446 447static int cmpc_bl_update_status(struct backlight_device *bd) 448{ 449 acpi_status status; 450 acpi_handle handle; 451 452 handle = bl_get_data(bd); 453 status = cmpc_set_brightness(handle, bd->props.brightness); 454 if (ACPI_SUCCESS(status)) 455 return 0; 456 else 457 return -1; 458} 459 460static const struct backlight_ops cmpc_bl_ops = { 461 .get_brightness = cmpc_bl_get_brightness, 462 .update_status = cmpc_bl_update_status 463}; 464 465/* 466 * RFKILL code. 467 */ 468 469static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle, 470 unsigned long long *value) 471{ 472 union acpi_object param; 473 struct acpi_object_list input; 474 unsigned long long output; 475 acpi_status status; 476 477 param.type = ACPI_TYPE_INTEGER; 478 param.integer.value = 0xC1; 479 input.count = 1; 480 input.pointer = ¶m; 481 status = acpi_evaluate_integer(handle, "GRDI", &input, &output); 482 if (ACPI_SUCCESS(status)) 483 *value = output; 484 return status; 485} 486 487static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle, 488 unsigned long long value) 489{ 490 union acpi_object param[2]; 491 struct acpi_object_list input; 492 acpi_status status; 493 unsigned long long output; 494 495 param[0].type = ACPI_TYPE_INTEGER; 496 param[0].integer.value = 0xC1; 497 param[1].type = ACPI_TYPE_INTEGER; 498 param[1].integer.value = value; 499 input.count = 2; 500 input.pointer = param; 501 status = acpi_evaluate_integer(handle, "GWRI", &input, &output); 502 return status; 503} 504 505static void cmpc_rfkill_query(struct rfkill *rfkill, void *data) 506{ 507 acpi_status status; 508 acpi_handle handle; 509 unsigned long long state; 510 bool blocked; 511 512 handle = data; 513 status = cmpc_get_rfkill_wlan(handle, &state); 514 if (ACPI_SUCCESS(status)) { 515 blocked = state & 1 ? false : true; 516 rfkill_set_sw_state(rfkill, blocked); 517 } 518} 519 520static int cmpc_rfkill_block(void *data, bool blocked) 521{ 522 acpi_status status; 523 acpi_handle handle; 524 unsigned long long state; 525 526 handle = data; 527 status = cmpc_get_rfkill_wlan(handle, &state); 528 if (ACPI_FAILURE(status)) 529 return -ENODEV; 530 if (blocked) 531 state &= ~1; 532 else 533 state |= 1; 534 status = cmpc_set_rfkill_wlan(handle, state); 535 if (ACPI_FAILURE(status)) 536 return -ENODEV; 537 return 0; 538} 539 540static const struct rfkill_ops cmpc_rfkill_ops = { 541 .query = cmpc_rfkill_query, 542 .set_block = cmpc_rfkill_block, 543}; 544 545/* 546 * Common backlight and rfkill code. 547 */ 548 549struct ipml200_dev { 550 struct backlight_device *bd; 551 struct rfkill *rf; 552}; 553 554static int cmpc_ipml_add(struct acpi_device *acpi) 555{ 556 int retval; 557 struct ipml200_dev *ipml; 558 struct backlight_properties props; 559 560 ipml = kmalloc(sizeof(*ipml), GFP_KERNEL); 561 if (ipml == NULL) 562 return -ENOMEM; 563 564 memset(&props, 0, sizeof(struct backlight_properties)); 565 props.max_brightness = 7; 566 ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev, 567 acpi->handle, &cmpc_bl_ops, 568 &props); 569 if (IS_ERR(ipml->bd)) { 570 retval = PTR_ERR(ipml->bd); 571 goto out_bd; 572 } 573 574 ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN, 575 &cmpc_rfkill_ops, acpi->handle); 576 /* 577 * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV). 578 * This is OK, however, since all other uses of the device will not 579 * derefence it. 580 */ 581 if (ipml->rf) { 582 retval = rfkill_register(ipml->rf); 583 if (retval) { 584 rfkill_destroy(ipml->rf); 585 ipml->rf = NULL; 586 } 587 } 588 589 dev_set_drvdata(&acpi->dev, ipml); 590 return 0; 591 592out_bd: 593 kfree(ipml); 594 return retval; 595} 596 597static int cmpc_ipml_remove(struct acpi_device *acpi, int type) 598{ 599 struct ipml200_dev *ipml; 600 601 ipml = dev_get_drvdata(&acpi->dev); 602 603 backlight_device_unregister(ipml->bd); 604 605 if (ipml->rf) { 606 rfkill_unregister(ipml->rf); 607 rfkill_destroy(ipml->rf); 608 } 609 610 kfree(ipml); 611 612 return 0; 613} 614 615static const struct acpi_device_id cmpc_ipml_device_ids[] = { 616 {CMPC_IPML_HID, 0}, 617 {"", 0} 618}; 619 620static struct acpi_driver cmpc_ipml_acpi_driver = { 621 .owner = THIS_MODULE, 622 .name = "cmpc", 623 .class = "cmpc", 624 .ids = cmpc_ipml_device_ids, 625 .ops = { 626 .add = cmpc_ipml_add, 627 .remove = cmpc_ipml_remove 628 } 629}; 630 631 632/* 633 * Extra keys code. 634 */ 635static int cmpc_keys_codes[] = { 636 KEY_UNKNOWN, 637 KEY_WLAN, 638 KEY_SWITCHVIDEOMODE, 639 KEY_BRIGHTNESSDOWN, 640 KEY_BRIGHTNESSUP, 641 KEY_VENDOR, 642 KEY_UNKNOWN, 643 KEY_CAMERA, 644 KEY_BACK, 645 KEY_FORWARD, 646 KEY_MAX 647}; 648 649static void cmpc_keys_handler(struct acpi_device *dev, u32 event) 650{ 651 struct input_dev *inputdev; 652 int code = KEY_MAX; 653 654 if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) 655 code = cmpc_keys_codes[event & 0x0F]; 656 inputdev = dev_get_drvdata(&dev->dev);; 657 input_report_key(inputdev, code, !(event & 0x10)); 658} 659 660static void cmpc_keys_idev_init(struct input_dev *inputdev) 661{ 662 int i; 663 664 set_bit(EV_KEY, inputdev->evbit); 665 for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++) 666 set_bit(cmpc_keys_codes[i], inputdev->keybit); 667} 668 669static int cmpc_keys_add(struct acpi_device *acpi) 670{ 671 return cmpc_add_acpi_notify_device(acpi, "cmpc_keys", 672 cmpc_keys_idev_init); 673} 674 675static int cmpc_keys_remove(struct acpi_device *acpi, int type) 676{ 677 return cmpc_remove_acpi_notify_device(acpi); 678} 679 680static const struct acpi_device_id cmpc_keys_device_ids[] = { 681 {CMPC_KEYS_HID, 0}, 682 {"", 0} 683}; 684 685static struct acpi_driver cmpc_keys_acpi_driver = { 686 .owner = THIS_MODULE, 687 .name = "cmpc_keys", 688 .class = "cmpc_keys", 689 .ids = cmpc_keys_device_ids, 690 .ops = { 691 .add = cmpc_keys_add, 692 .remove = cmpc_keys_remove, 693 .notify = cmpc_keys_handler, 694 } 695}; 696 697 698/* 699 * General init/exit code. 700 */ 701 702static int cmpc_init(void) 703{ 704 int r; 705 706 r = acpi_bus_register_driver(&cmpc_keys_acpi_driver); 707 if (r) 708 goto failed_keys; 709 710 r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver); 711 if (r) 712 goto failed_bl; 713 714 r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver); 715 if (r) 716 goto failed_tablet; 717 718 r = acpi_bus_register_driver(&cmpc_accel_acpi_driver); 719 if (r) 720 goto failed_accel; 721 722 return r; 723 724failed_accel: 725 acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); 726 727failed_tablet: 728 acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); 729 730failed_bl: 731 acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); 732 733failed_keys: 734 return r; 735} 736 737static void cmpc_exit(void) 738{ 739 acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); 740 acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); 741 acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); 742 acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); 743} 744 745module_init(cmpc_init); 746module_exit(cmpc_exit); 747 748static const struct acpi_device_id cmpc_device_ids[] = { 749 {CMPC_ACCEL_HID, 0}, 750 {CMPC_TABLET_HID, 0}, 751 {CMPC_IPML_HID, 0}, 752 {CMPC_KEYS_HID, 0}, 753 {"", 0} 754}; 755 756MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); 757