843 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Alienware AlienFX control4 *5 * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 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 0x121#define WMAX_METHOD_HDMI_STATUS 0x222#define WMAX_METHOD_BRIGHTNESS 0x323#define WMAX_METHOD_ZONE_CONTROL 0x424#define WMAX_METHOD_HDMI_CABLE 0x525#define WMAX_METHOD_AMPLIFIER_CABLE 0x626#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B27#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C28 29MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.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 control238 */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 control279 */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 } else306 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 else378 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 sysfs_emit(buf, "[booting] running suspend\n");402 else if (lighting_control_state == LEGACY_SUSPEND)403 return sysfs_emit(buf, "booting running [suspend]\n");404 return sysfs_emit(buf, "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 else419 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 *name;433 434 if (interface == WMAX) {435 lighting_control_state = WMAX_RUNNING;436 } else if (interface == LEGACY) {437 lighting_control_state = LEGACY_RUNNING;438 }439 global_led.max_brightness = 0x0F;440 global_brightness = global_led.max_brightness;441 442 /*443 * - zone_dev_attrs num_zones + 1 is for individual zones and then444 * null terminated445 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +446 * the lighting control + null terminated447 * - zone_data num_zones is for the distinct zones448 */449 zone_dev_attrs =450 kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute),451 GFP_KERNEL);452 if (!zone_dev_attrs)453 return -ENOMEM;454 455 zone_attrs =456 kcalloc(quirks->num_zones + 2, sizeof(struct attribute *),457 GFP_KERNEL);458 if (!zone_attrs)459 return -ENOMEM;460 461 zone_data =462 kcalloc(quirks->num_zones, sizeof(struct platform_zone),463 GFP_KERNEL);464 if (!zone_data)465 return -ENOMEM;466 467 for (zone = 0; zone < quirks->num_zones; zone++) {468 name = kasprintf(GFP_KERNEL, "zone%02hhX", zone);469 if (name == NULL)470 return 1;471 sysfs_attr_init(&zone_dev_attrs[zone].attr);472 zone_dev_attrs[zone].attr.name = name;473 zone_dev_attrs[zone].attr.mode = 0644;474 zone_dev_attrs[zone].show = zone_show;475 zone_dev_attrs[zone].store = zone_set;476 zone_data[zone].location = zone;477 zone_attrs[zone] = &zone_dev_attrs[zone].attr;478 zone_data[zone].attr = &zone_dev_attrs[zone];479 }480 zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;481 zone_attribute_group.attrs = zone_attrs;482 483 led_classdev_register(&dev->dev, &global_led);484 485 return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);486}487 488static void alienware_zone_exit(struct platform_device *dev)489{490 u8 zone;491 492 sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);493 led_classdev_unregister(&global_led);494 if (zone_dev_attrs) {495 for (zone = 0; zone < quirks->num_zones; zone++)496 kfree(zone_dev_attrs[zone].attr.name);497 }498 kfree(zone_dev_attrs);499 kfree(zone_data);500 kfree(zone_attrs);501}502 503static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,504 u32 command, int *out_data)505{506 acpi_status status;507 union acpi_object *obj;508 struct acpi_buffer input;509 struct acpi_buffer output;510 511 input.length = (acpi_size) sizeof(*in_args);512 input.pointer = in_args;513 if (out_data) {514 output.length = ACPI_ALLOCATE_BUFFER;515 output.pointer = NULL;516 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,517 command, &input, &output);518 if (ACPI_SUCCESS(status)) {519 obj = (union acpi_object *)output.pointer;520 if (obj && obj->type == ACPI_TYPE_INTEGER)521 *out_data = (u32)obj->integer.value;522 }523 kfree(output.pointer);524 } else {525 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,526 command, &input, NULL);527 }528 return status;529}530 531/*532 * The HDMI mux sysfs node indicates the status of the HDMI input mux.533 * It can toggle between standard system GPU output and HDMI input.534 */535static ssize_t show_hdmi_cable(struct device *dev,536 struct device_attribute *attr, char *buf)537{538 acpi_status status;539 u32 out_data;540 struct wmax_basic_args in_args = {541 .arg = 0,542 };543 status =544 alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,545 (u32 *) &out_data);546 if (ACPI_SUCCESS(status)) {547 if (out_data == 0)548 return sysfs_emit(buf, "[unconnected] connected unknown\n");549 else if (out_data == 1)550 return sysfs_emit(buf, "unconnected [connected] unknown\n");551 }552 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);553 return sysfs_emit(buf, "unconnected connected [unknown]\n");554}555 556static ssize_t show_hdmi_source(struct device *dev,557 struct device_attribute *attr, char *buf)558{559 acpi_status status;560 u32 out_data;561 struct wmax_basic_args in_args = {562 .arg = 0,563 };564 status =565 alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,566 (u32 *) &out_data);567 568 if (ACPI_SUCCESS(status)) {569 if (out_data == 1)570 return sysfs_emit(buf, "[input] gpu unknown\n");571 else if (out_data == 2)572 return sysfs_emit(buf, "input [gpu] unknown\n");573 }574 pr_err("alienware-wmi: unknown HDMI source status: %u\n", status);575 return sysfs_emit(buf, "input gpu [unknown]\n");576}577 578static ssize_t toggle_hdmi_source(struct device *dev,579 struct device_attribute *attr,580 const char *buf, size_t count)581{582 acpi_status status;583 struct wmax_basic_args args;584 if (strcmp(buf, "gpu\n") == 0)585 args.arg = 1;586 else if (strcmp(buf, "input\n") == 0)587 args.arg = 2;588 else589 args.arg = 3;590 pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);591 592 status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);593 594 if (ACPI_FAILURE(status))595 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",596 status);597 return count;598}599 600static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);601static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,602 toggle_hdmi_source);603 604static struct attribute *hdmi_attrs[] = {605 &dev_attr_cable.attr,606 &dev_attr_source.attr,607 NULL,608};609 610static const struct attribute_group hdmi_attribute_group = {611 .name = "hdmi",612 .attrs = hdmi_attrs,613};614 615static void remove_hdmi(struct platform_device *dev)616{617 if (quirks->hdmi_mux > 0)618 sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);619}620 621static int create_hdmi(struct platform_device *dev)622{623 int ret;624 625 ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);626 if (ret)627 remove_hdmi(dev);628 return ret;629}630 631/*632 * Alienware GFX amplifier support633 * - Currently supports reading cable status634 * - Leaving expansion room to possibly support dock/undock events later635 */636static ssize_t show_amplifier_status(struct device *dev,637 struct device_attribute *attr, char *buf)638{639 acpi_status status;640 u32 out_data;641 struct wmax_basic_args in_args = {642 .arg = 0,643 };644 status =645 alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,646 (u32 *) &out_data);647 if (ACPI_SUCCESS(status)) {648 if (out_data == 0)649 return sysfs_emit(buf, "[unconnected] connected unknown\n");650 else if (out_data == 1)651 return sysfs_emit(buf, "unconnected [connected] unknown\n");652 }653 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);654 return sysfs_emit(buf, "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 const 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, &lifier_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, &lifier_attribute_group);680 if (ret)681 remove_amplifier(dev);682 return ret;683}684 685/*686 * Deep Sleep Control support687 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events688 */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 sysfs_emit(buf, "[disabled] s5 s5_s4\n");702 else if (out_data == 1)703 return sysfs_emit(buf, "disabled [s5] s5_s4\n");704 else if (out_data == 2)705 return sysfs_emit(buf, "disabled s5 [s5_s4]\n");706 }707 pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);708 return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n");709}710 711static ssize_t toggle_deepsleep(struct device *dev,712 struct device_attribute *attr,713 const char *buf, size_t count)714{715 acpi_status status;716 struct wmax_basic_args args;717 718 if (strcmp(buf, "disabled\n") == 0)719 args.arg = 0;720 else if (strcmp(buf, "s5\n") == 0)721 args.arg = 1;722 else723 args.arg = 2;724 pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);725 726 status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,727 NULL);728 729 if (ACPI_FAILURE(status))730 pr_err("alienware-wmi: deep sleep control failed: results: %u\n",731 status);732 return count;733}734 735static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep);736 737static struct attribute *deepsleep_attrs[] = {738 &dev_attr_deepsleep.attr,739 NULL,740};741 742static const struct attribute_group deepsleep_attribute_group = {743 .name = "deepsleep",744 .attrs = deepsleep_attrs,745};746 747static void remove_deepsleep(struct platform_device *dev)748{749 if (quirks->deepslp > 0)750 sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group);751}752 753static int create_deepsleep(struct platform_device *dev)754{755 int ret;756 757 ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group);758 if (ret)759 remove_deepsleep(dev);760 return ret;761}762 763static int __init alienware_wmi_init(void)764{765 int ret;766 767 if (wmi_has_guid(LEGACY_CONTROL_GUID))768 interface = LEGACY;769 else if (wmi_has_guid(WMAX_CONTROL_GUID))770 interface = WMAX;771 else {772 pr_warn("alienware-wmi: No known WMI GUID found\n");773 return -ENODEV;774 }775 776 dmi_check_system(alienware_quirks);777 if (quirks == NULL)778 quirks = &quirk_unknown;779 780 ret = platform_driver_register(&platform_driver);781 if (ret)782 goto fail_platform_driver;783 platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE);784 if (!platform_device) {785 ret = -ENOMEM;786 goto fail_platform_device1;787 }788 ret = platform_device_add(platform_device);789 if (ret)790 goto fail_platform_device2;791 792 if (quirks->hdmi_mux > 0) {793 ret = create_hdmi(platform_device);794 if (ret)795 goto fail_prep_hdmi;796 }797 798 if (quirks->amplifier > 0) {799 ret = create_amplifier(platform_device);800 if (ret)801 goto fail_prep_amplifier;802 }803 804 if (quirks->deepslp > 0) {805 ret = create_deepsleep(platform_device);806 if (ret)807 goto fail_prep_deepsleep;808 }809 810 ret = alienware_zone_init(platform_device);811 if (ret)812 goto fail_prep_zones;813 814 return 0;815 816fail_prep_zones:817 alienware_zone_exit(platform_device);818fail_prep_deepsleep:819fail_prep_amplifier:820fail_prep_hdmi:821 platform_device_del(platform_device);822fail_platform_device2:823 platform_device_put(platform_device);824fail_platform_device1:825 platform_driver_unregister(&platform_driver);826fail_platform_driver:827 return ret;828}829 830module_init(alienware_wmi_init);831 832static void __exit alienware_wmi_exit(void)833{834 if (platform_device) {835 alienware_zone_exit(platform_device);836 remove_hdmi(platform_device);837 platform_device_unregister(platform_device);838 platform_driver_unregister(&platform_driver);839 }840}841 842module_exit(alienware_wmi_exit);843