]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/platform/x86/alienware-wmi.c
Merge remote-tracking branch 'usb/usb-next'
[karo-tx-linux.git] / drivers / platform / x86 / alienware-wmi.c
index 1e1e594238892a3bf18d99ee0e4e35d58ac96d1f..005629447b0ce624b24ed02835e3d9314065a50a 100644 (file)
@@ -33,6 +33,9 @@
 #define WMAX_METHOD_BRIGHTNESS         0x3
 #define WMAX_METHOD_ZONE_CONTROL       0x4
 #define WMAX_METHOD_HDMI_CABLE         0x5
+#define WMAX_METHOD_AMPLIFIER_CABLE    0x6
+#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
+#define WMAX_METHOD_DEEP_SLEEP_STATUS  0x0C
 
 MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
 MODULE_DESCRIPTION("Alienware special feature control");
@@ -60,6 +63,8 @@ enum WMAX_CONTROL_STATES {
 struct quirk_entry {
        u8 num_zones;
        u8 hdmi_mux;
+       u8 amplifier;
+       u8 deepslp;
 };
 
 static struct quirk_entry *quirks;
@@ -67,16 +72,43 @@ static struct quirk_entry *quirks;
 static struct quirk_entry quirk_unknown = {
        .num_zones = 2,
        .hdmi_mux = 0,
+       .amplifier = 0,
+       .deepslp = 0,
 };
 
-static struct quirk_entry quirk_x51_family = {
+static struct quirk_entry quirk_x51_r1_r2 = {
        .num_zones = 3,
-       .hdmi_mux = 0.
+       .hdmi_mux = 0,
+       .amplifier = 0,
+       .deepslp = 0,
+};
+
+static struct quirk_entry quirk_x51_r3 = {
+       .num_zones = 4,
+       .hdmi_mux = 0,
+       .amplifier = 1,
+       .deepslp = 0,
 };
 
 static struct quirk_entry quirk_asm100 = {
        .num_zones = 2,
        .hdmi_mux = 1,
+       .amplifier = 0,
+       .deepslp = 0,
+};
+
+static struct quirk_entry quirk_asm200 = {
+       .num_zones = 2,
+       .hdmi_mux = 1,
+       .amplifier = 0,
+       .deepslp = 1,
+};
+
+static struct quirk_entry quirk_asm201 = {
+       .num_zones = 2,
+       .hdmi_mux = 1,
+       .amplifier = 1,
+       .deepslp = 1,
 };
 
 static int __init dmi_matched(const struct dmi_system_id *dmi)
@@ -88,12 +120,12 @@ static int __init dmi_matched(const struct dmi_system_id *dmi)
 static const struct dmi_system_id alienware_quirks[] __initconst = {
        {
         .callback = dmi_matched,
-        .ident = "Alienware X51 R1",
+        .ident = "Alienware X51 R3",
         .matches = {
                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
-                    DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
                     },
-        .driver_data = &quirk_x51_family,
+        .driver_data = &quirk_x51_r3,
         },
        {
         .callback = dmi_matched,
@@ -102,17 +134,44 @@ static const struct dmi_system_id alienware_quirks[] __initconst = {
                     DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
                     DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
                     },
-        .driver_data = &quirk_x51_family,
+        .driver_data = &quirk_x51_r1_r2,
+        },
+       {
+        .callback = dmi_matched,
+        .ident = "Alienware X51 R1",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
+                    },
+        .driver_data = &quirk_x51_r1_r2,
+        },
+       {
+        .callback = dmi_matched,
+        .ident = "Alienware ASM100",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
+                    },
+        .driver_data = &quirk_asm100,
+        },
+       {
+        .callback = dmi_matched,
+        .ident = "Alienware ASM200",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
+                    },
+        .driver_data = &quirk_asm200,
         },
        {
-               .callback = dmi_matched,
-               .ident = "Alienware ASM100",
-               .matches = {
-                       DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
-                       DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
-               },
-               .driver_data = &quirk_asm100,
-       },
+        .callback = dmi_matched,
+        .ident = "Alienware ASM201",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
+                    },
+        .driver_data = &quirk_asm201,
+        },
        {}
 };
 
@@ -133,7 +192,7 @@ struct wmax_brightness_args {
        u32 percentage;
 };
 
-struct hdmi_args {
+struct wmax_basic_args {
        u8 arg;
 };
 
@@ -170,7 +229,7 @@ static u8 global_brightness;
 
 /*
  * Helpers used for zone control
-*/
+ */
 static int parse_rgb(const char *buf, struct platform_zone *zone)
 {
        long unsigned int rgb;
@@ -210,7 +269,7 @@ static struct platform_zone *match_zone(struct device_attribute *attr)
 
 /*
  * Individual RGB zone control
-*/
+ */
 static int alienware_update_led(struct platform_zone *zone)
 {
        int method_id;
@@ -218,16 +277,16 @@ static int alienware_update_led(struct platform_zone *zone)
        char *guid;
        struct acpi_buffer input;
        struct legacy_led_args legacy_args;
-       struct wmax_led_args wmax_args;
+       struct wmax_led_args wmax_basic_args;
        if (interface == WMAX) {
-               wmax_args.led_mask = 1 << zone->location;
-               wmax_args.colors = zone->colors;
-               wmax_args.state = lighting_control_state;
+               wmax_basic_args.led_mask = 1 << zone->location;
+               wmax_basic_args.colors = zone->colors;
+               wmax_basic_args.state = lighting_control_state;
                guid = WMAX_CONTROL_GUID;
                method_id = WMAX_METHOD_ZONE_CONTROL;
 
-               input.length = (acpi_size) sizeof(wmax_args);
-               input.pointer = &wmax_args;
+               input.length = (acpi_size) sizeof(wmax_basic_args);
+               input.pointer = &wmax_basic_args;
        } else {
                legacy_args.colors = zone->colors;
                legacy_args.brightness = global_brightness;
@@ -283,7 +342,7 @@ static ssize_t zone_set(struct device *dev, struct device_attribute *attr,
 
 /*
  * LED Brightness (Global)
-*/
+ */
 static int wmax_brightness(int brightness)
 {
        acpi_status status;
@@ -327,7 +386,7 @@ static struct led_classdev global_led = {
 
 /*
  * Lighting control state device attribute (Global)
-*/
+ */
 static ssize_t show_control_state(struct device *dev,
                                  struct device_attribute *attr, char *buf)
 {
@@ -435,11 +494,7 @@ static void alienware_zone_exit(struct platform_device *dev)
        kfree(zone_attrs);
 }
 
-/*
-       The HDMI mux sysfs node indicates the status of the HDMI input mux.
-       It can toggle between standard system GPU output and HDMI input.
-*/
-static acpi_status alienware_hdmi_command(struct hdmi_args *in_args,
+static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,
                                          u32 command, int *out_data)
 {
        acpi_status status;
@@ -467,16 +522,20 @@ static acpi_status alienware_hdmi_command(struct hdmi_args *in_args,
 
 }
 
+/*
+ *     The HDMI mux sysfs node indicates the status of the HDMI input mux.
+ *     It can toggle between standard system GPU output and HDMI input.
+ */
 static ssize_t show_hdmi_cable(struct device *dev,
                               struct device_attribute *attr, char *buf)
 {
        acpi_status status;
        u32 out_data;
-       struct hdmi_args in_args = {
+       struct wmax_basic_args in_args = {
                .arg = 0,
        };
        status =
-           alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_CABLE,
+           alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,
                                   (u32 *) &out_data);
        if (ACPI_SUCCESS(status)) {
                if (out_data == 0)
@@ -495,11 +554,11 @@ static ssize_t show_hdmi_source(struct device *dev,
 {
        acpi_status status;
        u32 out_data;
-       struct hdmi_args in_args = {
+       struct wmax_basic_args in_args = {
                .arg = 0,
        };
        status =
-           alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_STATUS,
+           alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,
                                   (u32 *) &out_data);
 
        if (ACPI_SUCCESS(status)) {
@@ -519,7 +578,7 @@ static ssize_t toggle_hdmi_source(struct device *dev,
                                  const char *buf, size_t count)
 {
        acpi_status status;
-       struct hdmi_args args;
+       struct wmax_basic_args args;
        if (strcmp(buf, "gpu\n") == 0)
                args.arg = 1;
        else if (strcmp(buf, "input\n") == 0)
@@ -528,7 +587,7 @@ static ssize_t toggle_hdmi_source(struct device *dev,
                args.arg = 3;
        pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
 
-       status = alienware_hdmi_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
+       status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
 
        if (ACPI_FAILURE(status))
                pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
@@ -563,11 +622,144 @@ static int create_hdmi(struct platform_device *dev)
 
        ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group);
        if (ret)
-               goto error_create_hdmi;
-       return 0;
+               remove_hdmi(dev);
+       return ret;
+}
 
-error_create_hdmi:
-       remove_hdmi(dev);
+/*
+ * Alienware GFX amplifier support
+ * - Currently supports reading cable status
+ * - Leaving expansion room to possibly support dock/undock events later
+ */
+static ssize_t show_amplifier_status(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       acpi_status status;
+       u32 out_data;
+       struct wmax_basic_args in_args = {
+               .arg = 0,
+       };
+       status =
+           alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,
+                                  (u32 *) &out_data);
+       if (ACPI_SUCCESS(status)) {
+               if (out_data == 0)
+                       return scnprintf(buf, PAGE_SIZE,
+                                        "[unconnected] connected unknown\n");
+               else if (out_data == 1)
+                       return scnprintf(buf, PAGE_SIZE,
+                                        "unconnected [connected] unknown\n");
+       }
+       pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);
+       return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n");
+}
+
+static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL);
+
+static struct attribute *amplifier_attrs[] = {
+       &dev_attr_status.attr,
+       NULL,
+};
+
+static struct attribute_group amplifier_attribute_group = {
+       .name = "amplifier",
+       .attrs = amplifier_attrs,
+};
+
+static void remove_amplifier(struct platform_device *dev)
+{
+       if (quirks->amplifier > 0)
+               sysfs_remove_group(&dev->dev.kobj, &amplifier_attribute_group);
+}
+
+static int create_amplifier(struct platform_device *dev)
+{
+       int ret;
+
+       ret = sysfs_create_group(&dev->dev.kobj, &amplifier_attribute_group);
+       if (ret)
+               remove_amplifier(dev);
+       return ret;
+}
+
+/*
+ * Deep Sleep Control support
+ * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
+ */
+static ssize_t show_deepsleep_status(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       acpi_status status;
+       u32 out_data;
+       struct wmax_basic_args in_args = {
+               .arg = 0,
+       };
+       status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,
+                                       (u32 *) &out_data);
+       if (ACPI_SUCCESS(status)) {
+               if (out_data == 0)
+                       return scnprintf(buf, PAGE_SIZE,
+                                        "[disabled] s5 s5_s4\n");
+               else if (out_data == 1)
+                       return scnprintf(buf, PAGE_SIZE,
+                                        "disabled [s5] s5_s4\n");
+               else if (out_data == 2)
+                       return scnprintf(buf, PAGE_SIZE,
+                                        "disabled s5 [s5_s4]\n");
+       }
+       pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);
+       return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n");
+}
+
+static ssize_t toggle_deepsleep(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       acpi_status status;
+       struct wmax_basic_args args;
+
+       if (strcmp(buf, "disabled\n") == 0)
+               args.arg = 0;
+       else if (strcmp(buf, "s5\n") == 0)
+               args.arg = 1;
+       else
+               args.arg = 2;
+       pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
+
+       status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,
+                                       NULL);
+
+       if (ACPI_FAILURE(status))
+               pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
+                       status);
+       return count;
+}
+
+static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep);
+
+static struct attribute *deepsleep_attrs[] = {
+       &dev_attr_deepsleep.attr,
+       NULL,
+};
+
+static struct attribute_group deepsleep_attribute_group = {
+       .name = "deepsleep",
+       .attrs = deepsleep_attrs,
+};
+
+static void remove_deepsleep(struct platform_device *dev)
+{
+       if (quirks->deepslp > 0)
+               sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group);
+}
+
+static int create_deepsleep(struct platform_device *dev)
+{
+       int ret;
+
+       ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group);
+       if (ret)
+               remove_deepsleep(dev);
        return ret;
 }
 
@@ -606,6 +798,18 @@ static int __init alienware_wmi_init(void)
                        goto fail_prep_hdmi;
        }
 
+       if (quirks->amplifier > 0) {
+               ret = create_amplifier(platform_device);
+               if (ret)
+                       goto fail_prep_amplifier;
+       }
+
+       if (quirks->deepslp > 0) {
+               ret = create_deepsleep(platform_device);
+               if (ret)
+                       goto fail_prep_deepsleep;
+       }
+
        ret = alienware_zone_init(platform_device);
        if (ret)
                goto fail_prep_zones;
@@ -614,6 +818,8 @@ static int __init alienware_wmi_init(void)
 
 fail_prep_zones:
        alienware_zone_exit(platform_device);
+fail_prep_deepsleep:
+fail_prep_amplifier:
 fail_prep_hdmi:
        platform_device_del(platform_device);
 fail_platform_device2: