linux/drivers/platform/x86/dell-wmi-led.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2010 Dell Inc.
   3 * Louis Davis <louis_davis@dell.com>
   4 * Jim Dailey <jim_dailey@dell.com>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as
   8 * published by the Free Software Foundation.
   9 *
  10 */
  11
  12#include <linux/acpi.h>
  13#include <linux/leds.h>
  14#include <linux/slab.h>
  15#include <linux/module.h>
  16
  17MODULE_AUTHOR("Louis Davis/Jim Dailey");
  18MODULE_DESCRIPTION("Dell LED Control Driver");
  19MODULE_LICENSE("GPL");
  20
  21#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
  22MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
  23
  24/* Error Result Codes: */
  25#define INVALID_DEVICE_ID       250
  26#define INVALID_PARAMETER       251
  27#define INVALID_BUFFER          252
  28#define INTERFACE_ERROR         253
  29#define UNSUPPORTED_COMMAND     254
  30#define UNSPECIFIED_ERROR       255
  31
  32/* Device ID */
  33#define DEVICE_ID_PANEL_BACK    1
  34
  35/* LED Commands */
  36#define CMD_LED_ON      16
  37#define CMD_LED_OFF     17
  38#define CMD_LED_BLINK   18
  39
  40struct bios_args {
  41        unsigned char length;
  42        unsigned char result_code;
  43        unsigned char device_id;
  44        unsigned char command;
  45        unsigned char on_time;
  46        unsigned char off_time;
  47};
  48
  49static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id,
  50                               u8 command, u8 on_time, u8 off_time)
  51{
  52        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
  53        struct bios_args *bios_return;
  54        struct acpi_buffer input;
  55        union acpi_object *obj;
  56        acpi_status status;
  57        u8 return_code;
  58
  59        struct bios_args args = {
  60                .length = length,
  61                .result_code = result_code,
  62                .device_id = device_id,
  63                .command = command,
  64                .on_time = on_time,
  65                .off_time = off_time
  66        };
  67
  68        input.length = sizeof(struct bios_args);
  69        input.pointer = &args;
  70
  71        status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output);
  72        if (ACPI_FAILURE(status))
  73                return status;
  74
  75        obj = output.pointer;
  76
  77        if (!obj)
  78                return -EINVAL;
  79        if (obj->type != ACPI_TYPE_BUFFER) {
  80                kfree(obj);
  81                return -EINVAL;
  82        }
  83
  84        bios_return = ((struct bios_args *)obj->buffer.pointer);
  85        return_code = bios_return->result_code;
  86
  87        kfree(obj);
  88
  89        return return_code;
  90}
  91
  92static int led_on(void)
  93{
  94        return dell_led_perform_fn(3,   /* Length of command */
  95                INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
  96                DEVICE_ID_PANEL_BACK,   /* Device ID */
  97                CMD_LED_ON,             /* Command */
  98                0,                      /* not used */
  99                0);                     /* not used */
 100}
 101
 102static int led_off(void)
 103{
 104        return dell_led_perform_fn(3,   /* Length of command */
 105                INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
 106                DEVICE_ID_PANEL_BACK,   /* Device ID */
 107                CMD_LED_OFF,            /* Command */
 108                0,                      /* not used */
 109                0);                     /* not used */
 110}
 111
 112static int led_blink(unsigned char on_eighths, unsigned char off_eighths)
 113{
 114        return dell_led_perform_fn(5,   /* Length of command */
 115                INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
 116                DEVICE_ID_PANEL_BACK,   /* Device ID */
 117                CMD_LED_BLINK,          /* Command */
 118                on_eighths,             /* blink on in eigths of a second */
 119                off_eighths);           /* blink off in eights of a second */
 120}
 121
 122static void dell_led_set(struct led_classdev *led_cdev,
 123                         enum led_brightness value)
 124{
 125        if (value == LED_OFF)
 126                led_off();
 127        else
 128                led_on();
 129}
 130
 131static int dell_led_blink(struct led_classdev *led_cdev,
 132                          unsigned long *delay_on, unsigned long *delay_off)
 133{
 134        unsigned long on_eighths;
 135        unsigned long off_eighths;
 136
 137        /*
 138         * The Dell LED delay is based on 125ms intervals.
 139         * Need to round up to next interval.
 140         */
 141
 142        on_eighths = DIV_ROUND_UP(*delay_on, 125);
 143        on_eighths = clamp_t(unsigned long, on_eighths, 1, 255);
 144        *delay_on = on_eighths * 125;
 145
 146        off_eighths = DIV_ROUND_UP(*delay_off, 125);
 147        off_eighths = clamp_t(unsigned long, off_eighths, 1, 255);
 148        *delay_off = off_eighths * 125;
 149
 150        led_blink(on_eighths, off_eighths);
 151
 152        return 0;
 153}
 154
 155static struct led_classdev dell_led = {
 156        .name           = "dell::lid",
 157        .brightness     = LED_OFF,
 158        .max_brightness = 1,
 159        .brightness_set = dell_led_set,
 160        .blink_set      = dell_led_blink,
 161        .flags          = LED_CORE_SUSPENDRESUME,
 162};
 163
 164static int __init dell_led_init(void)
 165{
 166        int error = 0;
 167
 168        if (!wmi_has_guid(DELL_LED_BIOS_GUID))
 169                return -ENODEV;
 170
 171        error = led_off();
 172        if (error != 0)
 173                return -ENODEV;
 174
 175        return led_classdev_register(NULL, &dell_led);
 176}
 177
 178static void __exit dell_led_exit(void)
 179{
 180        led_classdev_unregister(&dell_led);
 181
 182        led_off();
 183}
 184
 185module_init(dell_led_init);
 186module_exit(dell_led_exit);
 187