Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Alienware AlienFX control
0004  *
0005  * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>
0006  */
0007 
0008 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0009 
0010 #include <linux/acpi.h>
0011 #include <linux/module.h>
0012 #include <linux/platform_device.h>
0013 #include <linux/dmi.h>
0014 #include <linux/leds.h>
0015 
0016 #define LEGACY_CONTROL_GUID     "A90597CE-A997-11DA-B012-B622A1EF5492"
0017 #define LEGACY_POWER_CONTROL_GUID   "A80593CE-A997-11DA-B012-B622A1EF5492"
0018 #define WMAX_CONTROL_GUID       "A70591CE-A997-11DA-B012-B622A1EF5492"
0019 
0020 #define WMAX_METHOD_HDMI_SOURCE     0x1
0021 #define WMAX_METHOD_HDMI_STATUS     0x2
0022 #define WMAX_METHOD_BRIGHTNESS      0x3
0023 #define WMAX_METHOD_ZONE_CONTROL    0x4
0024 #define WMAX_METHOD_HDMI_CABLE      0x5
0025 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6
0026 #define WMAX_METHOD_DEEP_SLEEP_CONTROL  0x0B
0027 #define WMAX_METHOD_DEEP_SLEEP_STATUS   0x0C
0028 
0029 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
0030 MODULE_DESCRIPTION("Alienware special feature control");
0031 MODULE_LICENSE("GPL");
0032 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
0033 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
0034 
0035 enum INTERFACE_FLAGS {
0036     LEGACY,
0037     WMAX,
0038 };
0039 
0040 enum LEGACY_CONTROL_STATES {
0041     LEGACY_RUNNING = 1,
0042     LEGACY_BOOTING = 0,
0043     LEGACY_SUSPEND = 3,
0044 };
0045 
0046 enum WMAX_CONTROL_STATES {
0047     WMAX_RUNNING = 0xFF,
0048     WMAX_BOOTING = 0,
0049     WMAX_SUSPEND = 3,
0050 };
0051 
0052 struct quirk_entry {
0053     u8 num_zones;
0054     u8 hdmi_mux;
0055     u8 amplifier;
0056     u8 deepslp;
0057 };
0058 
0059 static struct quirk_entry *quirks;
0060 
0061 
0062 static struct quirk_entry quirk_inspiron5675 = {
0063     .num_zones = 2,
0064     .hdmi_mux = 0,
0065     .amplifier = 0,
0066     .deepslp = 0,
0067 };
0068 
0069 static struct quirk_entry quirk_unknown = {
0070     .num_zones = 2,
0071     .hdmi_mux = 0,
0072     .amplifier = 0,
0073     .deepslp = 0,
0074 };
0075 
0076 static struct quirk_entry quirk_x51_r1_r2 = {
0077     .num_zones = 3,
0078     .hdmi_mux = 0,
0079     .amplifier = 0,
0080     .deepslp = 0,
0081 };
0082 
0083 static struct quirk_entry quirk_x51_r3 = {
0084     .num_zones = 4,
0085     .hdmi_mux = 0,
0086     .amplifier = 1,
0087     .deepslp = 0,
0088 };
0089 
0090 static struct quirk_entry quirk_asm100 = {
0091     .num_zones = 2,
0092     .hdmi_mux = 1,
0093     .amplifier = 0,
0094     .deepslp = 0,
0095 };
0096 
0097 static struct quirk_entry quirk_asm200 = {
0098     .num_zones = 2,
0099     .hdmi_mux = 1,
0100     .amplifier = 0,
0101     .deepslp = 1,
0102 };
0103 
0104 static struct quirk_entry quirk_asm201 = {
0105     .num_zones = 2,
0106     .hdmi_mux = 1,
0107     .amplifier = 1,
0108     .deepslp = 1,
0109 };
0110 
0111 static int __init dmi_matched(const struct dmi_system_id *dmi)
0112 {
0113     quirks = dmi->driver_data;
0114     return 1;
0115 }
0116 
0117 static const struct dmi_system_id alienware_quirks[] __initconst = {
0118     {
0119      .callback = dmi_matched,
0120      .ident = "Alienware X51 R3",
0121      .matches = {
0122              DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
0123              DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
0124              },
0125      .driver_data = &quirk_x51_r3,
0126      },
0127     {
0128      .callback = dmi_matched,
0129      .ident = "Alienware X51 R2",
0130      .matches = {
0131              DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
0132              DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
0133              },
0134      .driver_data = &quirk_x51_r1_r2,
0135      },
0136     {
0137      .callback = dmi_matched,
0138      .ident = "Alienware X51 R1",
0139      .matches = {
0140              DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
0141              DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
0142              },
0143      .driver_data = &quirk_x51_r1_r2,
0144      },
0145     {
0146      .callback = dmi_matched,
0147      .ident = "Alienware ASM100",
0148      .matches = {
0149              DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
0150              DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
0151              },
0152      .driver_data = &quirk_asm100,
0153      },
0154     {
0155      .callback = dmi_matched,
0156      .ident = "Alienware ASM200",
0157      .matches = {
0158              DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
0159              DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
0160              },
0161      .driver_data = &quirk_asm200,
0162      },
0163     {
0164      .callback = dmi_matched,
0165      .ident = "Alienware ASM201",
0166      .matches = {
0167              DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
0168              DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
0169              },
0170      .driver_data = &quirk_asm201,
0171      },
0172      {
0173      .callback = dmi_matched,
0174      .ident = "Dell Inc. Inspiron 5675",
0175      .matches = {
0176              DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0177              DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),
0178              },
0179      .driver_data = &quirk_inspiron5675,
0180      },
0181     {}
0182 };
0183 
0184 struct color_platform {
0185     u8 blue;
0186     u8 green;
0187     u8 red;
0188 } __packed;
0189 
0190 struct platform_zone {
0191     u8 location;
0192     struct device_attribute *attr;
0193     struct color_platform colors;
0194 };
0195 
0196 struct wmax_brightness_args {
0197     u32 led_mask;
0198     u32 percentage;
0199 };
0200 
0201 struct wmax_basic_args {
0202     u8 arg;
0203 };
0204 
0205 struct legacy_led_args {
0206     struct color_platform colors;
0207     u8 brightness;
0208     u8 state;
0209 } __packed;
0210 
0211 struct wmax_led_args {
0212     u32 led_mask;
0213     struct color_platform colors;
0214     u8 state;
0215 } __packed;
0216 
0217 static struct platform_device *platform_device;
0218 static struct device_attribute *zone_dev_attrs;
0219 static struct attribute **zone_attrs;
0220 static struct platform_zone *zone_data;
0221 
0222 static struct platform_driver platform_driver = {
0223     .driver = {
0224            .name = "alienware-wmi",
0225            }
0226 };
0227 
0228 static struct attribute_group zone_attribute_group = {
0229     .name = "rgb_zones",
0230 };
0231 
0232 static u8 interface;
0233 static u8 lighting_control_state;
0234 static u8 global_brightness;
0235 
0236 /*
0237  * Helpers used for zone control
0238  */
0239 static int parse_rgb(const char *buf, struct platform_zone *zone)
0240 {
0241     long unsigned int rgb;
0242     int ret;
0243     union color_union {
0244         struct color_platform cp;
0245         int package;
0246     } repackager;
0247 
0248     ret = kstrtoul(buf, 16, &rgb);
0249     if (ret)
0250         return ret;
0251 
0252     /* RGB triplet notation is 24-bit hexadecimal */
0253     if (rgb > 0xFFFFFF)
0254         return -EINVAL;
0255 
0256     repackager.package = rgb & 0x0f0f0f0f;
0257     pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
0258          repackager.cp.red, repackager.cp.green, repackager.cp.blue);
0259     zone->colors = repackager.cp;
0260     return 0;
0261 }
0262 
0263 static struct platform_zone *match_zone(struct device_attribute *attr)
0264 {
0265     u8 zone;
0266 
0267     for (zone = 0; zone < quirks->num_zones; zone++) {
0268         if ((struct device_attribute *)zone_data[zone].attr == attr) {
0269             pr_debug("alienware-wmi: matched zone location: %d\n",
0270                  zone_data[zone].location);
0271             return &zone_data[zone];
0272         }
0273     }
0274     return NULL;
0275 }
0276 
0277 /*
0278  * Individual RGB zone control
0279  */
0280 static int alienware_update_led(struct platform_zone *zone)
0281 {
0282     int method_id;
0283     acpi_status status;
0284     char *guid;
0285     struct acpi_buffer input;
0286     struct legacy_led_args legacy_args;
0287     struct wmax_led_args wmax_basic_args;
0288     if (interface == WMAX) {
0289         wmax_basic_args.led_mask = 1 << zone->location;
0290         wmax_basic_args.colors = zone->colors;
0291         wmax_basic_args.state = lighting_control_state;
0292         guid = WMAX_CONTROL_GUID;
0293         method_id = WMAX_METHOD_ZONE_CONTROL;
0294 
0295         input.length = (acpi_size) sizeof(wmax_basic_args);
0296         input.pointer = &wmax_basic_args;
0297     } else {
0298         legacy_args.colors = zone->colors;
0299         legacy_args.brightness = global_brightness;
0300         legacy_args.state = 0;
0301         if (lighting_control_state == LEGACY_BOOTING ||
0302             lighting_control_state == LEGACY_SUSPEND) {
0303             guid = LEGACY_POWER_CONTROL_GUID;
0304             legacy_args.state = lighting_control_state;
0305         } else
0306             guid = LEGACY_CONTROL_GUID;
0307         method_id = zone->location + 1;
0308 
0309         input.length = (acpi_size) sizeof(legacy_args);
0310         input.pointer = &legacy_args;
0311     }
0312     pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
0313 
0314     status = wmi_evaluate_method(guid, 0, method_id, &input, NULL);
0315     if (ACPI_FAILURE(status))
0316         pr_err("alienware-wmi: zone set failure: %u\n", status);
0317     return ACPI_FAILURE(status);
0318 }
0319 
0320 static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
0321              char *buf)
0322 {
0323     struct platform_zone *target_zone;
0324     target_zone = match_zone(attr);
0325     if (target_zone == NULL)
0326         return sprintf(buf, "red: -1, green: -1, blue: -1\n");
0327     return sprintf(buf, "red: %d, green: %d, blue: %d\n",
0328                target_zone->colors.red,
0329                target_zone->colors.green, target_zone->colors.blue);
0330 
0331 }
0332 
0333 static ssize_t zone_set(struct device *dev, struct device_attribute *attr,
0334             const char *buf, size_t count)
0335 {
0336     struct platform_zone *target_zone;
0337     int ret;
0338     target_zone = match_zone(attr);
0339     if (target_zone == NULL) {
0340         pr_err("alienware-wmi: invalid target zone\n");
0341         return 1;
0342     }
0343     ret = parse_rgb(buf, target_zone);
0344     if (ret)
0345         return ret;
0346     ret = alienware_update_led(target_zone);
0347     return ret ? ret : count;
0348 }
0349 
0350 /*
0351  * LED Brightness (Global)
0352  */
0353 static int wmax_brightness(int brightness)
0354 {
0355     acpi_status status;
0356     struct acpi_buffer input;
0357     struct wmax_brightness_args args = {
0358         .led_mask = 0xFF,
0359         .percentage = brightness,
0360     };
0361     input.length = (acpi_size) sizeof(args);
0362     input.pointer = &args;
0363     status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
0364                      WMAX_METHOD_BRIGHTNESS, &input, NULL);
0365     if (ACPI_FAILURE(status))
0366         pr_err("alienware-wmi: brightness set failure: %u\n", status);
0367     return ACPI_FAILURE(status);
0368 }
0369 
0370 static void global_led_set(struct led_classdev *led_cdev,
0371                enum led_brightness brightness)
0372 {
0373     int ret;
0374     global_brightness = brightness;
0375     if (interface == WMAX)
0376         ret = wmax_brightness(brightness);
0377     else
0378         ret = alienware_update_led(&zone_data[0]);
0379     if (ret)
0380         pr_err("LED brightness update failed\n");
0381 }
0382 
0383 static enum led_brightness global_led_get(struct led_classdev *led_cdev)
0384 {
0385     return global_brightness;
0386 }
0387 
0388 static struct led_classdev global_led = {
0389     .brightness_set = global_led_set,
0390     .brightness_get = global_led_get,
0391     .name = "alienware::global_brightness",
0392 };
0393 
0394 /*
0395  * Lighting control state device attribute (Global)
0396  */
0397 static ssize_t show_control_state(struct device *dev,
0398                   struct device_attribute *attr, char *buf)
0399 {
0400     if (lighting_control_state == LEGACY_BOOTING)
0401         return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n");
0402     else if (lighting_control_state == LEGACY_SUSPEND)
0403         return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n");
0404     return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n");
0405 }
0406 
0407 static ssize_t store_control_state(struct device *dev,
0408                    struct device_attribute *attr,
0409                    const char *buf, size_t count)
0410 {
0411     long unsigned int val;
0412     if (strcmp(buf, "booting\n") == 0)
0413         val = LEGACY_BOOTING;
0414     else if (strcmp(buf, "suspend\n") == 0)
0415         val = LEGACY_SUSPEND;
0416     else if (interface == LEGACY)
0417         val = LEGACY_RUNNING;
0418     else
0419         val = WMAX_RUNNING;
0420     lighting_control_state = val;
0421     pr_debug("alienware-wmi: updated control state to %d\n",
0422          lighting_control_state);
0423     return count;
0424 }
0425 
0426 static DEVICE_ATTR(lighting_control_state, 0644, show_control_state,
0427            store_control_state);
0428 
0429 static int alienware_zone_init(struct platform_device *dev)
0430 {
0431     u8 zone;
0432     char buffer[10];
0433     char *name;
0434 
0435     if (interface == WMAX) {
0436         lighting_control_state = WMAX_RUNNING;
0437     } else if (interface == LEGACY) {
0438         lighting_control_state = LEGACY_RUNNING;
0439     }
0440     global_led.max_brightness = 0x0F;
0441     global_brightness = global_led.max_brightness;
0442 
0443     /*
0444      *      - zone_dev_attrs num_zones + 1 is for individual zones and then
0445      *        null terminated
0446      *      - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
0447      *        the lighting control + null terminated
0448      *      - zone_data num_zones is for the distinct zones
0449      */
0450     zone_dev_attrs =
0451         kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute),
0452             GFP_KERNEL);
0453     if (!zone_dev_attrs)
0454         return -ENOMEM;
0455 
0456     zone_attrs =
0457         kcalloc(quirks->num_zones + 2, sizeof(struct attribute *),
0458             GFP_KERNEL);
0459     if (!zone_attrs)
0460         return -ENOMEM;
0461 
0462     zone_data =
0463         kcalloc(quirks->num_zones, sizeof(struct platform_zone),
0464             GFP_KERNEL);
0465     if (!zone_data)
0466         return -ENOMEM;
0467 
0468     for (zone = 0; zone < quirks->num_zones; zone++) {
0469         sprintf(buffer, "zone%02hhX", zone);
0470         name = kstrdup(buffer, GFP_KERNEL);
0471         if (name == NULL)
0472             return 1;
0473         sysfs_attr_init(&zone_dev_attrs[zone].attr);
0474         zone_dev_attrs[zone].attr.name = name;
0475         zone_dev_attrs[zone].attr.mode = 0644;
0476         zone_dev_attrs[zone].show = zone_show;
0477         zone_dev_attrs[zone].store = zone_set;
0478         zone_data[zone].location = zone;
0479         zone_attrs[zone] = &zone_dev_attrs[zone].attr;
0480         zone_data[zone].attr = &zone_dev_attrs[zone];
0481     }
0482     zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr;
0483     zone_attribute_group.attrs = zone_attrs;
0484 
0485     led_classdev_register(&dev->dev, &global_led);
0486 
0487     return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);
0488 }
0489 
0490 static void alienware_zone_exit(struct platform_device *dev)
0491 {
0492     u8 zone;
0493 
0494     sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);
0495     led_classdev_unregister(&global_led);
0496     if (zone_dev_attrs) {
0497         for (zone = 0; zone < quirks->num_zones; zone++)
0498             kfree(zone_dev_attrs[zone].attr.name);
0499     }
0500     kfree(zone_dev_attrs);
0501     kfree(zone_data);
0502     kfree(zone_attrs);
0503 }
0504 
0505 static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,
0506                       u32 command, int *out_data)
0507 {
0508     acpi_status status;
0509     union acpi_object *obj;
0510     struct acpi_buffer input;
0511     struct acpi_buffer output;
0512 
0513     input.length = (acpi_size) sizeof(*in_args);
0514     input.pointer = in_args;
0515     if (out_data) {
0516         output.length = ACPI_ALLOCATE_BUFFER;
0517         output.pointer = NULL;
0518         status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
0519                          command, &input, &output);
0520         if (ACPI_SUCCESS(status)) {
0521             obj = (union acpi_object *)output.pointer;
0522             if (obj && obj->type == ACPI_TYPE_INTEGER)
0523                 *out_data = (u32)obj->integer.value;
0524         }
0525         kfree(output.pointer);
0526     } else {
0527         status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
0528                          command, &input, NULL);
0529     }
0530     return status;
0531 }
0532 
0533 /*
0534  *  The HDMI mux sysfs node indicates the status of the HDMI input mux.
0535  *  It can toggle between standard system GPU output and HDMI input.
0536  */
0537 static ssize_t show_hdmi_cable(struct device *dev,
0538                    struct device_attribute *attr, char *buf)
0539 {
0540     acpi_status status;
0541     u32 out_data;
0542     struct wmax_basic_args in_args = {
0543         .arg = 0,
0544     };
0545     status =
0546         alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,
0547                    (u32 *) &out_data);
0548     if (ACPI_SUCCESS(status)) {
0549         if (out_data == 0)
0550             return scnprintf(buf, PAGE_SIZE,
0551                      "[unconnected] connected unknown\n");
0552         else if (out_data == 1)
0553             return scnprintf(buf, PAGE_SIZE,
0554                      "unconnected [connected] unknown\n");
0555     }
0556     pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
0557     return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
0558 }
0559 
0560 static ssize_t show_hdmi_source(struct device *dev,
0561                 struct device_attribute *attr, char *buf)
0562 {
0563     acpi_status status;
0564     u32 out_data;
0565     struct wmax_basic_args in_args = {
0566         .arg = 0,
0567     };
0568     status =
0569         alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,
0570                    (u32 *) &out_data);
0571 
0572     if (ACPI_SUCCESS(status)) {
0573         if (out_data == 1)
0574             return scnprintf(buf, PAGE_SIZE,
0575                      "[input] gpu unknown\n");
0576         else if (out_data == 2)
0577             return scnprintf(buf, PAGE_SIZE,
0578                      "input [gpu] unknown\n");
0579     }
0580     pr_err("alienware-wmi: unknown HDMI source status: %u\n", status);
0581     return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n");
0582 }
0583 
0584 static ssize_t toggle_hdmi_source(struct device *dev,
0585                   struct device_attribute *attr,
0586                   const char *buf, size_t count)
0587 {
0588     acpi_status status;
0589     struct wmax_basic_args args;
0590     if (strcmp(buf, "gpu\n") == 0)
0591         args.arg = 1;
0592     else if (strcmp(buf, "input\n") == 0)
0593         args.arg = 2;
0594     else
0595         args.arg = 3;
0596     pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
0597 
0598     status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
0599 
0600     if (ACPI_FAILURE(status))
0601         pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
0602                status);
0603     return count;
0604 }
0605 
0606 static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL);
0607 static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source,
0608            toggle_hdmi_source);
0609 
0610 static struct attribute *hdmi_attrs[] = {
0611     &dev_attr_cable.attr,
0612     &dev_attr_source.attr,
0613     NULL,
0614 };
0615 
0616 static const struct attribute_group hdmi_attribute_group = {
0617     .name = "hdmi",
0618     .attrs = hdmi_attrs,
0619 };
0620 
0621 static void remove_hdmi(struct platform_device *dev)
0622 {
0623     if (quirks->hdmi_mux > 0)
0624         sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group);
0625 }
0626 
0627 static int create_hdmi(struct platform_device *dev)
0628 {
0629     int ret;
0630 
0631     ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
0632     if (ret)
0633         remove_hdmi(dev);
0634     return ret;
0635 }
0636 
0637 /*
0638  * Alienware GFX amplifier support
0639  * - Currently supports reading cable status
0640  * - Leaving expansion room to possibly support dock/undock events later
0641  */
0642 static ssize_t show_amplifier_status(struct device *dev,
0643                      struct device_attribute *attr, char *buf)
0644 {
0645     acpi_status status;
0646     u32 out_data;
0647     struct wmax_basic_args in_args = {
0648         .arg = 0,
0649     };
0650     status =
0651         alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,
0652                    (u32 *) &out_data);
0653     if (ACPI_SUCCESS(status)) {
0654         if (out_data == 0)
0655             return scnprintf(buf, PAGE_SIZE,
0656                      "[unconnected] connected unknown\n");
0657         else if (out_data == 1)
0658             return scnprintf(buf, PAGE_SIZE,
0659                      "unconnected [connected] unknown\n");
0660     }
0661     pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);
0662     return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
0663 }
0664 
0665 static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL);
0666 
0667 static struct attribute *amplifier_attrs[] = {
0668     &dev_attr_status.attr,
0669     NULL,
0670 };
0671 
0672 static const struct attribute_group amplifier_attribute_group = {
0673     .name = "amplifier",
0674     .attrs = amplifier_attrs,
0675 };
0676 
0677 static void remove_amplifier(struct platform_device *dev)
0678 {
0679     if (quirks->amplifier > 0)
0680         sysfs_remove_group(&dev->dev.kobj, &amplifier_attribute_group);
0681 }
0682 
0683 static int create_amplifier(struct platform_device *dev)
0684 {
0685     int ret;
0686 
0687     ret = sysfs_create_group(&dev->dev.kobj, &amplifier_attribute_group);
0688     if (ret)
0689         remove_amplifier(dev);
0690     return ret;
0691 }
0692 
0693 /*
0694  * Deep Sleep Control support
0695  * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
0696  */
0697 static ssize_t show_deepsleep_status(struct device *dev,
0698                      struct device_attribute *attr, char *buf)
0699 {
0700     acpi_status status;
0701     u32 out_data;
0702     struct wmax_basic_args in_args = {
0703         .arg = 0,
0704     };
0705     status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,
0706                     (u32 *) &out_data);
0707     if (ACPI_SUCCESS(status)) {
0708         if (out_data == 0)
0709             return scnprintf(buf, PAGE_SIZE,
0710                      "[disabled] s5 s5_s4\n");
0711         else if (out_data == 1)
0712             return scnprintf(buf, PAGE_SIZE,
0713                      "disabled [s5] s5_s4\n");
0714         else if (out_data == 2)
0715             return scnprintf(buf, PAGE_SIZE,
0716                      "disabled s5 [s5_s4]\n");
0717     }
0718     pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);
0719     return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n");
0720 }
0721 
0722 static ssize_t toggle_deepsleep(struct device *dev,
0723                 struct device_attribute *attr,
0724                 const char *buf, size_t count)
0725 {
0726     acpi_status status;
0727     struct wmax_basic_args args;
0728 
0729     if (strcmp(buf, "disabled\n") == 0)
0730         args.arg = 0;
0731     else if (strcmp(buf, "s5\n") == 0)
0732         args.arg = 1;
0733     else
0734         args.arg = 2;
0735     pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
0736 
0737     status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,
0738                     NULL);
0739 
0740     if (ACPI_FAILURE(status))
0741         pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
0742             status);
0743     return count;
0744 }
0745 
0746 static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep);
0747 
0748 static struct attribute *deepsleep_attrs[] = {
0749     &dev_attr_deepsleep.attr,
0750     NULL,
0751 };
0752 
0753 static const struct attribute_group deepsleep_attribute_group = {
0754     .name = "deepsleep",
0755     .attrs = deepsleep_attrs,
0756 };
0757 
0758 static void remove_deepsleep(struct platform_device *dev)
0759 {
0760     if (quirks->deepslp > 0)
0761         sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group);
0762 }
0763 
0764 static int create_deepsleep(struct platform_device *dev)
0765 {
0766     int ret;
0767 
0768     ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group);
0769     if (ret)
0770         remove_deepsleep(dev);
0771     return ret;
0772 }
0773 
0774 static int __init alienware_wmi_init(void)
0775 {
0776     int ret;
0777 
0778     if (wmi_has_guid(LEGACY_CONTROL_GUID))
0779         interface = LEGACY;
0780     else if (wmi_has_guid(WMAX_CONTROL_GUID))
0781         interface = WMAX;
0782     else {
0783         pr_warn("alienware-wmi: No known WMI GUID found\n");
0784         return -ENODEV;
0785     }
0786 
0787     dmi_check_system(alienware_quirks);
0788     if (quirks == NULL)
0789         quirks = &quirk_unknown;
0790 
0791     ret = platform_driver_register(&platform_driver);
0792     if (ret)
0793         goto fail_platform_driver;
0794     platform_device = platform_device_alloc("alienware-wmi", -1);
0795     if (!platform_device) {
0796         ret = -ENOMEM;
0797         goto fail_platform_device1;
0798     }
0799     ret = platform_device_add(platform_device);
0800     if (ret)
0801         goto fail_platform_device2;
0802 
0803     if (quirks->hdmi_mux > 0) {
0804         ret = create_hdmi(platform_device);
0805         if (ret)
0806             goto fail_prep_hdmi;
0807     }
0808 
0809     if (quirks->amplifier > 0) {
0810         ret = create_amplifier(platform_device);
0811         if (ret)
0812             goto fail_prep_amplifier;
0813     }
0814 
0815     if (quirks->deepslp > 0) {
0816         ret = create_deepsleep(platform_device);
0817         if (ret)
0818             goto fail_prep_deepsleep;
0819     }
0820 
0821     ret = alienware_zone_init(platform_device);
0822     if (ret)
0823         goto fail_prep_zones;
0824 
0825     return 0;
0826 
0827 fail_prep_zones:
0828     alienware_zone_exit(platform_device);
0829 fail_prep_deepsleep:
0830 fail_prep_amplifier:
0831 fail_prep_hdmi:
0832     platform_device_del(platform_device);
0833 fail_platform_device2:
0834     platform_device_put(platform_device);
0835 fail_platform_device1:
0836     platform_driver_unregister(&platform_driver);
0837 fail_platform_driver:
0838     return ret;
0839 }
0840 
0841 module_init(alienware_wmi_init);
0842 
0843 static void __exit alienware_wmi_exit(void)
0844 {
0845     if (platform_device) {
0846         alienware_zone_exit(platform_device);
0847         remove_hdmi(platform_device);
0848         platform_device_unregister(platform_device);
0849         platform_driver_unregister(&platform_driver);
0850     }
0851 }
0852 
0853 module_exit(alienware_wmi_exit);