Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Copyright (C) 2010 Dell Inc.
0003  * Louis Davis <louis_davis@dell.com>
0004  * Jim Dailey <jim_dailey@dell.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as
0008  * published by the Free Software Foundation.
0009  *
0010  */
0011 
0012 #include <linux/acpi.h>
0013 #include <linux/leds.h>
0014 #include <linux/slab.h>
0015 #include <linux/module.h>
0016 
0017 MODULE_AUTHOR("Louis Davis/Jim Dailey");
0018 MODULE_DESCRIPTION("Dell LED Control Driver");
0019 MODULE_LICENSE("GPL");
0020 
0021 #define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
0022 MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
0023 
0024 /* Error Result Codes: */
0025 #define INVALID_DEVICE_ID   250
0026 #define INVALID_PARAMETER   251
0027 #define INVALID_BUFFER      252
0028 #define INTERFACE_ERROR     253
0029 #define UNSUPPORTED_COMMAND 254
0030 #define UNSPECIFIED_ERROR   255
0031 
0032 /* Device ID */
0033 #define DEVICE_ID_PANEL_BACK    1
0034 
0035 /* LED Commands */
0036 #define CMD_LED_ON  16
0037 #define CMD_LED_OFF 17
0038 #define CMD_LED_BLINK   18
0039 
0040 struct bios_args {
0041     unsigned char length;
0042     unsigned char result_code;
0043     unsigned char device_id;
0044     unsigned char command;
0045     unsigned char on_time;
0046     unsigned char off_time;
0047 };
0048 
0049 static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id,
0050                    u8 command, u8 on_time, u8 off_time)
0051 {
0052     struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
0053     struct bios_args *bios_return;
0054     struct acpi_buffer input;
0055     union acpi_object *obj;
0056     acpi_status status;
0057     u8 return_code;
0058 
0059     struct bios_args args = {
0060         .length = length,
0061         .result_code = result_code,
0062         .device_id = device_id,
0063         .command = command,
0064         .on_time = on_time,
0065         .off_time = off_time
0066     };
0067 
0068     input.length = sizeof(struct bios_args);
0069     input.pointer = &args;
0070 
0071     status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output);
0072     if (ACPI_FAILURE(status))
0073         return status;
0074 
0075     obj = output.pointer;
0076 
0077     if (!obj)
0078         return -EINVAL;
0079     if (obj->type != ACPI_TYPE_BUFFER) {
0080         kfree(obj);
0081         return -EINVAL;
0082     }
0083 
0084     bios_return = ((struct bios_args *)obj->buffer.pointer);
0085     return_code = bios_return->result_code;
0086 
0087     kfree(obj);
0088 
0089     return return_code;
0090 }
0091 
0092 static int led_on(void)
0093 {
0094     return dell_led_perform_fn(3,   /* Length of command */
0095         INTERFACE_ERROR,    /* Init to  INTERFACE_ERROR */
0096         DEVICE_ID_PANEL_BACK,   /* Device ID */
0097         CMD_LED_ON,     /* Command */
0098         0,          /* not used */
0099         0);         /* not used */
0100 }
0101 
0102 static int led_off(void)
0103 {
0104     return dell_led_perform_fn(3,   /* Length of command */
0105         INTERFACE_ERROR,    /* Init to  INTERFACE_ERROR */
0106         DEVICE_ID_PANEL_BACK,   /* Device ID */
0107         CMD_LED_OFF,        /* Command */
0108         0,          /* not used */
0109         0);         /* not used */
0110 }
0111 
0112 static int led_blink(unsigned char on_eighths, unsigned char off_eighths)
0113 {
0114     return dell_led_perform_fn(5,   /* Length of command */
0115         INTERFACE_ERROR,    /* Init to  INTERFACE_ERROR */
0116         DEVICE_ID_PANEL_BACK,   /* Device ID */
0117         CMD_LED_BLINK,      /* Command */
0118         on_eighths,     /* blink on in eigths of a second */
0119         off_eighths);       /* blink off in eights of a second */
0120 }
0121 
0122 static void dell_led_set(struct led_classdev *led_cdev,
0123              enum led_brightness value)
0124 {
0125     if (value == LED_OFF)
0126         led_off();
0127     else
0128         led_on();
0129 }
0130 
0131 static int dell_led_blink(struct led_classdev *led_cdev,
0132               unsigned long *delay_on, unsigned long *delay_off)
0133 {
0134     unsigned long on_eighths;
0135     unsigned long off_eighths;
0136 
0137     /*
0138      * The Dell LED delay is based on 125ms intervals.
0139      * Need to round up to next interval.
0140      */
0141 
0142     on_eighths = DIV_ROUND_UP(*delay_on, 125);
0143     on_eighths = clamp_t(unsigned long, on_eighths, 1, 255);
0144     *delay_on = on_eighths * 125;
0145 
0146     off_eighths = DIV_ROUND_UP(*delay_off, 125);
0147     off_eighths = clamp_t(unsigned long, off_eighths, 1, 255);
0148     *delay_off = off_eighths * 125;
0149 
0150     led_blink(on_eighths, off_eighths);
0151 
0152     return 0;
0153 }
0154 
0155 static struct led_classdev dell_led = {
0156     .name       = "dell::lid",
0157     .brightness = LED_OFF,
0158     .max_brightness = 1,
0159     .brightness_set = dell_led_set,
0160     .blink_set  = dell_led_blink,
0161     .flags      = LED_CORE_SUSPENDRESUME,
0162 };
0163 
0164 static int __init dell_led_init(void)
0165 {
0166     int error = 0;
0167 
0168     if (!wmi_has_guid(DELL_LED_BIOS_GUID))
0169         return -ENODEV;
0170 
0171     error = led_off();
0172     if (error != 0)
0173         return -ENODEV;
0174 
0175     return led_classdev_register(NULL, &dell_led);
0176 }
0177 
0178 static void __exit dell_led_exit(void)
0179 {
0180     led_classdev_unregister(&dell_led);
0181 
0182     led_off();
0183 }
0184 
0185 module_init(dell_led_init);
0186 module_exit(dell_led_exit);