Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / platform / x86 / alienware-wmi.c
1 /*
2  * Alienware AlienFX control
3  *
4  * Copyright (C) 2014 Dell Inc <mario_limonciello@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 published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  */
17
18 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
19
20 #include <linux/acpi.h>
21 #include <linux/module.h>
22 #include <linux/platform_device.h>
23 #include <linux/dmi.h>
24 #include <linux/acpi.h>
25 #include <linux/leds.h>
26
27 #define LEGACY_CONTROL_GUID             "A90597CE-A997-11DA-B012-B622A1EF5492"
28 #define LEGACY_POWER_CONTROL_GUID       "A80593CE-A997-11DA-B012-B622A1EF5492"
29 #define WMAX_CONTROL_GUID               "A70591CE-A997-11DA-B012-B622A1EF5492"
30
31 #define WMAX_METHOD_HDMI_SOURCE         0x1
32 #define WMAX_METHOD_HDMI_STATUS         0x2
33 #define WMAX_METHOD_BRIGHTNESS          0x3
34 #define WMAX_METHOD_ZONE_CONTROL        0x4
35 #define WMAX_METHOD_HDMI_CABLE          0x5
36
37 MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
38 MODULE_DESCRIPTION("Alienware special feature control");
39 MODULE_LICENSE("GPL");
40 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
41 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
42
43 enum INTERFACE_FLAGS {
44         LEGACY,
45         WMAX,
46 };
47
48 enum LEGACY_CONTROL_STATES {
49         LEGACY_RUNNING = 1,
50         LEGACY_BOOTING = 0,
51         LEGACY_SUSPEND = 3,
52 };
53
54 enum WMAX_CONTROL_STATES {
55         WMAX_RUNNING = 0xFF,
56         WMAX_BOOTING = 0,
57         WMAX_SUSPEND = 3,
58 };
59
60 struct quirk_entry {
61         u8 num_zones;
62         u8 hdmi_mux;
63 };
64
65 static struct quirk_entry *quirks;
66
67 static struct quirk_entry quirk_unknown = {
68         .num_zones = 2,
69         .hdmi_mux = 0,
70 };
71
72 static struct quirk_entry quirk_x51_family = {
73         .num_zones = 3,
74         .hdmi_mux = 0.
75 };
76
77 static struct quirk_entry quirk_asm100 = {
78         .num_zones = 2,
79         .hdmi_mux = 1,
80 };
81
82 static int __init dmi_matched(const struct dmi_system_id *dmi)
83 {
84         quirks = dmi->driver_data;
85         return 1;
86 }
87
88 static const struct dmi_system_id alienware_quirks[] __initconst = {
89         {
90          .callback = dmi_matched,
91          .ident = "Alienware X51 R1",
92          .matches = {
93                      DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
94                      DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
95                      },
96          .driver_data = &quirk_x51_family,
97          },
98         {
99          .callback = dmi_matched,
100          .ident = "Alienware X51 R2",
101          .matches = {
102                      DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
103                      DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
104                      },
105          .driver_data = &quirk_x51_family,
106          },
107         {
108                 .callback = dmi_matched,
109                 .ident = "Alienware ASM100",
110                 .matches = {
111                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
112                         DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
113                 },
114                 .driver_data = &quirk_asm100,
115         },
116         {}
117 };
118
119 struct color_platform {
120         u8 blue;
121         u8 green;
122         u8 red;
123 } __packed;
124
125 struct platform_zone {
126         u8 location;
127         struct device_attribute *attr;
128         struct color_platform colors;
129 };
130
131 struct wmax_brightness_args {
132         u32 led_mask;
133         u32 percentage;
134 };
135
136 struct hdmi_args {
137         u8 arg;
138 };
139
140 struct legacy_led_args {
141         struct color_platform colors;
142         u8 brightness;
143         u8 state;
144 } __packed;
145
146 struct wmax_led_args {
147         u32 led_mask;
148         struct color_platform colors;
149         u8 state;
150 } __packed;
151
152 static struct platform_device *platform_device;
153 static struct device_attribute *zone_dev_attrs;
154 static struct attribute **zone_attrs;
155 static struct platform_zone *zone_data;
156
157 static struct platform_driver platform_driver = {
158         .driver = {
159                    .name = "alienware-wmi",
160                    }
161 };
162
163 static struct attribute_group zone_attribute_group = {
164         .name = "rgb_zones",
165 };
166
167 static u8 interface;
168 static u8 lighting_control_state;
169 static u8 global_brightness;
170
171 /*
172  * Helpers used for zone control
173 */
174 static int parse_rgb(const char *buf, struct platform_zone *zone)
175 {
176         long unsigned int rgb;
177         int ret;
178         union color_union {
179                 struct color_platform cp;
180                 int package;
181         } repackager;
182
183         ret = kstrtoul(buf, 16, &rgb);
184         if (ret)
185                 return ret;
186
187         /* RGB triplet notation is 24-bit hexadecimal */
188         if (rgb > 0xFFFFFF)
189                 return -EINVAL;
190
191         repackager.package = rgb & 0x0f0f0f0f;
192         pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
193                  repackager.cp.red, repackager.cp.green, repackager.cp.blue);
194         zone->colors = repackager.cp;
195         return 0;
196 }
197
198 static struct platform_zone *match_zone(struct device_attribute *attr)
199 {
200         int i;
201         for (i = 0; i < quirks->num_zones; i++) {
202                 if ((struct device_attribute *)zone_data[i].attr == attr) {
203                         pr_debug("alienware-wmi: matched zone location: %d\n",
204                                  zone_data[i].location);
205                         return &zone_data[i];
206                 }
207         }
208         return NULL;
209 }
210
211 /*
212  * Individual RGB zone control
213 */
214 static int alienware_update_led(struct platform_zone *zone)
215 {
216         int method_id;
217         acpi_status status;
218         char *guid;
219         struct acpi_buffer input;
220         struct legacy_led_args legacy_args;
221         struct wmax_led_args wmax_args;
222         if (interface == WMAX) {
223                 wmax_args.led_mask = 1 << zone->location;
224                 wmax_args.colors = zone->colors;
225                 wmax_args.state = lighting_control_state;
226                 guid = WMAX_CONTROL_GUID;
227                 method_id = WMAX_METHOD_ZONE_CONTROL;
228
229                 input.length = (acpi_size) sizeof(wmax_args);
230                 input.pointer = &wmax_args;
231         } else {
232                 legacy_args.colors = zone->colors;
233                 legacy_args.brightness = global_brightness;
234                 legacy_args.state = 0;
235                 if (lighting_control_state == LEGACY_BOOTING ||
236                     lighting_control_state == LEGACY_SUSPEND) {
237                         guid = LEGACY_POWER_CONTROL_GUID;
238                         legacy_args.state = lighting_control_state;
239                 } else
240                         guid = LEGACY_CONTROL_GUID;
241                 method_id = zone->location + 1;
242
243                 input.length = (acpi_size) sizeof(legacy_args);
244                 input.pointer = &legacy_args;
245         }
246         pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
247
248         status = wmi_evaluate_method(guid, 1, method_id, &input, NULL);
249         if (ACPI_FAILURE(status))
250                 pr_err("alienware-wmi: zone set failure: %u\n", status);
251         return ACPI_FAILURE(status);
252 }
253
254 static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
255                          char *buf)
256 {
257         struct platform_zone *target_zone;
258         target_zone = match_zone(attr);
259         if (target_zone == NULL)
260                 return sprintf(buf, "red: -1, green: -1, blue: -1\n");
261         return sprintf(buf, "red: %d, green: %d, blue: %d\n",
262                        target_zone->colors.red,
263                        target_zone->colors.green, target_zone->colors.blue);
264
265 }
266
267 static ssize_t zone_set(struct device *dev, struct device_attribute *attr,
268                         const char *buf, size_t count)
269 {
270         struct platform_zone *target_zone;
271         int ret;
272         target_zone = match_zone(attr);
273         if (target_zone == NULL) {
274                 pr_err("alienware-wmi: invalid target zone\n");
275                 return 1;
276         }
277         ret = parse_rgb(buf, target_zone);
278         if (ret)
279                 return ret;
280         ret = alienware_update_led(target_zone);
281         return ret ? ret : count;
282 }
283
284 /*
285  * LED Brightness (Global)
286 */
287 static int wmax_brightness(int brightness)
288 {
289         acpi_status status;
290         struct acpi_buffer input;
291         struct wmax_brightness_args args = {
292                 .led_mask = 0xFF,
293                 .percentage = brightness,
294         };
295         input.length = (acpi_size) sizeof(args);
296         input.pointer = &args;
297         status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
298                                      WMAX_METHOD_BRIGHTNESS, &input, NULL);
299         if (ACPI_FAILURE(status))
300                 pr_err("alienware-wmi: brightness set failure: %u\n", status);
301         return ACPI_FAILURE(status);
302 }
303
304 static void global_led_set(struct led_classdev *led_cdev,
305                            enum led_brightness brightness)
306 {
307         int ret;
308         global_brightness = brightness;
309         if (interface == WMAX)
310                 ret = wmax_brightness(brightness);
311         else
312                 ret = alienware_update_led(&zone_data[0]);
313         if (ret)
314                 pr_err("LED brightness update failed\n");
315 }
316
317 static enum led_brightness global_led_get(struct led_classdev *led_cdev)
318 {
319         return global_brightness;
320 }
321
322 static struct led_classdev global_led = {
323         .brightness_set = global_led_set,
324         .brightness_get = global_led_get,
325         .name = "alienware::global_brightness",
326 };
327
328 /*
329  * Lighting control state device attribute (Global)
330 */
331 static ssize_t show_control_state(struct device *dev,
332                                   struct device_attribute *attr, char *buf)
333 {
334         if (lighting_control_state == LEGACY_BOOTING)
335                 return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n");
336         else if (lighting_control_state == LEGACY_SUSPEND)
337                 return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n");
338         return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n");
339 }
340
341 static ssize_t store_control_state(struct device *dev,
342                                    struct device_attribute *attr,
343                                    const char *buf, size_t count)
344 {
345         long unsigned int val;
346         if (strcmp(buf, "booting\n") == 0)
347                 val = LEGACY_BOOTING;
348         else if (strcmp(buf, "suspend\n") == 0)
349                 val = LEGACY_SUSPEND;
350         else if (interface == LEGACY)
351                 val = LEGACY_RUNNING;
352         else
353                 val = WMAX_RUNNING;
354         lighting_control_state = val;
355         pr_debug("alienware-wmi: updated control state to %d\n",
356                  lighting_control_state);
357         return count;
358 }
359
360 static DEVICE_ATTR(lighting_control_state, 0644, show_control_state,
361                    store_control_state);
362
363 static int alienware_zone_init(struct platform_device *dev)
364 {
365         int i;
366         char buffer[10];
367         char *name;
368
369         if (interface == WMAX) {
370                 lighting_control_state = WMAX_RUNNING;
371         } else if (interface == LEGACY) {
372                 lighting_control_state = LEGACY_RUNNING;
373         }
374         global_led.max_brightness = 0x0F;
375         global_brightness = global_led.max_brightness;
376
377         /*
378          *      - zone_dev_attrs num_zones + 1 is for individual zones and then
379          *        null terminated
380          *      - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
381          *        the lighting control + null terminated
382          *      - zone_data num_zones is for the distinct zones
383          */
384         zone_dev_attrs =
385             kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1),
386                     GFP_KERNEL);
387         if (!zone_dev_attrs)
388                 return -ENOMEM;
389
390         zone_attrs =
391             kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2),
392                     GFP_KERNEL);
393         if (!zone_attrs)
394                 return -ENOMEM;
395
396         zone_data =
397             kzalloc(sizeof(struct platform_zone) * (quirks->num_zones),
398                     GFP_KERNEL);
399         if (!zone_data)
400                 return -ENOMEM;
401
402         for (i = 0; i < quirks->num_zones; i++) {
403                 sprintf(buffer, "zone%02X", i);
404                 name = kstrdup(buffer, GFP_KERNEL);
405                 if (name == NULL)
406                         return 1;
407                 sysfs_attr_init(&zone_dev_attrs[i].attr);
408                 zone_dev_attrs[i].attr.name = name;
409                 zone_dev_attrs[i].attr.mode = 0644;
410                 zone_dev_attrs[i].show = zone_show;
411                 zone_dev_attrs[i].store = zone_set;
412                 zone_data[i].location = i;
413                 zone_attrs[i] = &zone_dev_attrs[i].attr;
414                 zone_data[i].attr = &zone_dev_attrs[i];
415         }
416         zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;
417         zone_attribute_group.attrs = zone_attrs;
418
419         led_classdev_register(&dev->dev, &global_led);
420
421         return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);
422 }
423
424 static void alienware_zone_exit(struct platform_device *dev)
425 {
426         sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);
427         led_classdev_unregister(&global_led);
428         if (zone_dev_attrs) {
429                 int i;
430                 for (i = 0; i < quirks->num_zones; i++)
431                         kfree(zone_dev_attrs[i].attr.name);
432         }
433         kfree(zone_dev_attrs);
434         kfree(zone_data);
435         kfree(zone_attrs);
436 }
437
438 /*
439         The HDMI mux sysfs node indicates the status of the HDMI input mux.
440         It can toggle between standard system GPU output and HDMI input.
441 */
442 static acpi_status alienware_hdmi_command(struct hdmi_args *in_args,
443                                           u32 command, int *out_data)
444 {
445         acpi_status status;
446         union acpi_object *obj;
447         struct acpi_buffer input;
448         struct acpi_buffer output;
449
450         input.length = (acpi_size) sizeof(*in_args);
451         input.pointer = in_args;
452         if (out_data != NULL) {
453                 output.length = ACPI_ALLOCATE_BUFFER;
454                 output.pointer = NULL;
455                 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
456                                              command, &input, &output);
457         } else
458                 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
459                                              command, &input, NULL);
460
461         if (ACPI_SUCCESS(status) && out_data != NULL) {
462                 obj = (union acpi_object *)output.pointer;
463                 if (obj && obj->type == ACPI_TYPE_INTEGER)
464                         *out_data = (u32) obj->integer.value;
465         }
466         return status;
467
468 }
469
470 static ssize_t show_hdmi_cable(struct device *dev,
471                                struct device_attribute *attr, char *buf)
472 {
473         acpi_status status;
474         u32 out_data;
475         struct hdmi_args in_args = {
476                 .arg = 0,
477         };
478         status =
479             alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_CABLE,
480                                    (u32 *) &out_data);
481         if (ACPI_SUCCESS(status)) {
482                 if (out_data == 0)
483                         return scnprintf(buf, PAGE_SIZE,
484                                          "[unconnected] connected unknown\n");
485                 else if (out_data == 1)
486                         return scnprintf(buf, PAGE_SIZE,
487                                          "unconnected [connected] unknown\n");
488         }
489         pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
490         return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
491 }
492
493 static ssize_t show_hdmi_source(struct device *dev,
494                                 struct device_attribute *attr, char *buf)
495 {
496         acpi_status status;
497         u32 out_data;
498         struct hdmi_args in_args = {
499                 .arg = 0,
500         };
501         status =
502             alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_STATUS,
503                                    (u32 *) &out_data);
504
505         if (ACPI_SUCCESS(status)) {
506                 if (out_data == 1)
507                         return scnprintf(buf, PAGE_SIZE,
508                                          "[input] gpu unknown\n");
509                 else if (out_data == 2)
510                         return scnprintf(buf, PAGE_SIZE,
511                                          "input [gpu] unknown\n");
512         }
513         pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data);
514         return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n");
515 }
516
517 static ssize_t toggle_hdmi_source(struct device *dev,
518                                   struct device_attribute *attr,
519                                   const char *buf, size_t count)
520 {
521         acpi_status status;
522         struct hdmi_args args;
523         if (strcmp(buf, "gpu\n") == 0)
524                 args.arg = 1;
525         else if (strcmp(buf, "input\n") == 0)
526                 args.arg = 2;
527         else
528                 args.arg = 3;
529         pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
530
531         status = alienware_hdmi_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
532
533         if (ACPI_FAILURE(status))
534                 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
535                        status);
536         return count;
537 }
538
539 static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);
540 static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,
541                    toggle_hdmi_source);
542
543 static struct attribute *hdmi_attrs[] = {
544         &dev_attr_cable.attr,
545         &dev_attr_source.attr,
546         NULL,
547 };
548
549 static struct attribute_group hdmi_attribute_group = {
550         .name = "hdmi",
551         .attrs = hdmi_attrs,
552 };
553
554 static void remove_hdmi(struct platform_device *dev)
555 {
556         if (quirks->hdmi_mux > 0)
557                 sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);
558 }
559
560 static int create_hdmi(struct platform_device *dev)
561 {
562         int ret;
563
564         ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
565         if (ret)
566                 goto error_create_hdmi;
567         return 0;
568
569 error_create_hdmi:
570         remove_hdmi(dev);
571         return ret;
572 }
573
574 static int __init alienware_wmi_init(void)
575 {
576         int ret;
577
578         if (wmi_has_guid(LEGACY_CONTROL_GUID))
579                 interface = LEGACY;
580         else if (wmi_has_guid(WMAX_CONTROL_GUID))
581                 interface = WMAX;
582         else {
583                 pr_warn("alienware-wmi: No known WMI GUID found\n");
584                 return -ENODEV;
585         }
586
587         dmi_check_system(alienware_quirks);
588         if (quirks == NULL)
589                 quirks = &quirk_unknown;
590
591         ret = platform_driver_register(&platform_driver);
592         if (ret)
593                 goto fail_platform_driver;
594         platform_device = platform_device_alloc("alienware-wmi", -1);
595         if (!platform_device) {
596                 ret = -ENOMEM;
597                 goto fail_platform_device1;
598         }
599         ret = platform_device_add(platform_device);
600         if (ret)
601                 goto fail_platform_device2;
602
603         if (quirks->hdmi_mux > 0) {
604                 ret = create_hdmi(platform_device);
605                 if (ret)
606                         goto fail_prep_hdmi;
607         }
608
609         ret = alienware_zone_init(platform_device);
610         if (ret)
611                 goto fail_prep_zones;
612
613         return 0;
614
615 fail_prep_zones:
616         alienware_zone_exit(platform_device);
617 fail_prep_hdmi:
618         platform_device_del(platform_device);
619 fail_platform_device2:
620         platform_device_put(platform_device);
621 fail_platform_device1:
622         platform_driver_unregister(&platform_driver);
623 fail_platform_driver:
624         return ret;
625 }
626
627 module_init(alienware_wmi_init);
628
629 static void __exit alienware_wmi_exit(void)
630 {
631         if (platform_device) {
632                 alienware_zone_exit(platform_device);
633                 remove_hdmi(platform_device);
634                 platform_device_unregister(platform_device);
635                 platform_driver_unregister(&platform_driver);
636         }
637 }
638
639 module_exit(alienware_wmi_exit);