diff options
| author | Corentin Chary <corentincj@iksaif.net> | 2007-01-26 08:04:35 -0500 |
|---|---|---|
| committer | Len Brown <len.brown@intel.com> | 2007-01-30 01:37:00 -0500 |
| commit | be18cdabb8ed40ff4b8a240e0d6f4e6c30ff866d (patch) | |
| tree | d0ca8b148430db06d3b428a3cdf905c4434fab2e | |
| parent | 85091b718969be7b8e6f795af7e264b8afcd7a6d (diff) | |
asus-laptop: add led support
Add led support, using generic led class. Thomas Tuttle's patch
<http://lkml.org/lkml/2006/7/6/247> was very usefull. We use
hotk->status to store led status because it's very hard to find
acpi method to get the right status... To reduce the code,
I use a lot of macro (ASUS_LED, ASUS_LED_REGISTER, etc ...),
because the code is the same for all leds ...
Signed-off-by: Corentin Chary <corentincj@iksaif.net>
Signed-off-by: Len Brown <len.brown@intel.com>
| -rw-r--r-- | drivers/misc/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/misc/asus-laptop.c | 145 |
2 files changed, 146 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4b1e367e8feb..87e1db8ffd47 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig | |||
| @@ -74,6 +74,7 @@ config ASUS_LAPTOP | |||
| 74 | depends on X86 | 74 | depends on X86 |
| 75 | depends on ACPI | 75 | depends on ACPI |
| 76 | depends on EXPERIMENTAL && !ACPI_ASUS | 76 | depends on EXPERIMENTAL && !ACPI_ASUS |
| 77 | depends on LEDS_CLASS | ||
| 77 | ---help--- | 78 | ---help--- |
| 78 | This is the new Linux driver for Asus laptops. It may also support some | 79 | This is the new Linux driver for Asus laptops. It may also support some |
| 79 | MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate | 80 | MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate |
diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c index 959b20f5604a..d0d5ee9b358f 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c | |||
| @@ -40,6 +40,7 @@ | |||
| 40 | #include <linux/types.h> | 40 | #include <linux/types.h> |
| 41 | #include <linux/err.h> | 41 | #include <linux/err.h> |
| 42 | #include <linux/proc_fs.h> | 42 | #include <linux/proc_fs.h> |
| 43 | #include <linux/leds.h> | ||
| 43 | #include <linux/platform_device.h> | 44 | #include <linux/platform_device.h> |
| 44 | #include <acpi/acpi_drivers.h> | 45 | #include <acpi/acpi_drivers.h> |
| 45 | #include <acpi/acpi_bus.h> | 46 | #include <acpi/acpi_bus.h> |
| @@ -54,6 +55,14 @@ | |||
| 54 | #define ASUS_HOTK_FILE "asus-laptop" | 55 | #define ASUS_HOTK_FILE "asus-laptop" |
| 55 | #define ASUS_HOTK_PREFIX "\\_SB.ATKD." | 56 | #define ASUS_HOTK_PREFIX "\\_SB.ATKD." |
| 56 | 57 | ||
| 58 | /* | ||
| 59 | * Flags for hotk status | ||
| 60 | */ | ||
| 61 | #define MLED_ON 0x04 //mail LED | ||
| 62 | #define TLED_ON 0x08 //touchpad LED | ||
| 63 | #define RLED_ON 0x10 //Record LED | ||
| 64 | #define PLED_ON 0x20 //Phone LED | ||
| 65 | |||
| 57 | #define ASUS_LOG ASUS_HOTK_FILE ": " | 66 | #define ASUS_LOG ASUS_HOTK_FILE ": " |
| 58 | #define ASUS_ERR KERN_ERR ASUS_LOG | 67 | #define ASUS_ERR KERN_ERR ASUS_LOG |
| 59 | #define ASUS_WARNING KERN_WARNING ASUS_LOG | 68 | #define ASUS_WARNING KERN_WARNING ASUS_LOG |
| @@ -69,6 +78,12 @@ MODULE_LICENSE("GPL"); | |||
| 69 | static acpi_handle object##_handle = NULL; \ | 78 | static acpi_handle object##_handle = NULL; \ |
| 70 | static char *object##_paths[] = { paths } | 79 | static char *object##_paths[] = { paths } |
| 71 | 80 | ||
| 81 | /* LED */ | ||
| 82 | ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED"); | ||
| 83 | ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED"); | ||
| 84 | ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */ | ||
| 85 | ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */ | ||
| 86 | |||
| 72 | /* | 87 | /* |
| 73 | * This is the main structure, we can use it to store anything interesting | 88 | * This is the main structure, we can use it to store anything interesting |
| 74 | * about the hotk device | 89 | * about the hotk device |
| @@ -106,6 +121,28 @@ static struct acpi_driver asus_hotk_driver = { | |||
| 106 | }, | 121 | }, |
| 107 | }; | 122 | }; |
| 108 | 123 | ||
| 124 | /* These functions actually update the LED's, and are called from a | ||
| 125 | * workqueue. By doing this as separate work rather than when the LED | ||
| 126 | * subsystem asks, we avoid messing with the Asus ACPI stuff during a | ||
| 127 | * potentially bad time, such as a timer interrupt. */ | ||
| 128 | static struct workqueue_struct *led_workqueue; | ||
| 129 | |||
| 130 | #define ASUS_LED(object, ledname) \ | ||
| 131 | static void object##_led_set(struct led_classdev *led_cdev, \ | ||
| 132 | enum led_brightness value); \ | ||
| 133 | static void object##_led_update(struct work_struct *ignored); \ | ||
| 134 | static int object##_led_wk; \ | ||
| 135 | DECLARE_WORK(object##_led_work, object##_led_update); \ | ||
| 136 | static struct led_classdev object##_led = { \ | ||
| 137 | .name = "asus:" ledname, \ | ||
| 138 | .brightness_set = object##_led_set, \ | ||
| 139 | } | ||
| 140 | |||
| 141 | ASUS_LED(mled, "mail"); | ||
| 142 | ASUS_LED(tled, "touchpad"); | ||
| 143 | ASUS_LED(rled, "record"); | ||
| 144 | ASUS_LED(pled, "phone"); | ||
| 145 | |||
| 109 | /* | 146 | /* |
| 110 | * This function evaluates an ACPI method, given an int as parameter, the | 147 | * This function evaluates an ACPI method, given an int as parameter, the |
| 111 | * method is searched within the scope of the handle, can be NULL. The output | 148 | * method is searched within the scope of the handle, can be NULL. The output |
| @@ -144,6 +181,43 @@ static int read_acpi_int(acpi_handle handle, const char *method, int *val, | |||
| 144 | return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); | 181 | return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); |
| 145 | } | 182 | } |
| 146 | 183 | ||
| 184 | /* Generic LED functions */ | ||
| 185 | static int read_status(int mask) | ||
| 186 | { | ||
| 187 | return (hotk->status & mask) ? 1 : 0; | ||
| 188 | } | ||
| 189 | |||
| 190 | static void write_status(acpi_handle handle, int out, int mask, | ||
| 191 | int invert) | ||
| 192 | { | ||
| 193 | hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask); | ||
| 194 | |||
| 195 | if (invert) /* invert target value */ | ||
| 196 | out = !out & 0x1; | ||
| 197 | |||
| 198 | if (handle && !write_acpi_int(handle, NULL, out, NULL)) | ||
| 199 | printk(ASUS_WARNING " write failed\n"); | ||
| 200 | } | ||
| 201 | |||
| 202 | /* /sys/class/led handlers */ | ||
| 203 | #define ASUS_LED_HANDLER(object, mask, invert) \ | ||
| 204 | static void object##_led_set(struct led_classdev *led_cdev, \ | ||
| 205 | enum led_brightness value) \ | ||
| 206 | { \ | ||
| 207 | object##_led_wk = value; \ | ||
| 208 | queue_work(led_workqueue, &object##_led_work); \ | ||
| 209 | } \ | ||
| 210 | static void object##_led_update(struct work_struct *ignored) \ | ||
| 211 | { \ | ||
| 212 | int value = object##_led_wk; \ | ||
| 213 | write_status(object##_set_handle, value, (mask), (invert)); \ | ||
| 214 | } | ||
| 215 | |||
| 216 | ASUS_LED_HANDLER(mled, MLED_ON, 1); | ||
| 217 | ASUS_LED_HANDLER(pled, PLED_ON, 0); | ||
| 218 | ASUS_LED_HANDLER(rled, RLED_ON, 0); | ||
| 219 | ASUS_LED_HANDLER(tled, TLED_ON, 0); | ||
| 220 | |||
| 147 | /* | 221 | /* |
| 148 | * Platform device handlers | 222 | * Platform device handlers |
| 149 | */ | 223 | */ |
| @@ -361,6 +435,11 @@ static int asus_hotk_get_info(void) | |||
| 361 | if(*string) | 435 | if(*string) |
| 362 | printk(ASUS_NOTICE " %s model detected\n", string); | 436 | printk(ASUS_NOTICE " %s model detected\n", string); |
| 363 | 437 | ||
| 438 | ASUS_HANDLE_INIT(mled_set); | ||
| 439 | ASUS_HANDLE_INIT(tled_set); | ||
| 440 | ASUS_HANDLE_INIT(rled_set); | ||
| 441 | ASUS_HANDLE_INIT(pled_set); | ||
| 442 | |||
| 364 | kfree(model); | 443 | kfree(model); |
| 365 | 444 | ||
| 366 | return AE_OK; | 445 | return AE_OK; |
| @@ -452,8 +531,25 @@ static int asus_hotk_remove(struct acpi_device *device, int type) | |||
| 452 | return 0; | 531 | return 0; |
| 453 | } | 532 | } |
| 454 | 533 | ||
| 534 | #define ASUS_LED_UNREGISTER(object) \ | ||
| 535 | if(object##_led.class_dev \ | ||
| 536 | && !IS_ERR(object##_led.class_dev)) \ | ||
| 537 | led_classdev_unregister(&object##_led) | ||
| 538 | |||
| 539 | static void asus_led_exit(void) | ||
| 540 | { | ||
| 541 | ASUS_LED_UNREGISTER(mled); | ||
| 542 | ASUS_LED_UNREGISTER(tled); | ||
| 543 | ASUS_LED_UNREGISTER(pled); | ||
| 544 | ASUS_LED_UNREGISTER(rled); | ||
| 545 | |||
| 546 | destroy_workqueue(led_workqueue); | ||
| 547 | } | ||
| 548 | |||
| 455 | static void __exit asus_laptop_exit(void) | 549 | static void __exit asus_laptop_exit(void) |
| 456 | { | 550 | { |
| 551 | asus_led_exit(); | ||
| 552 | |||
| 457 | acpi_bus_unregister_driver(&asus_hotk_driver); | 553 | acpi_bus_unregister_driver(&asus_hotk_driver); |
| 458 | sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group); | 554 | sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group); |
| 459 | platform_device_unregister(asuspf_device); | 555 | platform_device_unregister(asuspf_device); |
| @@ -462,8 +558,48 @@ static void __exit asus_laptop_exit(void) | |||
| 462 | kfree(asus_info); | 558 | kfree(asus_info); |
| 463 | } | 559 | } |
| 464 | 560 | ||
| 561 | static int asus_led_register(acpi_handle handle, | ||
| 562 | struct led_classdev * ldev, | ||
| 563 | struct device * dev) | ||
| 564 | { | ||
| 565 | if(!handle) | ||
| 566 | return 0; | ||
| 567 | |||
| 568 | return led_classdev_register(dev, ldev); | ||
| 569 | } | ||
| 570 | #define ASUS_LED_REGISTER(object, device) \ | ||
| 571 | asus_led_register(object##_set_handle, &object##_led, device) | ||
| 572 | |||
| 573 | static int asus_led_init(struct device * dev) | ||
| 574 | { | ||
| 575 | int rv; | ||
| 576 | |||
| 577 | rv = ASUS_LED_REGISTER(mled, dev); | ||
| 578 | if(rv) | ||
| 579 | return rv; | ||
| 580 | |||
| 581 | rv = ASUS_LED_REGISTER(tled, dev); | ||
| 582 | if(rv) | ||
| 583 | return rv; | ||
| 584 | |||
| 585 | rv = ASUS_LED_REGISTER(rled, dev); | ||
| 586 | if(rv) | ||
| 587 | return rv; | ||
| 588 | |||
| 589 | rv = ASUS_LED_REGISTER(pled, dev); | ||
| 590 | if(rv) | ||
| 591 | return rv; | ||
| 592 | |||
| 593 | led_workqueue = create_singlethread_workqueue("led_workqueue"); | ||
| 594 | if(!led_workqueue) | ||
| 595 | return -ENOMEM; | ||
| 596 | |||
| 597 | return 0; | ||
| 598 | } | ||
| 599 | |||
| 465 | static int __init asus_laptop_init(void) | 600 | static int __init asus_laptop_init(void) |
| 466 | { | 601 | { |
| 602 | struct device *dev; | ||
| 467 | int result; | 603 | int result; |
| 468 | 604 | ||
| 469 | if (acpi_disabled) | 605 | if (acpi_disabled) |
| @@ -490,6 +626,12 @@ static int __init asus_laptop_init(void) | |||
| 490 | return -ENODEV; | 626 | return -ENODEV; |
| 491 | } | 627 | } |
| 492 | 628 | ||
| 629 | dev = acpi_get_physical_device(hotk->device->handle); | ||
| 630 | |||
| 631 | result = asus_led_init(dev); | ||
| 632 | if(result) | ||
| 633 | goto fail_led; | ||
| 634 | |||
| 493 | /* Register platform stuff */ | 635 | /* Register platform stuff */ |
| 494 | result = platform_driver_register(&asuspf_driver); | 636 | result = platform_driver_register(&asuspf_driver); |
| 495 | if (result) | 637 | if (result) |
| @@ -522,6 +664,9 @@ fail_platform_device1: | |||
| 522 | platform_driver_unregister(&asuspf_driver); | 664 | platform_driver_unregister(&asuspf_driver); |
| 523 | 665 | ||
| 524 | fail_platform_driver: | 666 | fail_platform_driver: |
| 667 | asus_led_exit(); | ||
| 668 | |||
| 669 | fail_led: | ||
| 525 | 670 | ||
| 526 | return result; | 671 | return result; |
| 527 | } | 672 | } |
