leds: Add Dell Business Class Netbook LED driver
[safe/jmp/linux-2.6] / drivers / leds / dell-led.c
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
17 MODULE_AUTHOR("Louis Davis/Jim Dailey");
18 MODULE_DESCRIPTION("Dell LED Control Driver");
19 MODULE_LICENSE("GPL");
20
21 #define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
22 MODULE_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
40 struct 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
49 static int dell_led_perform_fn(u8 length,
50                 u8 result_code,
51                 u8 device_id,
52                 u8 command,
53                 u8 on_time,
54                 u8 off_time)
55 {
56         struct bios_args *bios_return;
57         u8 return_code;
58         union acpi_object *obj;
59         struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
60         struct acpi_buffer input;
61         acpi_status status;
62
63         struct bios_args args;
64         args.length = length;
65         args.result_code = result_code;
66         args.device_id = device_id;
67         args.command = command;
68         args.on_time = on_time;
69         args.off_time = off_time;
70
71         input.length = sizeof(struct bios_args);
72         input.pointer = &args;
73
74         status = wmi_evaluate_method(DELL_LED_BIOS_GUID,
75                 1,
76                 1,
77                 &input,
78                 &output);
79
80         if (ACPI_FAILURE(status))
81                 return status;
82
83         obj = output.pointer;
84
85         if (!obj)
86                 return -EINVAL;
87         else if (obj->type != ACPI_TYPE_BUFFER) {
88                 kfree(obj);
89                 return -EINVAL;
90         }
91
92         bios_return = ((struct bios_args *)obj->buffer.pointer);
93         return_code = bios_return->result_code;
94
95         kfree(obj);
96
97         return return_code;
98 }
99
100 static int led_on(void)
101 {
102         return dell_led_perform_fn(3,   /* Length of command */
103                 INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
104                 DEVICE_ID_PANEL_BACK,   /* Device ID */
105                 CMD_LED_ON,             /* Command */
106                 0,                      /* not used */
107                 0);                     /* not used */
108 }
109
110 static int led_off(void)
111 {
112         return dell_led_perform_fn(3,   /* Length of command */
113                 INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
114                 DEVICE_ID_PANEL_BACK,   /* Device ID */
115                 CMD_LED_OFF,            /* Command */
116                 0,                      /* not used */
117                 0);                     /* not used */
118 }
119
120 static int led_blink(unsigned char on_eighths,
121                 unsigned char off_eighths)
122 {
123         return dell_led_perform_fn(5,   /* Length of command */
124                 INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
125                 DEVICE_ID_PANEL_BACK,   /* Device ID */
126                 CMD_LED_BLINK,          /* Command */
127                 on_eighths,             /* blink on in eigths of a second */
128                 off_eighths);           /* blink off in eights of a second */
129 }
130
131 static void dell_led_set(struct led_classdev *led_cdev,
132                 enum led_brightness value)
133 {
134         if (value == LED_OFF)
135                 led_off();
136         else
137                 led_on();
138 }
139
140 static int dell_led_blink(struct led_classdev *led_cdev,
141                 unsigned long *delay_on,
142                 unsigned long *delay_off)
143 {
144         unsigned long on_eighths;
145         unsigned long off_eighths;
146
147         /* The Dell LED delay is based on 125ms intervals.
148            Need to round up to next interval. */
149
150         on_eighths = (*delay_on + 124) / 125;
151         if (0 == on_eighths)
152                 on_eighths = 1;
153         if (on_eighths > 255)
154                 on_eighths = 255;
155         *delay_on = on_eighths * 125;
156
157         off_eighths = (*delay_off + 124) / 125;
158         if (0 == off_eighths)
159                 off_eighths = 1;
160         if (off_eighths > 255)
161                 off_eighths = 255;
162         *delay_off = off_eighths * 125;
163
164         led_blink(on_eighths, off_eighths);
165
166         return 0;
167 }
168
169 static struct led_classdev dell_led = {
170         .name           = "dell::lid",
171         .brightness     = LED_OFF,
172         .max_brightness = 1,
173         .brightness_set = dell_led_set,
174         .blink_set      = dell_led_blink,
175         .flags          = LED_CORE_SUSPENDRESUME,
176 };
177
178 static int __init dell_led_init(void)
179 {
180         int error = 0;
181
182         if (!wmi_has_guid(DELL_LED_BIOS_GUID))
183                 return -ENODEV;
184
185         error = led_off();
186         if (error != 0)
187                 return -ENODEV;
188
189         return led_classdev_register(NULL, &dell_led);
190 }
191
192 static void __exit dell_led_exit(void)
193 {
194         led_classdev_unregister(&dell_led);
195
196         led_off();
197 }
198
199 module_init(dell_led_init);
200 module_exit(dell_led_exit);