linux/drivers/leds/dell-led.c
<<
>>
Prefs
   1/*
   2 * dell_led.c - Dell LED Driver
   3 *
   4 * Copyright (C) 2010 Dell Inc.
   5 * Louis Davis <louis_davis@dell.com>
   6 * Jim Dailey <jim_dailey@dell.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as
  10 * published by the Free Software Foundation.
  11 *
  12 */
  13
  14#include <linux/acpi.h>
  15#include <linux/leds.h>
  16#include <linux/slab.h>
  17#include <linux/module.h>
  18#include <linux/dmi.h>
  19#include <linux/dell-led.h>
  20
  21MODULE_AUTHOR("Louis Davis/Jim Dailey");
  22MODULE_DESCRIPTION("Dell LED Control Driver");
  23MODULE_LICENSE("GPL");
  24
  25#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
  26#define DELL_APP_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
  27MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
  28
  29/* Error Result Codes: */
  30#define INVALID_DEVICE_ID       250
  31#define INVALID_PARAMETER       251
  32#define INVALID_BUFFER          252
  33#define INTERFACE_ERROR         253
  34#define UNSUPPORTED_COMMAND     254
  35#define UNSPECIFIED_ERROR       255
  36
  37/* Device ID */
  38#define DEVICE_ID_PANEL_BACK    1
  39
  40/* LED Commands */
  41#define CMD_LED_ON      16
  42#define CMD_LED_OFF     17
  43#define CMD_LED_BLINK   18
  44
  45struct app_wmi_args {
  46        u16 class;
  47        u16 selector;
  48        u32 arg1;
  49        u32 arg2;
  50        u32 arg3;
  51        u32 arg4;
  52        u32 res1;
  53        u32 res2;
  54        u32 res3;
  55        u32 res4;
  56        char dummy[92];
  57};
  58
  59#define GLOBAL_MIC_MUTE_ENABLE  0x364
  60#define GLOBAL_MIC_MUTE_DISABLE 0x365
  61
  62struct dell_bios_data_token {
  63        u16 tokenid;
  64        u16 location;
  65        u16 value;
  66};
  67
  68struct __attribute__ ((__packed__)) dell_bios_calling_interface {
  69        struct  dmi_header header;
  70        u16     cmd_io_addr;
  71        u8      cmd_io_code;
  72        u32     supported_cmds;
  73        struct  dell_bios_data_token damap[];
  74};
  75
  76static struct dell_bios_data_token dell_mic_tokens[2];
  77
  78static int dell_wmi_perform_query(struct app_wmi_args *args)
  79{
  80        struct app_wmi_args *bios_return;
  81        union acpi_object *obj;
  82        struct acpi_buffer input;
  83        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
  84        acpi_status status;
  85        u32 rc = -EINVAL;
  86
  87        input.length = 128;
  88        input.pointer = args;
  89
  90        status = wmi_evaluate_method(DELL_APP_GUID, 0, 1, &input, &output);
  91        if (!ACPI_SUCCESS(status))
  92                goto err_out0;
  93
  94        obj = output.pointer;
  95        if (!obj)
  96                goto err_out0;
  97
  98        if (obj->type != ACPI_TYPE_BUFFER)
  99                goto err_out1;
 100
 101        bios_return = (struct app_wmi_args *)obj->buffer.pointer;
 102        rc = bios_return->res1;
 103        if (rc)
 104                goto err_out1;
 105
 106        memcpy(args, bios_return, sizeof(struct app_wmi_args));
 107        rc = 0;
 108
 109 err_out1:
 110        kfree(obj);
 111 err_out0:
 112        return rc;
 113}
 114
 115static void __init find_micmute_tokens(const struct dmi_header *dm, void *dummy)
 116{
 117        struct dell_bios_calling_interface *calling_interface;
 118        struct dell_bios_data_token *token;
 119        int token_size = sizeof(struct dell_bios_data_token);
 120        int i = 0;
 121
 122        if (dm->type == 0xda && dm->length > 17) {
 123                calling_interface = container_of(dm,
 124                                struct dell_bios_calling_interface, header);
 125
 126                token = &calling_interface->damap[i];
 127                while (token->tokenid != 0xffff) {
 128                        if (token->tokenid == GLOBAL_MIC_MUTE_DISABLE)
 129                                memcpy(&dell_mic_tokens[0], token, token_size);
 130                        else if (token->tokenid == GLOBAL_MIC_MUTE_ENABLE)
 131                                memcpy(&dell_mic_tokens[1], token, token_size);
 132
 133                        i++;
 134                        token = &calling_interface->damap[i];
 135                }
 136        }
 137}
 138
 139static int dell_micmute_led_set(int state)
 140{
 141        struct app_wmi_args args;
 142        struct dell_bios_data_token *token;
 143
 144        if (!wmi_has_guid(DELL_APP_GUID))
 145                return -ENODEV;
 146
 147        if (state == 0 || state == 1)
 148                token = &dell_mic_tokens[state];
 149        else
 150                return -EINVAL;
 151
 152        memset(&args, 0, sizeof(struct app_wmi_args));
 153
 154        args.class = 1;
 155        args.arg1 = token->location;
 156        args.arg2 = token->value;
 157
 158        dell_wmi_perform_query(&args);
 159
 160        return state;
 161}
 162
 163int dell_app_wmi_led_set(int whichled, int on)
 164{
 165        int state = 0;
 166
 167        switch (whichled) {
 168        case DELL_LED_MICMUTE:
 169                state = dell_micmute_led_set(on);
 170                break;
 171        default:
 172                pr_warn("led type %x is not supported\n", whichled);
 173                break;
 174        }
 175
 176        return state;
 177}
 178EXPORT_SYMBOL_GPL(dell_app_wmi_led_set);
 179
 180static int __init dell_micmute_led_init(void)
 181{
 182        memset(dell_mic_tokens, 0, sizeof(struct dell_bios_data_token) * 2);
 183        dmi_walk(find_micmute_tokens, NULL);
 184
 185        return 0;
 186}
 187
 188struct bios_args {
 189        unsigned char length;
 190        unsigned char result_code;
 191        unsigned char device_id;
 192        unsigned char command;
 193        unsigned char on_time;
 194        unsigned char off_time;
 195};
 196
 197static int dell_led_perform_fn(u8 length,
 198                u8 result_code,
 199                u8 device_id,
 200                u8 command,
 201                u8 on_time,
 202                u8 off_time)
 203{
 204        struct bios_args *bios_return;
 205        u8 return_code;
 206        union acpi_object *obj;
 207        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
 208        struct acpi_buffer input;
 209        acpi_status status;
 210
 211        struct bios_args args;
 212        args.length = length;
 213        args.result_code = result_code;
 214        args.device_id = device_id;
 215        args.command = command;
 216        args.on_time = on_time;
 217        args.off_time = off_time;
 218
 219        input.length = sizeof(struct bios_args);
 220        input.pointer = &args;
 221
 222        status = wmi_evaluate_method(DELL_LED_BIOS_GUID,
 223                1,
 224                1,
 225                &input,
 226                &output);
 227
 228        if (ACPI_FAILURE(status))
 229                return status;
 230
 231        obj = output.pointer;
 232
 233        if (!obj)
 234                return -EINVAL;
 235        else if (obj->type != ACPI_TYPE_BUFFER) {
 236                kfree(obj);
 237                return -EINVAL;
 238        }
 239
 240        bios_return = ((struct bios_args *)obj->buffer.pointer);
 241        return_code = bios_return->result_code;
 242
 243        kfree(obj);
 244
 245        return return_code;
 246}
 247
 248static int led_on(void)
 249{
 250        return dell_led_perform_fn(3,   /* Length of command */
 251                INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
 252                DEVICE_ID_PANEL_BACK,   /* Device ID */
 253                CMD_LED_ON,             /* Command */
 254                0,                      /* not used */
 255                0);                     /* not used */
 256}
 257
 258static int led_off(void)
 259{
 260        return dell_led_perform_fn(3,   /* Length of command */
 261                INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
 262                DEVICE_ID_PANEL_BACK,   /* Device ID */
 263                CMD_LED_OFF,            /* Command */
 264                0,                      /* not used */
 265                0);                     /* not used */
 266}
 267
 268static int led_blink(unsigned char on_eighths,
 269                unsigned char off_eighths)
 270{
 271        return dell_led_perform_fn(5,   /* Length of command */
 272                INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
 273                DEVICE_ID_PANEL_BACK,   /* Device ID */
 274                CMD_LED_BLINK,          /* Command */
 275                on_eighths,             /* blink on in eigths of a second */
 276                off_eighths);           /* blink off in eights of a second */
 277}
 278
 279static void dell_led_set(struct led_classdev *led_cdev,
 280                enum led_brightness value)
 281{
 282        if (value == LED_OFF)
 283                led_off();
 284        else
 285                led_on();
 286}
 287
 288static int dell_led_blink(struct led_classdev *led_cdev,
 289                unsigned long *delay_on,
 290                unsigned long *delay_off)
 291{
 292        unsigned long on_eighths;
 293        unsigned long off_eighths;
 294
 295        /* The Dell LED delay is based on 125ms intervals.
 296           Need to round up to next interval. */
 297
 298        on_eighths = (*delay_on + 124) / 125;
 299        if (0 == on_eighths)
 300                on_eighths = 1;
 301        if (on_eighths > 255)
 302                on_eighths = 255;
 303        *delay_on = on_eighths * 125;
 304
 305        off_eighths = (*delay_off + 124) / 125;
 306        if (0 == off_eighths)
 307                off_eighths = 1;
 308        if (off_eighths > 255)
 309                off_eighths = 255;
 310        *delay_off = off_eighths * 125;
 311
 312        led_blink(on_eighths, off_eighths);
 313
 314        return 0;
 315}
 316
 317static struct led_classdev dell_led = {
 318        .name           = "dell::lid",
 319        .brightness     = LED_OFF,
 320        .max_brightness = 1,
 321        .brightness_set = dell_led_set,
 322        .blink_set      = dell_led_blink,
 323        .flags          = LED_CORE_SUSPENDRESUME,
 324};
 325
 326static int __init dell_led_init(void)
 327{
 328        int error = 0;
 329
 330        if (!wmi_has_guid(DELL_LED_BIOS_GUID) && !wmi_has_guid(DELL_APP_GUID))
 331                return -ENODEV;
 332
 333        if (wmi_has_guid(DELL_APP_GUID))
 334                error = dell_micmute_led_init();
 335
 336        if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
 337                error = led_off();
 338                if (error != 0)
 339                        return -ENODEV;
 340
 341                error = led_classdev_register(NULL, &dell_led);
 342        }
 343
 344        return error;
 345}
 346
 347static void __exit dell_led_exit(void)
 348{
 349        int error = 0;
 350
 351        if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
 352                error = led_off();
 353                if (error == 0)
 354                        led_classdev_unregister(&dell_led);
 355        }
 356}
 357
 358module_init(dell_led_init);
 359module_exit(dell_led_exit);
 360