diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4b1e367e8febadf239a877c5fc545a073ed0efb2..87e1db8ffd47b1e653744d0b7b47bced86c95364 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -74,6 +74,7 @@ config ASUS_LAPTOP depends on X86 depends on ACPI depends on EXPERIMENTAL && !ACPI_ASUS + depends on LEDS_CLASS ---help--- This is the new Linux driver for Asus laptops. It may also support some 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 959b20f5604a2e1791d3947822b182b4e2b2d269..d0d5ee9b358f239a2db3e03a33e485e80bae7304 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,14 @@ #define ASUS_HOTK_FILE "asus-laptop" #define ASUS_HOTK_PREFIX "\\_SB.ATKD." +/* + * Flags for hotk status + */ +#define MLED_ON 0x04 //mail LED +#define TLED_ON 0x08 //touchpad LED +#define RLED_ON 0x10 //Record LED +#define PLED_ON 0x20 //Phone LED + #define ASUS_LOG ASUS_HOTK_FILE ": " #define ASUS_ERR KERN_ERR ASUS_LOG #define ASUS_WARNING KERN_WARNING ASUS_LOG @@ -69,6 +78,12 @@ MODULE_LICENSE("GPL"); static acpi_handle object##_handle = NULL; \ static char *object##_paths[] = { paths } +/* LED */ +ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED"); +ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED"); +ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */ +ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */ + /* * This is the main structure, we can use it to store anything interesting * about the hotk device @@ -106,6 +121,28 @@ static struct acpi_driver asus_hotk_driver = { }, }; +/* These functions actually update the LED's, and are called from a + * workqueue. By doing this as separate work rather than when the LED + * subsystem asks, we avoid messing with the Asus ACPI stuff during a + * potentially bad time, such as a timer interrupt. */ +static struct workqueue_struct *led_workqueue; + +#define ASUS_LED(object, ledname) \ + static void object##_led_set(struct led_classdev *led_cdev, \ + enum led_brightness value); \ + static void object##_led_update(struct work_struct *ignored); \ + static int object##_led_wk; \ + DECLARE_WORK(object##_led_work, object##_led_update); \ + static struct led_classdev object##_led = { \ + .name = "asus:" ledname, \ + .brightness_set = object##_led_set, \ + } + +ASUS_LED(mled, "mail"); +ASUS_LED(tled, "touchpad"); +ASUS_LED(rled, "record"); +ASUS_LED(pled, "phone"); + /* * This function evaluates an ACPI method, given an int as parameter, the * 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, return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); } +/* Generic LED functions */ +static int read_status(int mask) +{ + return (hotk->status & mask) ? 1 : 0; +} + +static void write_status(acpi_handle handle, int out, int mask, + int invert) +{ + hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask); + + if (invert) /* invert target value */ + out = !out & 0x1; + + if (handle && !write_acpi_int(handle, NULL, out, NULL)) + printk(ASUS_WARNING " write failed\n"); +} + +/* /sys/class/led handlers */ +#define ASUS_LED_HANDLER(object, mask, invert) \ + static void object##_led_set(struct led_classdev *led_cdev, \ + enum led_brightness value) \ + { \ + object##_led_wk = value; \ + queue_work(led_workqueue, &object##_led_work); \ + } \ + static void object##_led_update(struct work_struct *ignored) \ + { \ + int value = object##_led_wk; \ + write_status(object##_set_handle, value, (mask), (invert)); \ + } + +ASUS_LED_HANDLER(mled, MLED_ON, 1); +ASUS_LED_HANDLER(pled, PLED_ON, 0); +ASUS_LED_HANDLER(rled, RLED_ON, 0); +ASUS_LED_HANDLER(tled, TLED_ON, 0); + /* * Platform device handlers */ @@ -361,6 +435,11 @@ static int asus_hotk_get_info(void) if(*string) printk(ASUS_NOTICE " %s model detected\n", string); + ASUS_HANDLE_INIT(mled_set); + ASUS_HANDLE_INIT(tled_set); + ASUS_HANDLE_INIT(rled_set); + ASUS_HANDLE_INIT(pled_set); + kfree(model); return AE_OK; @@ -452,8 +531,25 @@ static int asus_hotk_remove(struct acpi_device *device, int type) return 0; } +#define ASUS_LED_UNREGISTER(object) \ + if(object##_led.class_dev \ + && !IS_ERR(object##_led.class_dev)) \ + led_classdev_unregister(&object##_led) + +static void asus_led_exit(void) +{ + ASUS_LED_UNREGISTER(mled); + ASUS_LED_UNREGISTER(tled); + ASUS_LED_UNREGISTER(pled); + ASUS_LED_UNREGISTER(rled); + + destroy_workqueue(led_workqueue); +} + static void __exit asus_laptop_exit(void) { + asus_led_exit(); + acpi_bus_unregister_driver(&asus_hotk_driver); sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group); platform_device_unregister(asuspf_device); @@ -462,8 +558,48 @@ static void __exit asus_laptop_exit(void) kfree(asus_info); } +static int asus_led_register(acpi_handle handle, + struct led_classdev * ldev, + struct device * dev) +{ + if(!handle) + return 0; + + return led_classdev_register(dev, ldev); +} +#define ASUS_LED_REGISTER(object, device) \ + asus_led_register(object##_set_handle, &object##_led, device) + +static int asus_led_init(struct device * dev) +{ + int rv; + + rv = ASUS_LED_REGISTER(mled, dev); + if(rv) + return rv; + + rv = ASUS_LED_REGISTER(tled, dev); + if(rv) + return rv; + + rv = ASUS_LED_REGISTER(rled, dev); + if(rv) + return rv; + + rv = ASUS_LED_REGISTER(pled, dev); + if(rv) + return rv; + + led_workqueue = create_singlethread_workqueue("led_workqueue"); + if(!led_workqueue) + return -ENOMEM; + + return 0; +} + static int __init asus_laptop_init(void) { + struct device *dev; int result; if (acpi_disabled) @@ -490,6 +626,12 @@ static int __init asus_laptop_init(void) return -ENODEV; } + dev = acpi_get_physical_device(hotk->device->handle); + + result = asus_led_init(dev); + if(result) + goto fail_led; + /* Register platform stuff */ result = platform_driver_register(&asuspf_driver); if (result) @@ -522,6 +664,9 @@ static int __init asus_laptop_init(void) platform_driver_unregister(&asuspf_driver); fail_platform_driver: + asus_led_exit(); + +fail_led: return result; }