Specifically for platforms that don't contain an AlienFX USB based MCU
such as the Alienware X51-R2.

Signed-off-by: Mario Limonciello <[email protected]>
---
 drivers/platform/x86/Kconfig         |   13 ++
 drivers/platform/x86/Makefile        |    1 +
 drivers/platform/x86/alienware-wmi.c |  345 ++++++++++++++++++++++++++++++++++
 3 files changed, 359 insertions(+)
 create mode 100644 drivers/platform/x86/alienware-wmi.c

diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 5ae65c1..d030525 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -55,6 +55,19 @@ config ACERHDF
          If you have an Acer Aspire One netbook, say Y or M
          here.
 
+config ALIENWARE_WMI
+       tristate "Alienware Special feature control"
+       depends on ACPI
+       depends on ACPI_WMI
+       ---help---
+         This is a driver for controlling ALienware BIOS driven
+         features.  It exposes an interface for controlling the AlienFX
+         zones on Alienware machines that don't contain a dedicated AlienFX
+         USB MCU such as the X51-R2.
+
+         If you have an ACPI-WMI compatible Alienware desktop, say Y or M
+         here.
+
 config ASUS_LAPTOP
        tristate "Asus Laptop Extras"
        depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 9b87cfc..0e9b157 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_INTEL_SMARTCONNECT)      += intel-smartconnect.o
 
 obj-$(CONFIG_PVPANIC)           += pvpanic.o
 obj-$(CONFIG_INTEL_BAYTRAIL_MBI)       += intel_baytrail.o
+obj-$(CONFIG_ALIENWARE_WMI)    += alienware-wmi.o
diff --git a/drivers/platform/x86/alienware-wmi.c 
b/drivers/platform/x86/alienware-wmi.c
new file mode 100644
index 0000000..76087c9
--- /dev/null
+++ b/drivers/platform/x86/alienware-wmi.c
@@ -0,0 +1,345 @@
+/*
+ * Alienware AlienFX control
+ *
+ * Copyright (C) 2014 Dell Inc <[email protected]>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+
+MODULE_AUTHOR("Mario Limonciello <[email protected]>");
+MODULE_DESCRIPTION("Alienware special feature control");
+MODULE_LICENSE("GPL");
+
+static struct platform_driver platform_driver = {
+       .driver = {
+               .name = "alienware-wmi",
+               .owner = THIS_MODULE,
+       }
+};
+
+static struct platform_device *platform_device;
+
+static const struct dmi_system_id alienware_device_table[] __initconst = {
+       {
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "TBD"),
+               },
+       },
+       {}
+};
+
+MODULE_DEVICE_TABLE(dmi, alienware_device_table);
+
+#define RUNNING_CONTROL_GUID           "A90597CE-A997-11DA-B012-B622A1EF5492"
+#define POWERSTATE_CONTROL_GUID                
"A80593CE-A997-11DA-B012-B622A1EF5492"
+#define HDMI_TOGGLE_GUID               "TBD"
+#define HDMI_MUX_GUID                  "TBD"
+
+MODULE_ALIAS("wmi:" RUNNING_CONTROL_GUID);
+
+/*
+  Lighting Zone control groups
+*/
+
+#define ALIENWARE_HEAD_ZONE    1
+#define ALIENWARE_LEFT_ZONE    2
+#define ALIENWARE_RIGHT_ZONE   3
+
+enum LIGHTING_CONTROL_STATE {
+       RUNNING = 1,
+       BOOTING = 0,
+       SUSPEND = 3,
+};
+
+struct color_platform {
+       u8 blue;
+       u8 green;
+       u8 red;
+       u8 brightness;
+} __packed;
+
+struct platform_zone {
+       struct color_platform colors;
+       u8 location;
+};
+
+static struct platform_zone head = {
+       .location = ALIENWARE_HEAD_ZONE,
+};
+
+static struct platform_zone left = {
+       .location = ALIENWARE_LEFT_ZONE,
+};
+
+static struct platform_zone right = {
+       .location = ALIENWARE_RIGHT_ZONE,
+};
+
+static u8 lighting_control_state = RUNNING;
+
+static int update_leds(struct platform_zone zone, unsigned long count)
+{
+       acpi_status status;
+       char *guid;
+       struct platform_zone args = {
+               .colors = zone.colors,
+               .location = lighting_control_state
+       };
+       struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+       if (lighting_control_state == BOOTING ||
+               lighting_control_state == SUSPEND)
+               guid = POWERSTATE_CONTROL_GUID;
+       else
+               guid = RUNNING_CONTROL_GUID;
+       pr_debug("alienware-wmi: evaluate [ guid %s | zone %d ]\n",
+               guid, zone.location);
+
+       status = wmi_evaluate_method(guid, 1, zone.location, &input, NULL);
+       if (ACPI_FAILURE(status))
+               pr_err("alienware-wmi: zone set failure: %u\n", status);
+
+       return count;
+}
+
+/*
+       All color values need to be in range from 0 - 255.
+*/
+static long sanitize_input(const char *buf, unsigned long count, int *val)
+{
+       if (!count)
+               return 0;
+       if (sscanf(buf, "%i", val) != 1)
+               return -EINVAL;
+       if (*val < 0)
+               *val = 0;
+       if (*val > 255)
+               *val = 255;
+       return count;
+}
+
+#define ALIENWARE_WMI_CREATE_DEVICE_ATTR(_zone, _color, _mode)         \
+       static ssize_t show_##_zone##_color(struct device *dev,         \
+               struct device_attribute *attr, char *buf)               \
+       {                                                               \
+               return sprintf(buf, "%d\n", _zone.colors._color);       \
+       }                                                               \
+       static ssize_t store_##_zone##_color(struct device *dev,        \
+               struct device_attribute *attr, const char *buf,         \
+               size_t count)                                           \
+       {                                                               \
+               int val, rc;                                            \
+               rc = sanitize_input(buf, count, &val);                  \
+               _zone.colors._color = val;                              \
+               return update_leds(_zone, rc);                          \
+       }                                                               \
+       static struct device_attribute dev_attr_##_zone##_##_color = {  \
+               .attr = {                                               \
+                       .name = __stringify(_zone##_##_color),          \
+                       .mode = _mode },                                \
+               .show = show_##_zone##_color,                           \
+               .store = store_##_zone##_color,                         \
+       }
+
+static ssize_t show_control_state(struct device *dev,
+                                 struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", lighting_control_state);
+}
+
+static ssize_t set_control_state(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, ssize_t count)
+{
+       int val, rc;
+       rc = sanitize_input(buf, count, &val);
+       if (val != BOOTING && val != SUSPEND)
+               val = RUNNING;
+       lighting_control_state = val;
+       pr_debug("alienware-wmi: updated control state to %d\n", val);
+       return rc;
+}
+
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(head, blue, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(head, green, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(head, red, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(head, brightness, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(left, blue, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(left, green, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(left, red, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(left, brightness, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(right, blue, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(right, green, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(right, red, 0644);
+ALIENWARE_WMI_CREATE_DEVICE_ATTR(right, brightness, 0644);
+
+static DEVICE_ATTR(lighting_control_state, S_IRUGO | S_IWUSR,
+                  show_control_state, set_control_state);
+
+static struct attribute *zone_attributes[] = {
+       &dev_attr_head_blue.attr,
+       &dev_attr_head_red.attr,
+       &dev_attr_head_green.attr,
+       &dev_attr_head_brightness.attr,
+       &dev_attr_left_blue.attr,
+       &dev_attr_left_red.attr,
+       &dev_attr_left_green.attr,
+       &dev_attr_left_brightness.attr,
+       &dev_attr_right_blue.attr,
+       &dev_attr_right_red.attr,
+       &dev_attr_right_green.attr,
+       &dev_attr_right_brightness.attr,
+       &dev_attr_lighting_control_state.attr,
+       NULL
+};
+
+static struct attribute_group zone_attribute_group = {
+       .attrs = zone_attributes
+};
+
+static void remove_zones(struct platform_device *dev)
+{
+       sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group);
+}
+
+static int prepare_zones(struct platform_device *dev)
+{
+       return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group);
+}
+
+/*
+  HDMI toggle control (interface and platform still TBD)
+*/
+
+static ssize_t show_hdmi(struct device *dev, struct device_attribute *attr,
+                        char *buf)
+{
+       acpi_status status;
+       status = wmi_evaluate_method(HDMI_MUX_GUID, 1, 3, NULL, NULL);
+       if (status == 1) {
+               sprintf(buf, "hdmi-i\n");
+               return 0;
+       } else if (status == 2) {
+               sprintf(buf, "gpu\n");
+               return 0;
+       }
+       sprintf(buf, "error reading mux\n");
+       pr_err("alienware-wmi: HDMI mux read failed: results: %u\n", status);
+       return status;
+}
+
+static ssize_t toggle_hdmi(struct device *dev, struct device_attribute *attr,
+                          const char *buf, size_t count)
+{
+       acpi_status status;
+       status = wmi_evaluate_method(HDMI_TOGGLE_GUID, 1, 3, NULL, NULL);
+       if (ACPI_FAILURE(status))
+               pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
+                          status);
+       return count;
+}
+
+static DEVICE_ATTR(hdmi, S_IRUGO | S_IWUSR, show_hdmi, toggle_hdmi);
+
+static void remove_hdmi(struct platform_device *device)
+{
+       device_remove_file(&device->dev, &dev_attr_hdmi);
+}
+
+static int create_hdmi(void)
+{
+       int ret = -ENOMEM;
+
+       ret = device_create_file(&platform_device->dev, &dev_attr_hdmi);
+       if (ret)
+               goto error_create_hdmi;
+       return 0;
+
+error_create_hdmi:
+       remove_hdmi(platform_device);
+       return ret;
+}
+
+static int __init alienware_wmi_init(void)
+{
+       int ret;
+
+       if (!wmi_has_guid(RUNNING_CONTROL_GUID)) {
+               pr_warn("No known WMI GUID found\n");
+               return -ENODEV;
+       }
+
+       ret = platform_driver_register(&platform_driver);
+       if (ret)
+               goto fail_platform_driver;
+       platform_device = platform_device_alloc("alienware-wmi", -1);
+       if (!platform_device) {
+               ret = -ENOMEM;
+               goto fail_platform_device1;
+       }
+       ret = platform_device_add(platform_device);
+       if (ret)
+               goto fail_platform_device2;
+
+       /*
+               No systems with HDMI-i yet, for later.
+               might make this a WMI check at that time.
+       */
+       if (dmi_check_system(alienware_device_table)) {
+               ret = create_hdmi();
+               if (ret)
+                       goto fail_prep_hdmi;
+       }
+       /*
+               Only present on AW platforms w/o MCU.
+       */
+       if (wmi_has_guid(RUNNING_CONTROL_GUID)) {
+               ret = prepare_zones(platform_device);
+               if (ret)
+                       goto fail_prep_zones;
+       }
+
+       return 0;
+
+fail_prep_zones:
+       remove_zones(platform_device);
+fail_prep_hdmi:
+       platform_device_del(platform_device);
+fail_platform_device2:
+       platform_device_put(platform_device);
+fail_platform_device1:
+       platform_driver_unregister(&platform_driver);
+fail_platform_driver:
+       return ret;
+}
+
+module_init(alienware_wmi_init);
+
+static void __exit alienware_wmi_exit(void)
+{
+       remove_zones(platform_device);
+       remove_hdmi(platform_device);
+       if (platform_device) {
+               platform_device_unregister(platform_device);
+               platform_driver_unregister(&platform_driver);
+       }
+}
+
+module_exit(alienware_wmi_exit);
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" 
in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to