diff options
Diffstat (limited to 'drivers/misc/asus-laptop.c')
-rw-r--r-- | drivers/misc/asus-laptop.c | 145 |
1 files changed, 145 insertions, 0 deletions
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 | } |