dell-led: use dell_smbios_find_token() for finding mic DMI tokens
[cascardo/linux.git] / 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 #include <linux/slab.h>
17 #include <linux/module.h>
18 #include <linux/dmi.h>
19 #include <linux/dell-led.h>
20 #include "../platform/x86/dell-smbios.h"
21
22 MODULE_AUTHOR("Louis Davis/Jim Dailey");
23 MODULE_DESCRIPTION("Dell LED Control Driver");
24 MODULE_LICENSE("GPL");
25
26 #define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
27 #define DELL_APP_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
28 MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
29
30 /* Error Result Codes: */
31 #define INVALID_DEVICE_ID       250
32 #define INVALID_PARAMETER       251
33 #define INVALID_BUFFER          252
34 #define INTERFACE_ERROR         253
35 #define UNSUPPORTED_COMMAND     254
36 #define UNSPECIFIED_ERROR       255
37
38 /* Device ID */
39 #define DEVICE_ID_PANEL_BACK    1
40
41 /* LED Commands */
42 #define CMD_LED_ON      16
43 #define CMD_LED_OFF     17
44 #define CMD_LED_BLINK   18
45
46 struct app_wmi_args {
47         u16 class;
48         u16 selector;
49         u32 arg1;
50         u32 arg2;
51         u32 arg3;
52         u32 arg4;
53         u32 res1;
54         u32 res2;
55         u32 res3;
56         u32 res4;
57         char dummy[92];
58 };
59
60 #define GLOBAL_MIC_MUTE_ENABLE  0x364
61 #define GLOBAL_MIC_MUTE_DISABLE 0x365
62
63 static int dell_wmi_perform_query(struct app_wmi_args *args)
64 {
65         struct app_wmi_args *bios_return;
66         union acpi_object *obj;
67         struct acpi_buffer input;
68         struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
69         acpi_status status;
70         u32 rc = -EINVAL;
71
72         input.length = 128;
73         input.pointer = args;
74
75         status = wmi_evaluate_method(DELL_APP_GUID, 0, 1, &input, &output);
76         if (!ACPI_SUCCESS(status))
77                 goto err_out0;
78
79         obj = output.pointer;
80         if (!obj)
81                 goto err_out0;
82
83         if (obj->type != ACPI_TYPE_BUFFER)
84                 goto err_out1;
85
86         bios_return = (struct app_wmi_args *)obj->buffer.pointer;
87         rc = bios_return->res1;
88         if (rc)
89                 goto err_out1;
90
91         memcpy(args, bios_return, sizeof(struct app_wmi_args));
92         rc = 0;
93
94  err_out1:
95         kfree(obj);
96  err_out0:
97         return rc;
98 }
99
100 static int dell_micmute_led_set(int state)
101 {
102         struct calling_interface_token *token;
103         struct app_wmi_args args;
104
105         if (!wmi_has_guid(DELL_APP_GUID))
106                 return -ENODEV;
107
108         if (state == 0)
109                 token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE);
110         else if (state == 1)
111                 token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE);
112         else
113                 return -EINVAL;
114
115         if (!token)
116                 return -ENODEV;
117
118         memset(&args, 0, sizeof(struct app_wmi_args));
119
120         args.class = 1;
121         args.arg1 = token->location;
122         args.arg2 = token->value;
123
124         dell_wmi_perform_query(&args);
125
126         return state;
127 }
128
129 int dell_app_wmi_led_set(int whichled, int on)
130 {
131         int state = 0;
132
133         switch (whichled) {
134         case DELL_LED_MICMUTE:
135                 state = dell_micmute_led_set(on);
136                 break;
137         default:
138                 pr_warn("led type %x is not supported\n", whichled);
139                 break;
140         }
141
142         return state;
143 }
144 EXPORT_SYMBOL_GPL(dell_app_wmi_led_set);
145
146 struct bios_args {
147         unsigned char length;
148         unsigned char result_code;
149         unsigned char device_id;
150         unsigned char command;
151         unsigned char on_time;
152         unsigned char off_time;
153 };
154
155 static int dell_led_perform_fn(u8 length,
156                 u8 result_code,
157                 u8 device_id,
158                 u8 command,
159                 u8 on_time,
160                 u8 off_time)
161 {
162         struct bios_args *bios_return;
163         u8 return_code;
164         union acpi_object *obj;
165         struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
166         struct acpi_buffer input;
167         acpi_status status;
168
169         struct bios_args args;
170         args.length = length;
171         args.result_code = result_code;
172         args.device_id = device_id;
173         args.command = command;
174         args.on_time = on_time;
175         args.off_time = off_time;
176
177         input.length = sizeof(struct bios_args);
178         input.pointer = &args;
179
180         status = wmi_evaluate_method(DELL_LED_BIOS_GUID,
181                 1,
182                 1,
183                 &input,
184                 &output);
185
186         if (ACPI_FAILURE(status))
187                 return status;
188
189         obj = output.pointer;
190
191         if (!obj)
192                 return -EINVAL;
193         else if (obj->type != ACPI_TYPE_BUFFER) {
194                 kfree(obj);
195                 return -EINVAL;
196         }
197
198         bios_return = ((struct bios_args *)obj->buffer.pointer);
199         return_code = bios_return->result_code;
200
201         kfree(obj);
202
203         return return_code;
204 }
205
206 static int led_on(void)
207 {
208         return dell_led_perform_fn(3,   /* Length of command */
209                 INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
210                 DEVICE_ID_PANEL_BACK,   /* Device ID */
211                 CMD_LED_ON,             /* Command */
212                 0,                      /* not used */
213                 0);                     /* not used */
214 }
215
216 static int led_off(void)
217 {
218         return dell_led_perform_fn(3,   /* Length of command */
219                 INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
220                 DEVICE_ID_PANEL_BACK,   /* Device ID */
221                 CMD_LED_OFF,            /* Command */
222                 0,                      /* not used */
223                 0);                     /* not used */
224 }
225
226 static int led_blink(unsigned char on_eighths,
227                 unsigned char off_eighths)
228 {
229         return dell_led_perform_fn(5,   /* Length of command */
230                 INTERFACE_ERROR,        /* Init to  INTERFACE_ERROR */
231                 DEVICE_ID_PANEL_BACK,   /* Device ID */
232                 CMD_LED_BLINK,          /* Command */
233                 on_eighths,             /* blink on in eigths of a second */
234                 off_eighths);           /* blink off in eights of a second */
235 }
236
237 static void dell_led_set(struct led_classdev *led_cdev,
238                 enum led_brightness value)
239 {
240         if (value == LED_OFF)
241                 led_off();
242         else
243                 led_on();
244 }
245
246 static int dell_led_blink(struct led_classdev *led_cdev,
247                 unsigned long *delay_on,
248                 unsigned long *delay_off)
249 {
250         unsigned long on_eighths;
251         unsigned long off_eighths;
252
253         /* The Dell LED delay is based on 125ms intervals.
254            Need to round up to next interval. */
255
256         on_eighths = (*delay_on + 124) / 125;
257         if (0 == on_eighths)
258                 on_eighths = 1;
259         if (on_eighths > 255)
260                 on_eighths = 255;
261         *delay_on = on_eighths * 125;
262
263         off_eighths = (*delay_off + 124) / 125;
264         if (0 == off_eighths)
265                 off_eighths = 1;
266         if (off_eighths > 255)
267                 off_eighths = 255;
268         *delay_off = off_eighths * 125;
269
270         led_blink(on_eighths, off_eighths);
271
272         return 0;
273 }
274
275 static struct led_classdev dell_led = {
276         .name           = "dell::lid",
277         .brightness     = LED_OFF,
278         .max_brightness = 1,
279         .brightness_set = dell_led_set,
280         .blink_set      = dell_led_blink,
281         .flags          = LED_CORE_SUSPENDRESUME,
282 };
283
284 static int __init dell_led_init(void)
285 {
286         int error = 0;
287
288         if (!wmi_has_guid(DELL_LED_BIOS_GUID) && !wmi_has_guid(DELL_APP_GUID))
289                 return -ENODEV;
290
291         if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
292                 error = led_off();
293                 if (error != 0)
294                         return -ENODEV;
295
296                 error = led_classdev_register(NULL, &dell_led);
297         }
298
299         return error;
300 }
301
302 static void __exit dell_led_exit(void)
303 {
304         int error = 0;
305
306         if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
307                 error = led_off();
308                 if (error == 0)
309                         led_classdev_unregister(&dell_led);
310         }
311 }
312
313 module_init(dell_led_init);
314 module_exit(dell_led_exit);