linux/drivers/platform/x86/alienware-wmi.c
<<
>>
Prefs
   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#define WMAX_METHOD_AMPLIFIER_CABLE     0x6
  37#define WMAX_METHOD_DEEP_SLEEP_CONTROL  0x0B
  38#define WMAX_METHOD_DEEP_SLEEP_STATUS   0x0C
  39
  40MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
  41MODULE_DESCRIPTION("Alienware special feature control");
  42MODULE_LICENSE("GPL");
  43MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
  44MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
  45
  46enum INTERFACE_FLAGS {
  47        LEGACY,
  48        WMAX,
  49};
  50
  51enum LEGACY_CONTROL_STATES {
  52        LEGACY_RUNNING = 1,
  53        LEGACY_BOOTING = 0,
  54        LEGACY_SUSPEND = 3,
  55};
  56
  57enum WMAX_CONTROL_STATES {
  58        WMAX_RUNNING = 0xFF,
  59        WMAX_BOOTING = 0,
  60        WMAX_SUSPEND = 3,
  61};
  62
  63struct quirk_entry {
  64        u8 num_zones;
  65        u8 hdmi_mux;
  66        u8 amplifier;
  67        u8 deepslp;
  68};
  69
  70static struct quirk_entry *quirks;
  71
  72static struct quirk_entry quirk_unknown = {
  73        .num_zones = 2,
  74        .hdmi_mux = 0,
  75        .amplifier = 0,
  76        .deepslp = 0,
  77};
  78
  79static struct quirk_entry quirk_x51_r1_r2 = {
  80        .num_zones = 3,
  81        .hdmi_mux = 0,
  82        .amplifier = 0,
  83        .deepslp = 0,
  84};
  85
  86static struct quirk_entry quirk_x51_r3 = {
  87        .num_zones = 4,
  88        .hdmi_mux = 0,
  89        .amplifier = 1,
  90        .deepslp = 0,
  91};
  92
  93static struct quirk_entry quirk_asm100 = {
  94        .num_zones = 2,
  95        .hdmi_mux = 1,
  96        .amplifier = 0,
  97        .deepslp = 0,
  98};
  99
 100static struct quirk_entry quirk_asm200 = {
 101        .num_zones = 2,
 102        .hdmi_mux = 1,
 103        .amplifier = 0,
 104        .deepslp = 1,
 105};
 106
 107static struct quirk_entry quirk_asm201 = {
 108        .num_zones = 2,
 109        .hdmi_mux = 1,
 110        .amplifier = 1,
 111        .deepslp = 1,
 112};
 113
 114static int __init dmi_matched(const struct dmi_system_id *dmi)
 115{
 116        quirks = dmi->driver_data;
 117        return 1;
 118}
 119
 120static const struct dmi_system_id alienware_quirks[] __initconst = {
 121        {
 122         .callback = dmi_matched,
 123         .ident = "Alienware X51 R3",
 124         .matches = {
 125                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 126                     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
 127                     },
 128         .driver_data = &quirk_x51_r3,
 129         },
 130        {
 131         .callback = dmi_matched,
 132         .ident = "Alienware X51 R2",
 133         .matches = {
 134                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 135                     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
 136                     },
 137         .driver_data = &quirk_x51_r1_r2,
 138         },
 139        {
 140         .callback = dmi_matched,
 141         .ident = "Alienware X51 R1",
 142         .matches = {
 143                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 144                     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
 145                     },
 146         .driver_data = &quirk_x51_r1_r2,
 147         },
 148        {
 149         .callback = dmi_matched,
 150         .ident = "Alienware ASM100",
 151         .matches = {
 152                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 153                     DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
 154                     },
 155         .driver_data = &quirk_asm100,
 156         },
 157        {
 158         .callback = dmi_matched,
 159         .ident = "Alienware ASM200",
 160         .matches = {
 161                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 162                     DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
 163                     },
 164         .driver_data = &quirk_asm200,
 165         },
 166        {
 167         .callback = dmi_matched,
 168         .ident = "Alienware ASM201",
 169         .matches = {
 170                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 171                     DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
 172                     },
 173         .driver_data = &quirk_asm201,
 174         },
 175        {}
 176};
 177
 178struct color_platform {
 179        u8 blue;
 180        u8 green;
 181        u8 red;
 182} __packed;
 183
 184struct platform_zone {
 185        u8 location;
 186        struct device_attribute *attr;
 187        struct color_platform colors;
 188};
 189
 190struct wmax_brightness_args {
 191        u32 led_mask;
 192        u32 percentage;
 193};
 194
 195struct wmax_basic_args {
 196        u8 arg;
 197};
 198
 199struct legacy_led_args {
 200        struct color_platform colors;
 201        u8 brightness;
 202        u8 state;
 203} __packed;
 204
 205struct wmax_led_args {
 206        u32 led_mask;
 207        struct color_platform colors;
 208        u8 state;
 209} __packed;
 210
 211static struct platform_device *platform_device;
 212static struct device_attribute *zone_dev_attrs;
 213static struct attribute **zone_attrs;
 214static struct platform_zone *zone_data;
 215
 216static struct platform_driver platform_driver = {
 217        .driver = {
 218                   .name = "alienware-wmi",
 219                   }
 220};
 221
 222static struct attribute_group zone_attribute_group = {
 223        .name = "rgb_zones",
 224};
 225
 226static u8 interface;
 227static u8 lighting_control_state;
 228static u8 global_brightness;
 229
 230/*
 231 * Helpers used for zone control
 232 */
 233static int parse_rgb(const char *buf, struct platform_zone *zone)
 234{
 235        long unsigned int rgb;
 236        int ret;
 237        union color_union {
 238                struct color_platform cp;
 239                int package;
 240        } repackager;
 241
 242        ret = kstrtoul(buf, 16, &rgb);
 243        if (ret)
 244                return ret;
 245
 246        /* RGB triplet notation is 24-bit hexadecimal */
 247        if (rgb > 0xFFFFFF)
 248                return -EINVAL;
 249
 250        repackager.package = rgb & 0x0f0f0f0f;
 251        pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
 252                 repackager.cp.red, repackager.cp.green, repackager.cp.blue);
 253        zone->colors = repackager.cp;
 254        return 0;
 255}
 256
 257static struct platform_zone *match_zone(struct device_attribute *attr)
 258{
 259        int i;
 260        for (i = 0; i < quirks->num_zones; i++) {
 261                if ((struct device_attribute *)zone_data[i].attr == attr) {
 262                        pr_debug("alienware-wmi: matched zone location: %d\n",
 263                                 zone_data[i].location);
 264                        return &zone_data[i];
 265                }
 266        }
 267        return NULL;
 268}
 269
 270/*
 271 * Individual RGB zone control
 272 */
 273static int alienware_update_led(struct platform_zone *zone)
 274{
 275        int method_id;
 276        acpi_status status;
 277        char *guid;
 278        struct acpi_buffer input;
 279        struct legacy_led_args legacy_args;
 280        struct wmax_led_args wmax_basic_args;
 281        if (interface == WMAX) {
 282                wmax_basic_args.led_mask = 1 << zone->location;
 283                wmax_basic_args.colors = zone->colors;
 284                wmax_basic_args.state = lighting_control_state;
 285                guid = WMAX_CONTROL_GUID;
 286                method_id = WMAX_METHOD_ZONE_CONTROL;
 287
 288                input.length = (acpi_size) sizeof(wmax_basic_args);
 289                input.pointer = &wmax_basic_args;
 290        } else {
 291                legacy_args.colors = zone->colors;
 292                legacy_args.brightness = global_brightness;
 293                legacy_args.state = 0;
 294                if (lighting_control_state == LEGACY_BOOTING ||
 295                    lighting_control_state == LEGACY_SUSPEND) {
 296                        guid = LEGACY_POWER_CONTROL_GUID;
 297                        legacy_args.state = lighting_control_state;
 298                } else
 299                        guid = LEGACY_CONTROL_GUID;
 300                method_id = zone->location + 1;
 301
 302                input.length = (acpi_size) sizeof(legacy_args);
 303                input.pointer = &legacy_args;
 304        }
 305        pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
 306
 307        status = wmi_evaluate_method(guid, 1, method_id, &input, NULL);
 308        if (ACPI_FAILURE(status))
 309                pr_err("alienware-wmi: zone set failure: %u\n", status);
 310        return ACPI_FAILURE(status);
 311}
 312
 313static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
 314                         char *buf)
 315{
 316        struct platform_zone *target_zone;
 317        target_zone = match_zone(attr);
 318        if (target_zone == NULL)
 319                return sprintf(buf, "red: -1, green: -1, blue: -1\n");
 320        return sprintf(buf, "red: %d, green: %d, blue: %d\n",
 321                       target_zone->colors.red,
 322                       target_zone->colors.green, target_zone->colors.blue);
 323
 324}
 325
 326static ssize_t zone_set(struct device *dev, struct device_attribute *attr,
 327                        const char *buf, size_t count)
 328{
 329        struct platform_zone *target_zone;
 330        int ret;
 331        target_zone = match_zone(attr);
 332        if (target_zone == NULL) {
 333                pr_err("alienware-wmi: invalid target zone\n");
 334                return 1;
 335        }
 336        ret = parse_rgb(buf, target_zone);
 337        if (ret)
 338                return ret;
 339        ret = alienware_update_led(target_zone);
 340        return ret ? ret : count;
 341}
 342
 343/*
 344 * LED Brightness (Global)
 345 */
 346static int wmax_brightness(int brightness)
 347{
 348        acpi_status status;
 349        struct acpi_buffer input;
 350        struct wmax_brightness_args args = {
 351                .led_mask = 0xFF,
 352                .percentage = brightness,
 353        };
 354        input.length = (acpi_size) sizeof(args);
 355        input.pointer = &args;
 356        status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
 357                                     WMAX_METHOD_BRIGHTNESS, &input, NULL);
 358        if (ACPI_FAILURE(status))
 359                pr_err("alienware-wmi: brightness set failure: %u\n", status);
 360        return ACPI_FAILURE(status);
 361}
 362
 363static void global_led_set(struct led_classdev *led_cdev,
 364                           enum led_brightness brightness)
 365{
 366        int ret;
 367        global_brightness = brightness;
 368        if (interface == WMAX)
 369                ret = wmax_brightness(brightness);
 370        else
 371                ret = alienware_update_led(&zone_data[0]);
 372        if (ret)
 373                pr_err("LED brightness update failed\n");
 374}
 375
 376static enum led_brightness global_led_get(struct led_classdev *led_cdev)
 377{
 378        return global_brightness;
 379}
 380
 381static struct led_classdev global_led = {
 382        .brightness_set = global_led_set,
 383        .brightness_get = global_led_get,
 384        .name = "alienware::global_brightness",
 385};
 386
 387/*
 388 * Lighting control state device attribute (Global)
 389 */
 390static ssize_t show_control_state(struct device *dev,
 391                                  struct device_attribute *attr, char *buf)
 392{
 393        if (lighting_control_state == LEGACY_BOOTING)
 394                return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n");
 395        else if (lighting_control_state == LEGACY_SUSPEND)
 396                return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n");
 397        return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n");
 398}
 399
 400static ssize_t store_control_state(struct device *dev,
 401                                   struct device_attribute *attr,
 402                                   const char *buf, size_t count)
 403{
 404        long unsigned int val;
 405        if (strcmp(buf, "booting\n") == 0)
 406                val = LEGACY_BOOTING;
 407        else if (strcmp(buf, "suspend\n") == 0)
 408                val = LEGACY_SUSPEND;
 409        else if (interface == LEGACY)
 410                val = LEGACY_RUNNING;
 411        else
 412                val = WMAX_RUNNING;
 413        lighting_control_state = val;
 414        pr_debug("alienware-wmi: updated control state to %d\n",
 415                 lighting_control_state);
 416        return count;
 417}
 418
 419static DEVICE_ATTR(lighting_control_state, 0644, show_control_state,
 420                   store_control_state);
 421
 422static int alienware_zone_init(struct platform_device *dev)
 423{
 424        int i;
 425        char buffer[10];
 426        char *name;
 427
 428        if (interface == WMAX) {
 429                lighting_control_state = WMAX_RUNNING;
 430        } else if (interface == LEGACY) {
 431                lighting_control_state = LEGACY_RUNNING;
 432        }
 433        global_led.max_brightness = 0x0F;
 434        global_brightness = global_led.max_brightness;
 435
 436        /*
 437         *      - zone_dev_attrs num_zones + 1 is for individual zones and then
 438         *        null terminated
 439         *      - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
 440         *        the lighting control + null terminated
 441         *      - zone_data num_zones is for the distinct zones
 442         */
 443        zone_dev_attrs =
 444            kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1),
 445                    GFP_KERNEL);
 446        if (!zone_dev_attrs)
 447                return -ENOMEM;
 448
 449        zone_attrs =
 450            kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2),
 451                    GFP_KERNEL);
 452        if (!zone_attrs)
 453                return -ENOMEM;
 454
 455        zone_data =
 456            kzalloc(sizeof(struct platform_zone) * (quirks->num_zones),
 457                    GFP_KERNEL);
 458        if (!zone_data)
 459                return -ENOMEM;
 460
 461        for (i = 0; i < quirks->num_zones; i++) {
 462                sprintf(buffer, "zone%02X", i);
 463                name = kstrdup(buffer, GFP_KERNEL);
 464                if (name == NULL)
 465                        return 1;
 466                sysfs_attr_init(&zone_dev_attrs[i].attr);
 467                zone_dev_attrs[i].attr.name = name;
 468                zone_dev_attrs[i].attr.mode = 0644;
 469                zone_dev_attrs[i].show = zone_show;
 470                zone_dev_attrs[i].store = zone_set;
 471                zone_data[i].location = i;
 472                zone_attrs[i] = &zone_dev_attrs[i].attr;
 473                zone_data[i].attr = &zone_dev_attrs[i];
 474        }
 475        zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;
 476        zone_attribute_group.attrs = zone_attrs;
 477
 478        led_classdev_register(&dev->dev, &global_led);
 479
 480        return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);
 481}
 482
 483static void alienware_zone_exit(struct platform_device *dev)
 484{
 485        sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);
 486        led_classdev_unregister(&global_led);
 487        if (zone_dev_attrs) {
 488                int i;
 489                for (i = 0; i < quirks->num_zones; i++)
 490                        kfree(zone_dev_attrs[i].attr.name);
 491        }
 492        kfree(zone_dev_attrs);
 493        kfree(zone_data);
 494        kfree(zone_attrs);
 495}
 496
 497static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,
 498                                          u32 command, int *out_data)
 499{
 500        acpi_status status;
 501        union acpi_object *obj;
 502        struct acpi_buffer input;
 503        struct acpi_buffer output;
 504
 505        input.length = (acpi_size) sizeof(*in_args);
 506        input.pointer = in_args;
 507        if (out_data != NULL) {
 508                output.length = ACPI_ALLOCATE_BUFFER;
 509                output.pointer = NULL;
 510                status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
 511                                             command, &input, &output);
 512        } else
 513                status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1,
 514                                             command, &input, NULL);
 515
 516        if (ACPI_SUCCESS(status) && out_data != NULL) {
 517                obj = (union acpi_object *)output.pointer;
 518                if (obj && obj->type == ACPI_TYPE_INTEGER)
 519                        *out_data = (u32) obj->integer.value;
 520        }
 521        return status;
 522
 523}
 524
 525/*
 526 *      The HDMI mux sysfs node indicates the status of the HDMI input mux.
 527 *      It can toggle between standard system GPU output and HDMI input.
 528 */
 529static ssize_t show_hdmi_cable(struct device *dev,
 530                               struct device_attribute *attr, char *buf)
 531{
 532        acpi_status status;
 533        u32 out_data;
 534        struct wmax_basic_args in_args = {
 535                .arg = 0,
 536        };
 537        status =
 538            alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,
 539                                   (u32 *) &out_data);
 540        if (ACPI_SUCCESS(status)) {
 541                if (out_data == 0)
 542                        return scnprintf(buf, PAGE_SIZE,
 543                                         "[unconnected] connected unknown\n");
 544                else if (out_data == 1)
 545                        return scnprintf(buf, PAGE_SIZE,
 546                                         "unconnected [connected] unknown\n");
 547        }
 548        pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
 549        return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
 550}
 551
 552static ssize_t show_hdmi_source(struct device *dev,
 553                                struct device_attribute *attr, char *buf)
 554{
 555        acpi_status status;
 556        u32 out_data;
 557        struct wmax_basic_args in_args = {
 558                .arg = 0,
 559        };
 560        status =
 561            alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,
 562                                   (u32 *) &out_data);
 563
 564        if (ACPI_SUCCESS(status)) {
 565                if (out_data == 1)
 566                        return scnprintf(buf, PAGE_SIZE,
 567                                         "[input] gpu unknown\n");
 568                else if (out_data == 2)
 569                        return scnprintf(buf, PAGE_SIZE,
 570                                         "input [gpu] unknown\n");
 571        }
 572        pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data);
 573        return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n");
 574}
 575
 576static ssize_t toggle_hdmi_source(struct device *dev,
 577                                  struct device_attribute *attr,
 578                                  const char *buf, size_t count)
 579{
 580        acpi_status status;
 581        struct wmax_basic_args args;
 582        if (strcmp(buf, "gpu\n") == 0)
 583                args.arg = 1;
 584        else if (strcmp(buf, "input\n") == 0)
 585                args.arg = 2;
 586        else
 587                args.arg = 3;
 588        pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
 589
 590        status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
 591
 592        if (ACPI_FAILURE(status))
 593                pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
 594                       status);
 595        return count;
 596}
 597
 598static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);
 599static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,
 600                   toggle_hdmi_source);
 601
 602static struct attribute *hdmi_attrs[] = {
 603        &dev_attr_cable.attr,
 604        &dev_attr_source.attr,
 605        NULL,
 606};
 607
 608static struct attribute_group hdmi_attribute_group = {
 609        .name = "hdmi",
 610        .attrs = hdmi_attrs,
 611};
 612
 613static void remove_hdmi(struct platform_device *dev)
 614{
 615        if (quirks->hdmi_mux > 0)
 616                sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);
 617}
 618
 619static int create_hdmi(struct platform_device *dev)
 620{
 621        int ret;
 622
 623        ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
 624        if (ret)
 625                remove_hdmi(dev);
 626        return ret;
 627}
 628
 629/*
 630 * Alienware GFX amplifier support
 631 * - Currently supports reading cable status
 632 * - Leaving expansion room to possibly support dock/undock events later
 633 */
 634static ssize_t show_amplifier_status(struct device *dev,
 635                                     struct device_attribute *attr, char *buf)
 636{
 637        acpi_status status;
 638        u32 out_data;
 639        struct wmax_basic_args in_args = {
 640                .arg = 0,
 641        };
 642        status =
 643            alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,
 644                                   (u32 *) &out_data);
 645        if (ACPI_SUCCESS(status)) {
 646                if (out_data == 0)
 647                        return scnprintf(buf, PAGE_SIZE,
 648                                         "[unconnected] connected unknown\n");
 649                else if (out_data == 1)
 650                        return scnprintf(buf, PAGE_SIZE,
 651                                         "unconnected [connected] unknown\n");
 652        }
 653        pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);
 654        return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
 655}
 656
 657static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL);
 658
 659static struct attribute *amplifier_attrs[] = {
 660        &dev_attr_status.attr,
 661        NULL,
 662};
 663
 664static struct attribute_group amplifier_attribute_group = {
 665        .name = "amplifier",
 666        .attrs = amplifier_attrs,
 667};
 668
 669static void remove_amplifier(struct platform_device *dev)
 670{
 671        if (quirks->amplifier > 0)
 672                sysfs_remove_group(&dev->dev.kobj, &amplifier_attribute_group);
 673}
 674
 675static int create_amplifier(struct platform_device *dev)
 676{
 677        int ret;
 678
 679        ret = sysfs_create_group(&dev->dev.kobj, &amplifier_attribute_group);
 680        if (ret)
 681                remove_amplifier(dev);
 682        return ret;
 683}
 684
 685/*
 686 * Deep Sleep Control support
 687 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
 688 */
 689static ssize_t show_deepsleep_status(struct device *dev,
 690                                     struct device_attribute *attr, char *buf)
 691{
 692        acpi_status status;
 693        u32 out_data;
 694        struct wmax_basic_args in_args = {
 695                .arg = 0,
 696        };
 697        status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,
 698                                        (u32 *) &out_data);
 699        if (ACPI_SUCCESS(status)) {
 700                if (out_data == 0)
 701                        return scnprintf(buf, PAGE_SIZE,
 702                                         "[disabled] s5 s5_s4\n");
 703                else if (out_data == 1)
 704                        return scnprintf(buf, PAGE_SIZE,
 705                                         "disabled [s5] s5_s4\n");
 706                else if (out_data == 2)
 707                        return scnprintf(buf, PAGE_SIZE,
 708                                         "disabled s5 [s5_s4]\n");
 709        }
 710        pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);
 711        return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n");
 712}
 713
 714static ssize_t toggle_deepsleep(struct device *dev,
 715                                struct device_attribute *attr,
 716                                const char *buf, size_t count)
 717{
 718        acpi_status status;
 719        struct wmax_basic_args args;
 720
 721        if (strcmp(buf, "disabled\n") == 0)
 722                args.arg = 0;
 723        else if (strcmp(buf, "s5\n") == 0)
 724                args.arg = 1;
 725        else
 726                args.arg = 2;
 727        pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
 728
 729        status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,
 730                                        NULL);
 731
 732        if (ACPI_FAILURE(status))
 733                pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
 734                        status);
 735        return count;
 736}
 737
 738static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep);
 739
 740static struct attribute *deepsleep_attrs[] = {
 741        &dev_attr_deepsleep.attr,
 742        NULL,
 743};
 744
 745static struct attribute_group deepsleep_attribute_group = {
 746        .name = "deepsleep",
 747        .attrs = deepsleep_attrs,
 748};
 749
 750static void remove_deepsleep(struct platform_device *dev)
 751{
 752        if (quirks->deepslp > 0)
 753                sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group);
 754}
 755
 756static int create_deepsleep(struct platform_device *dev)
 757{
 758        int ret;
 759
 760        ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group);
 761        if (ret)
 762                remove_deepsleep(dev);
 763        return ret;
 764}
 765
 766static int __init alienware_wmi_init(void)
 767{
 768        int ret;
 769
 770        if (wmi_has_guid(LEGACY_CONTROL_GUID))
 771                interface = LEGACY;
 772        else if (wmi_has_guid(WMAX_CONTROL_GUID))
 773                interface = WMAX;
 774        else {
 775                pr_warn("alienware-wmi: No known WMI GUID found\n");
 776                return -ENODEV;
 777        }
 778
 779        dmi_check_system(alienware_quirks);
 780        if (quirks == NULL)
 781                quirks = &quirk_unknown;
 782
 783        ret = platform_driver_register(&platform_driver);
 784        if (ret)
 785                goto fail_platform_driver;
 786        platform_device = platform_device_alloc("alienware-wmi", -1);
 787        if (!platform_device) {
 788                ret = -ENOMEM;
 789                goto fail_platform_device1;
 790        }
 791        ret = platform_device_add(platform_device);
 792        if (ret)
 793                goto fail_platform_device2;
 794
 795        if (quirks->hdmi_mux > 0) {
 796                ret = create_hdmi(platform_device);
 797                if (ret)
 798                        goto fail_prep_hdmi;
 799        }
 800
 801        if (quirks->amplifier > 0) {
 802                ret = create_amplifier(platform_device);
 803                if (ret)
 804                        goto fail_prep_amplifier;
 805        }
 806
 807        if (quirks->deepslp > 0) {
 808                ret = create_deepsleep(platform_device);
 809                if (ret)
 810                        goto fail_prep_deepsleep;
 811        }
 812
 813        ret = alienware_zone_init(platform_device);
 814        if (ret)
 815                goto fail_prep_zones;
 816
 817        return 0;
 818
 819fail_prep_zones:
 820        alienware_zone_exit(platform_device);
 821fail_prep_deepsleep:
 822fail_prep_amplifier:
 823fail_prep_hdmi:
 824        platform_device_del(platform_device);
 825fail_platform_device2:
 826        platform_device_put(platform_device);
 827fail_platform_device1:
 828        platform_driver_unregister(&platform_driver);
 829fail_platform_driver:
 830        return ret;
 831}
 832
 833module_init(alienware_wmi_init);
 834
 835static void __exit alienware_wmi_exit(void)
 836{
 837        if (platform_device) {
 838                alienware_zone_exit(platform_device);
 839                remove_hdmi(platform_device);
 840                platform_device_unregister(platform_device);
 841                platform_driver_unregister(&platform_driver);
 842        }
 843}
 844
 845module_exit(alienware_wmi_exit);
 846