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