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        kfree(output.pointer);
 540        return status;
 541
 542}
 543
 544/*
 545 *      The HDMI mux sysfs node indicates the status of the HDMI input mux.
 546 *      It can toggle between standard system GPU output and HDMI input.
 547 */
 548static ssize_t show_hdmi_cable(struct device *dev,
 549                               struct device_attribute *attr, char *buf)
 550{
 551        acpi_status status;
 552        u32 out_data;
 553        struct wmax_basic_args in_args = {
 554                .arg = 0,
 555        };
 556        status =
 557            alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,
 558                                   (u32 *) &out_data);
 559        if (ACPI_SUCCESS(status)) {
 560                if (out_data == 0)
 561                        return scnprintf(buf, PAGE_SIZE,
 562                                         "[unconnected] connected unknown\n");
 563                else if (out_data == 1)
 564                        return scnprintf(buf, PAGE_SIZE,
 565                                         "unconnected [connected] unknown\n");
 566        }
 567        pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
 568        return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
 569}
 570
 571static ssize_t show_hdmi_source(struct device *dev,
 572                                struct device_attribute *attr, char *buf)
 573{
 574        acpi_status status;
 575        u32 out_data;
 576        struct wmax_basic_args in_args = {
 577                .arg = 0,
 578        };
 579        status =
 580            alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,
 581                                   (u32 *) &out_data);
 582
 583        if (ACPI_SUCCESS(status)) {
 584                if (out_data == 1)
 585                        return scnprintf(buf, PAGE_SIZE,
 586                                         "[input] gpu unknown\n");
 587                else if (out_data == 2)
 588                        return scnprintf(buf, PAGE_SIZE,
 589                                         "input [gpu] unknown\n");
 590        }
 591        pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data);
 592        return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n");
 593}
 594
 595static ssize_t toggle_hdmi_source(struct device *dev,
 596                                  struct device_attribute *attr,
 597                                  const char *buf, size_t count)
 598{
 599        acpi_status status;
 600        struct wmax_basic_args args;
 601        if (strcmp(buf, "gpu\n") == 0)
 602                args.arg = 1;
 603        else if (strcmp(buf, "input\n") == 0)
 604                args.arg = 2;
 605        else
 606                args.arg = 3;
 607        pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
 608
 609        status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
 610
 611        if (ACPI_FAILURE(status))
 612                pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
 613                       status);
 614        return count;
 615}
 616
 617static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);
 618static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,
 619                   toggle_hdmi_source);
 620
 621static struct attribute *hdmi_attrs[] = {
 622        &dev_attr_cable.attr,
 623        &dev_attr_source.attr,
 624        NULL,
 625};
 626
 627static const struct attribute_group hdmi_attribute_group = {
 628        .name = "hdmi",
 629        .attrs = hdmi_attrs,
 630};
 631
 632static void remove_hdmi(struct platform_device *dev)
 633{
 634        if (quirks->hdmi_mux > 0)
 635                sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);
 636}
 637
 638static int create_hdmi(struct platform_device *dev)
 639{
 640        int ret;
 641
 642        ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
 643        if (ret)
 644                remove_hdmi(dev);
 645        return ret;
 646}
 647
 648/*
 649 * Alienware GFX amplifier support
 650 * - Currently supports reading cable status
 651 * - Leaving expansion room to possibly support dock/undock events later
 652 */
 653static ssize_t show_amplifier_status(struct device *dev,
 654                                     struct device_attribute *attr, char *buf)
 655{
 656        acpi_status status;
 657        u32 out_data;
 658        struct wmax_basic_args in_args = {
 659                .arg = 0,
 660        };
 661        status =
 662            alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,
 663                                   (u32 *) &out_data);
 664        if (ACPI_SUCCESS(status)) {
 665                if (out_data == 0)
 666                        return scnprintf(buf, PAGE_SIZE,
 667                                         "[unconnected] connected unknown\n");
 668                else if (out_data == 1)
 669                        return scnprintf(buf, PAGE_SIZE,
 670                                         "unconnected [connected] unknown\n");
 671        }
 672        pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);
 673        return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
 674}
 675
 676static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL);
 677
 678static struct attribute *amplifier_attrs[] = {
 679        &dev_attr_status.attr,
 680        NULL,
 681};
 682
 683static const struct attribute_group amplifier_attribute_group = {
 684        .name = "amplifier",
 685        .attrs = amplifier_attrs,
 686};
 687
 688static void remove_amplifier(struct platform_device *dev)
 689{
 690        if (quirks->amplifier > 0)
 691                sysfs_remove_group(&dev->dev.kobj, &amplifier_attribute_group);
 692}
 693
 694static int create_amplifier(struct platform_device *dev)
 695{
 696        int ret;
 697
 698        ret = sysfs_create_group(&dev->dev.kobj, &amplifier_attribute_group);
 699        if (ret)
 700                remove_amplifier(dev);
 701        return ret;
 702}
 703
 704/*
 705 * Deep Sleep Control support
 706 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
 707 */
 708static ssize_t show_deepsleep_status(struct device *dev,
 709                                     struct device_attribute *attr, char *buf)
 710{
 711        acpi_status status;
 712        u32 out_data;
 713        struct wmax_basic_args in_args = {
 714                .arg = 0,
 715        };
 716        status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,
 717                                        (u32 *) &out_data);
 718        if (ACPI_SUCCESS(status)) {
 719                if (out_data == 0)
 720                        return scnprintf(buf, PAGE_SIZE,
 721                                         "[disabled] s5 s5_s4\n");
 722                else if (out_data == 1)
 723                        return scnprintf(buf, PAGE_SIZE,
 724                                         "disabled [s5] s5_s4\n");
 725                else if (out_data == 2)
 726                        return scnprintf(buf, PAGE_SIZE,
 727                                         "disabled s5 [s5_s4]\n");
 728        }
 729        pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);
 730        return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n");
 731}
 732
 733static ssize_t toggle_deepsleep(struct device *dev,
 734                                struct device_attribute *attr,
 735                                const char *buf, size_t count)
 736{
 737        acpi_status status;
 738        struct wmax_basic_args args;
 739
 740        if (strcmp(buf, "disabled\n") == 0)
 741                args.arg = 0;
 742        else if (strcmp(buf, "s5\n") == 0)
 743                args.arg = 1;
 744        else
 745                args.arg = 2;
 746        pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
 747
 748        status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,
 749                                        NULL);
 750
 751        if (ACPI_FAILURE(status))
 752                pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
 753                        status);
 754        return count;
 755}
 756
 757static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep);
 758
 759static struct attribute *deepsleep_attrs[] = {
 760        &dev_attr_deepsleep.attr,
 761        NULL,
 762};
 763
 764static const struct attribute_group deepsleep_attribute_group = {
 765        .name = "deepsleep",
 766        .attrs = deepsleep_attrs,
 767};
 768
 769static void remove_deepsleep(struct platform_device *dev)
 770{
 771        if (quirks->deepslp > 0)
 772                sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group);
 773}
 774
 775static int create_deepsleep(struct platform_device *dev)
 776{
 777        int ret;
 778
 779        ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group);
 780        if (ret)
 781                remove_deepsleep(dev);
 782        return ret;
 783}
 784
 785static int __init alienware_wmi_init(void)
 786{
 787        int ret;
 788
 789        if (wmi_has_guid(LEGACY_CONTROL_GUID))
 790                interface = LEGACY;
 791        else if (wmi_has_guid(WMAX_CONTROL_GUID))
 792                interface = WMAX;
 793        else {
 794                pr_warn("alienware-wmi: No known WMI GUID found\n");
 795                return -ENODEV;
 796        }
 797
 798        dmi_check_system(alienware_quirks);
 799        if (quirks == NULL)
 800                quirks = &quirk_unknown;
 801
 802        ret = platform_driver_register(&platform_driver);
 803        if (ret)
 804                goto fail_platform_driver;
 805        platform_device = platform_device_alloc("alienware-wmi", -1);
 806        if (!platform_device) {
 807                ret = -ENOMEM;
 808                goto fail_platform_device1;
 809        }
 810        ret = platform_device_add(platform_device);
 811        if (ret)
 812                goto fail_platform_device2;
 813
 814        if (quirks->hdmi_mux > 0) {
 815                ret = create_hdmi(platform_device);
 816                if (ret)
 817                        goto fail_prep_hdmi;
 818        }
 819
 820        if (quirks->amplifier > 0) {
 821                ret = create_amplifier(platform_device);
 822                if (ret)
 823                        goto fail_prep_amplifier;
 824        }
 825
 826        if (quirks->deepslp > 0) {
 827                ret = create_deepsleep(platform_device);
 828                if (ret)
 829                        goto fail_prep_deepsleep;
 830        }
 831
 832        ret = alienware_zone_init(platform_device);
 833        if (ret)
 834                goto fail_prep_zones;
 835
 836        return 0;
 837
 838fail_prep_zones:
 839        alienware_zone_exit(platform_device);
 840fail_prep_deepsleep:
 841fail_prep_amplifier:
 842fail_prep_hdmi:
 843        platform_device_del(platform_device);
 844fail_platform_device2:
 845        platform_device_put(platform_device);
 846fail_platform_device1:
 847        platform_driver_unregister(&platform_driver);
 848fail_platform_driver:
 849        return ret;
 850}
 851
 852module_init(alienware_wmi_init);
 853
 854static void __exit alienware_wmi_exit(void)
 855{
 856        if (platform_device) {
 857                alienware_zone_exit(platform_device);
 858                remove_hdmi(platform_device);
 859                platform_device_unregister(platform_device);
 860                platform_driver_unregister(&platform_driver);
 861        }
 862}
 863
 864module_exit(alienware_wmi_exit);
 865