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