/* * dell_led.c - Dell LED Driver * * Copyright (C) 2010 Dell Inc. * Louis Davis * Jim Dailey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation. * */ #include #include #include MODULE_AUTHOR("Louis Davis/Jim Dailey"); MODULE_DESCRIPTION("Dell LED Control Driver"); MODULE_LICENSE("GPL"); #define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); // Error Result Codes: #define INVALID_DEVICE_ID 250 #define INVALID_PARAMETER 251 #define INVALID_BUFFER 252 #define INTERFACE_ERROR 253 #define UNSUPPORTED_COMMAND 254 #define UNSPECIFIED_ERROR 255 // Devide ID #define DEVICE_ID_PANEL_BACK 1 // LED Commands #define CMD_LED_ON 16 #define CMD_LED_OFF 17 #define CMD_LED_BLINK 18 struct bios_args { unsigned char Length; unsigned char ResultCode; unsigned char DeviceId; unsigned char Command; unsigned char OnTime; unsigned char OffTime; unsigned char Reserved[122]; }; static int dell_led_perform_fn(u8 Length, u8 ResultCode, u8 DeviceId, u8 Command, u8 OnTime, u8 OffTime) { struct bios_args bios_return; acpi_status status; union acpi_object *obj; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer input; struct bios_args args; args.Length = Length; args.ResultCode = ResultCode; args.DeviceId = DeviceId; args.Command = Command; args.OnTime = OnTime; args.OffTime = OffTime; input.length = sizeof(struct bios_args); input.pointer = &args; status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 1, 1, &input, &output); obj = output.pointer; if (!obj || obj->type != ACPI_TYPE_BUFFER) return -EINVAL; bios_return = *((struct bios_args *)obj->buffer.pointer); // free the output ACPI object allocated by ACPI driver kfree(obj); return bios_return.ResultCode; } static void led_on(void) { dell_led_perform_fn(3, // Length of command INTERFACE_ERROR, // Init result code to INTERFACE_ERROR DEVICE_ID_PANEL_BACK, // Device ID CMD_LED_ON, // Command 0, // not used 0); // not used } static void led_off(void) { dell_led_perform_fn(3, // Length of command INTERFACE_ERROR, // Init result code to INTERFACE_ERROR DEVICE_ID_PANEL_BACK, // Device ID CMD_LED_OFF, // Command 0, // not used 0); // not used } static void led_blink(unsigned char on_eighths, unsigned char off_eighths) { dell_led_perform_fn(5, // Length of command INTERFACE_ERROR, // Init result code to INTERFACE_ERROR DEVICE_ID_PANEL_BACK, // Device ID CMD_LED_BLINK, // Command on_eighths, // blink on in eigths of a second off_eighths); // blink off in eights of a second } static void dell_led_set(struct led_classdev *led_cdev, enum led_brightness value) { if (value == LED_OFF) { led_off(); } else { led_on(); } } static int dell_led_blink(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { unsigned long on_eighths; unsigned long off_eighths; // The Dell LED delay is based on 125ms intervals. // Need to round up to next interval. on_eighths = (*delay_on + 124) / 125; if (0 == on_eighths) on_eighths = 1; if (on_eighths > 255) on_eighths = 255; *delay_on = on_eighths * 125; off_eighths = (*delay_off + 124) / 125; if (0 == off_eighths) off_eighths = 1; if (off_eighths > 255) off_eighths = 255; *delay_off = off_eighths * 125; led_blink(on_eighths, off_eighths); return 0; } static struct led_classdev dell_led = { .name = "dell::lid", .brightness = LED_OFF, .max_brightness = 1, .brightness_set = dell_led_set, .blink_set = dell_led_blink, .flags = LED_CORE_SUSPENDRESUME, }; static int __init dell_led_probe(struct platform_device *pdev) { return led_classdev_register(&pdev->dev, &dell_led); } static int dell_led_remove(struct platform_device *pdev) { led_classdev_unregister(&dell_led); return 0; } static struct platform_driver dell_led_driver = { .probe = dell_led_probe, .remove = dell_led_remove, .driver = { .name = KBUILD_MODNAME, .owner = THIS_MODULE, }, }; static struct platform_device *pdev; static int __init dell_led_init(void) { int error = 0; if (!wmi_has_guid(DELL_LED_BIOS_GUID)) { printk(KERN_DEBUG KBUILD_MODNAME ": could not find: DELL_LED_BIOS_GUID\n"); return -ENODEV; } led_off(); error = platform_driver_register(&dell_led_driver); if ( error < 0 ) return error; pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); if (IS_ERR(pdev)) { error = PTR_ERR(pdev); platform_driver_unregister(&dell_led_driver); } return error; } static void __exit dell_led_exit(void) { platform_driver_unregister(&dell_led_driver); platform_device_unregister(pdev); led_off(); } module_init(dell_led_init); module_exit(dell_led_exit);