diff options
| author | Mario Limonciello <mario_limonciello@dell.com> | 2014-04-04 14:15:42 -0400 |
|---|---|---|
| committer | Matthew Garrett <matthew.garrett@nebula.com> | 2014-04-06 12:58:15 -0400 |
| commit | a46ad0f13bc32a9601f3c5dff43fafdc2c598814 (patch) | |
| tree | 7eaebe1c76a15bae513fb9e68ee34794c708a883 | |
| parent | 71db1183d4c661eaedc299b721526160bf304bd3 (diff) | |
Add WMI driver for controlling AlienFX features on some Alienware products
Signed-off-by: Mario Limonciello <mario_limonciello@dell.com>
Signed-off-by: Matthew Garrett <matthew.garrett@nebula.com>
| -rw-r--r-- | drivers/platform/x86/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/platform/x86/Makefile | 1 | ||||
| -rw-r--r-- | drivers/platform/x86/alienware-wmi.c | 557 |
3 files changed, 570 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 34e3025e25ba..27df2c533b09 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig | |||
| @@ -53,6 +53,18 @@ config ACERHDF | |||
| 53 | If you have an Acer Aspire One netbook, say Y or M | 53 | If you have an Acer Aspire One netbook, say Y or M |
| 54 | here. | 54 | here. |
| 55 | 55 | ||
| 56 | config ALIENWARE_WMI | ||
| 57 | tristate "Alienware Special feature control" | ||
| 58 | depends on ACPI | ||
| 59 | depends on LEDS_CLASS | ||
| 60 | depends on NEW_LEDS | ||
| 61 | depends on ACPI_WMI | ||
| 62 | ---help--- | ||
| 63 | This is a driver for controlling Alienware BIOS driven | ||
| 64 | features. It exposes an interface for controlling the AlienFX | ||
| 65 | zones on Alienware machines that don't contain a dedicated AlienFX | ||
| 66 | USB MCU such as the X51 and X51-R2. | ||
| 67 | |||
| 56 | config ASUS_LAPTOP | 68 | config ASUS_LAPTOP |
| 57 | tristate "Asus Laptop Extras" | 69 | tristate "Asus Laptop Extras" |
| 58 | depends on ACPI | 70 | depends on ACPI |
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index b8c36f785312..1a2eafc9d48e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile | |||
| @@ -55,3 +55,4 @@ obj-$(CONFIG_INTEL_RST) += intel-rst.o | |||
| 55 | obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o | 55 | obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o |
| 56 | 56 | ||
| 57 | obj-$(CONFIG_PVPANIC) += pvpanic.o | 57 | obj-$(CONFIG_PVPANIC) += pvpanic.o |
| 58 | 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 000000000000..3e17e996021c --- /dev/null +++ b/drivers/platform/x86/alienware-wmi.c | |||
| @@ -0,0 +1,557 @@ | |||
| 1 | /* | ||
| 2 | * Alienware AlienFX control | ||
| 3 | * | ||
| 4 | * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com> | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | * GNU General Public License for more details. | ||
| 15 | * | ||
| 16 | */ | ||
| 17 | |||
| 18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
| 19 | |||
| 20 | #include <linux/acpi.h> | ||
| 21 | #include <linux/module.h> | ||
| 22 | #include <linux/platform_device.h> | ||
| 23 | #include <linux/dmi.h> | ||
| 24 | #include <linux/acpi.h> | ||
| 25 | #include <linux/leds.h> | ||
| 26 | |||
| 27 | #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" | ||
| 28 | #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" | ||
| 29 | #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" | ||
| 30 | |||
| 31 | #define WMAX_METHOD_HDMI_SOURCE 0x1 | ||
| 32 | #define WMAX_METHOD_HDMI_STATUS 0x2 | ||
| 33 | #define WMAX_METHOD_BRIGHTNESS 0x3 | ||
| 34 | #define WMAX_METHOD_ZONE_CONTROL 0x4 | ||
| 35 | |||
| 36 | MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>"); | ||
| 37 | MODULE_DESCRIPTION("Alienware special feature control"); | ||
| 38 | MODULE_LICENSE("GPL"); | ||
| 39 | MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); | ||
| 40 | MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); | ||
| 41 | |||
| 42 | enum INTERFACE_FLAGS { | ||
| 43 | LEGACY, | ||
| 44 | WMAX, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum LEGACY_CONTROL_STATES { | ||
| 48 | LEGACY_RUNNING = 1, | ||
| 49 | LEGACY_BOOTING = 0, | ||
| 50 | LEGACY_SUSPEND = 3, | ||
| 51 | }; | ||
| 52 | |||
| 53 | enum WMAX_CONTROL_STATES { | ||
| 54 | WMAX_RUNNING = 0xFF, | ||
| 55 | WMAX_BOOTING = 0, | ||
| 56 | WMAX_SUSPEND = 3, | ||
| 57 | }; | ||
| 58 | |||
| 59 | struct quirk_entry { | ||
| 60 | u8 num_zones; | ||
| 61 | }; | ||
| 62 | |||
| 63 | static struct quirk_entry *quirks; | ||
| 64 | |||
| 65 | static struct quirk_entry quirk_unknown = { | ||
| 66 | .num_zones = 2, | ||
| 67 | }; | ||
| 68 | |||
| 69 | static struct quirk_entry quirk_x51_family = { | ||
| 70 | .num_zones = 3, | ||
| 71 | }; | ||
| 72 | |||
| 73 | static int dmi_matched(const struct dmi_system_id *dmi) | ||
| 74 | { | ||
| 75 | quirks = dmi->driver_data; | ||
| 76 | return 1; | ||
| 77 | } | ||
| 78 | |||
| 79 | static struct dmi_system_id alienware_quirks[] = { | ||
| 80 | { | ||
| 81 | .callback = dmi_matched, | ||
| 82 | .ident = "Alienware X51 R1", | ||
| 83 | .matches = { | ||
| 84 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), | ||
| 85 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), | ||
| 86 | }, | ||
| 87 | .driver_data = &quirk_x51_family, | ||
| 88 | }, | ||
| 89 | { | ||
| 90 | .callback = dmi_matched, | ||
| 91 | .ident = "Alienware X51 R2", | ||
| 92 | .matches = { | ||
| 93 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), | ||
| 94 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), | ||
| 95 | }, | ||
| 96 | .driver_data = &quirk_x51_family, | ||
| 97 | }, | ||
| 98 | {} | ||
| 99 | }; | ||
| 100 | |||
| 101 | struct color_platform { | ||
| 102 | u8 blue; | ||
| 103 | u8 green; | ||
| 104 | u8 red; | ||
| 105 | } __packed; | ||
| 106 | |||
| 107 | struct platform_zone { | ||
| 108 | u8 location; | ||
| 109 | struct device_attribute *attr; | ||
| 110 | struct color_platform colors; | ||
| 111 | }; | ||
| 112 | |||
| 113 | struct wmax_brightness_args { | ||
| 114 | u32 led_mask; | ||
| 115 | u32 percentage; | ||
| 116 | }; | ||
| 117 | |||
| 118 | struct hdmi_args { | ||
| 119 | u8 arg; | ||
| 120 | }; | ||
| 121 | |||
| 122 | struct legacy_led_args { | ||
| 123 | struct color_platform colors; | ||
| 124 | u8 brightness; | ||
| 125 | u8 state; | ||
| 126 | } __packed; | ||
| 127 | |||
| 128 | struct wmax_led_args { | ||
| 129 | u32 led_mask; | ||
| 130 | struct color_platform colors; | ||
| 131 | u8 state; | ||
| 132 | } __packed; | ||
| 133 | |||
| 134 | static struct platform_device *platform_device; | ||
| 135 | static struct device_attribute *zone_dev_attrs; | ||
| 136 | static struct attribute **zone_attrs; | ||
| 137 | static struct platform_zone *zone_data; | ||
| 138 | |||
| 139 | static struct platform_driver platform_driver = { | ||
| 140 | .driver = { | ||
| 141 | .name = "alienware-wmi", | ||
| 142 | .owner = THIS_MODULE, | ||
| 143 | } | ||
| 144 | }; | ||
| 145 | |||
| 146 | static struct attribute_group zone_attribute_group = { | ||
| 147 | .name = "rgb_zones", | ||
| 148 | }; | ||
| 149 | |||
| 150 | static u8 interface; | ||
| 151 | static u8 lighting_control_state; | ||
| 152 | static u8 global_brightness; | ||
| 153 | |||
| 154 | /* | ||
| 155 | * Helpers used for zone control | ||
| 156 | */ | ||
| 157 | static int parse_rgb(const char *buf, struct platform_zone *zone) | ||
| 158 | { | ||
| 159 | long unsigned int rgb; | ||
| 160 | int ret; | ||
| 161 | union color_union { | ||
| 162 | struct color_platform cp; | ||
| 163 | int package; | ||
| 164 | } repackager; | ||
| 165 | |||
| 166 | ret = kstrtoul(buf, 16, &rgb); | ||
| 167 | if (ret) | ||
| 168 | return ret; | ||
| 169 | |||
| 170 | /* RGB triplet notation is 24-bit hexadecimal */ | ||
| 171 | if (rgb > 0xFFFFFF) | ||
| 172 | return -EINVAL; | ||
| 173 | |||
| 174 | repackager.package = rgb & 0x0f0f0f0f; | ||
| 175 | pr_debug("alienware-wmi: r: %d g:%d b: %d\n", | ||
| 176 | repackager.cp.red, repackager.cp.green, repackager.cp.blue); | ||
| 177 | zone->colors = repackager.cp; | ||
| 178 | return 0; | ||
| 179 | } | ||
| 180 | |||
| 181 | static struct platform_zone *match_zone(struct device_attribute *attr) | ||
| 182 | { | ||
| 183 | int i; | ||
| 184 | for (i = 0; i < quirks->num_zones; i++) { | ||
| 185 | if ((struct device_attribute *)zone_data[i].attr == attr) { | ||
| 186 | pr_debug("alienware-wmi: matched zone location: %d\n", | ||
| 187 | zone_data[i].location); | ||
| 188 | return &zone_data[i]; | ||
| 189 | } | ||
| 190 | } | ||
| 191 | return NULL; | ||
| 192 | } | ||
| 193 | |||
| 194 | /* | ||
| 195 | * Individual RGB zone control | ||
| 196 | */ | ||
| 197 | static int alienware_update_led(struct platform_zone *zone) | ||
| 198 | { | ||
| 199 | int method_id; | ||
| 200 | acpi_status status; | ||
| 201 | char *guid; | ||
| 202 | struct acpi_buffer input; | ||
| 203 | struct legacy_led_args legacy_args; | ||
| 204 | struct wmax_led_args wmax_args; | ||
| 205 | if (interface == WMAX) { | ||
| 206 | wmax_args.led_mask = 1 << zone->location; | ||
| 207 | wmax_args.colors = zone->colors; | ||
| 208 | wmax_args.state = lighting_control_state; | ||
| 209 | guid = WMAX_CONTROL_GUID; | ||
| 210 | method_id = WMAX_METHOD_ZONE_CONTROL; | ||
| 211 | |||
| 212 | input.length = (acpi_size) sizeof(wmax_args); | ||
| 213 | input.pointer = &wmax_args; | ||
| 214 | } else { | ||
| 215 | legacy_args.colors = zone->colors; | ||
| 216 | legacy_args.brightness = global_brightness; | ||
| 217 | legacy_args.state = 0; | ||
| 218 | if (lighting_control_state == LEGACY_BOOTING || | ||
| 219 | lighting_control_state == LEGACY_SUSPEND) { | ||
| 220 | guid = LEGACY_POWER_CONTROL_GUID; | ||
| 221 | legacy_args.state = lighting_control_state; | ||
| 222 | } else | ||
| 223 | guid = LEGACY_CONTROL_GUID; | ||
| 224 | method_id = zone->location + 1; | ||
| 225 | |||
| 226 | input.length = (acpi_size) sizeof(legacy_args); | ||
| 227 | input.pointer = &legacy_args; | ||
| 228 | } | ||
| 229 | pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); | ||
| 230 | |||
| 231 | status = wmi_evaluate_method(guid, 1, method_id, &input, NULL); | ||
| 232 | if (ACPI_FAILURE(status)) | ||
| 233 | pr_err("alienware-wmi: zone set failure: %u\n", status); | ||
| 234 | return ACPI_FAILURE(status); | ||
| 235 | } | ||
| 236 | |||
| 237 | static ssize_t zone_show(struct device *dev, struct device_attribute *attr, | ||
| 238 | char *buf) | ||
| 239 | { | ||
| 240 | struct platform_zone *target_zone; | ||
| 241 | target_zone = match_zone(attr); | ||
| 242 | if (target_zone == NULL) | ||
| 243 | return sprintf(buf, "red: -1, green: -1, blue: -1\n"); | ||
| 244 | return sprintf(buf, "red: %d, green: %d, blue: %d\n", | ||
| 245 | target_zone->colors.red, | ||
| 246 | target_zone->colors.green, target_zone->colors.blue); | ||
| 247 | |||
| 248 | } | ||
| 249 | |||
| 250 | static ssize_t zone_set(struct device *dev, struct device_attribute *attr, | ||
| 251 | const char *buf, size_t count) | ||
| 252 | { | ||
| 253 | struct platform_zone *target_zone; | ||
| 254 | int ret; | ||
| 255 | target_zone = match_zone(attr); | ||
| 256 | if (target_zone == NULL) { | ||
| 257 | pr_err("alienware-wmi: invalid target zone\n"); | ||
| 258 | return 1; | ||
| 259 | } | ||
| 260 | ret = parse_rgb(buf, target_zone); | ||
| 261 | if (ret) | ||
| 262 | return ret; | ||
| 263 | ret = alienware_update_led(target_zone); | ||
| 264 | return ret ? ret : count; | ||
| 265 | } | ||
| 266 | |||
| 267 | /* | ||
| 268 | * LED Brightness (Global) | ||
| 269 | */ | ||
| 270 | static int wmax_brightness(int brightness) | ||
| 271 | { | ||
| 272 | acpi_status status; | ||
| 273 | struct acpi_buffer input; | ||
| 274 | struct wmax_brightness_args args = { | ||
| 275 | .led_mask = 0xFF, | ||
| 276 | .percentage = brightness, | ||
| 277 | }; | ||
| 278 | input.length = (acpi_size) sizeof(args); | ||
| 279 | input.pointer = &args; | ||
| 280 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
| 281 | WMAX_METHOD_BRIGHTNESS, &input, NULL); | ||
| 282 | if (ACPI_FAILURE(status)) | ||
| 283 | pr_err("alienware-wmi: brightness set failure: %u\n", status); | ||
| 284 | return ACPI_FAILURE(status); | ||
| 285 | } | ||
| 286 | |||
| 287 | static void global_led_set(struct led_classdev *led_cdev, | ||
| 288 | enum led_brightness brightness) | ||
| 289 | { | ||
| 290 | int ret; | ||
| 291 | global_brightness = brightness; | ||
| 292 | if (interface == WMAX) | ||
| 293 | ret = wmax_brightness(brightness); | ||
| 294 | else | ||
| 295 | ret = alienware_update_led(&zone_data[0]); | ||
| 296 | if (ret) | ||
| 297 | pr_err("LED brightness update failed\n"); | ||
| 298 | } | ||
| 299 | |||
| 300 | static enum led_brightness global_led_get(struct led_classdev *led_cdev) | ||
| 301 | { | ||
| 302 | return global_brightness; | ||
| 303 | } | ||
| 304 | |||
| 305 | static struct led_classdev global_led = { | ||
| 306 | .brightness_set = global_led_set, | ||
| 307 | .brightness_get = global_led_get, | ||
| 308 | .name = "alienware::global_brightness", | ||
| 309 | }; | ||
| 310 | |||
| 311 | /* | ||
| 312 | * Lighting control state device attribute (Global) | ||
| 313 | */ | ||
| 314 | static ssize_t show_control_state(struct device *dev, | ||
| 315 | struct device_attribute *attr, char *buf) | ||
| 316 | { | ||
| 317 | if (lighting_control_state == LEGACY_BOOTING) | ||
| 318 | return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); | ||
| 319 | else if (lighting_control_state == LEGACY_SUSPEND) | ||
| 320 | return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); | ||
| 321 | return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); | ||
| 322 | } | ||
| 323 | |||
| 324 | static ssize_t store_control_state(struct device *dev, | ||
| 325 | struct device_attribute *attr, | ||
| 326 | const char *buf, size_t count) | ||
| 327 | { | ||
| 328 | long unsigned int val; | ||
| 329 | if (strcmp(buf, "booting\n") == 0) | ||
| 330 | val = LEGACY_BOOTING; | ||
| 331 | else if (strcmp(buf, "suspend\n") == 0) | ||
| 332 | val = LEGACY_SUSPEND; | ||
| 333 | else if (interface == LEGACY) | ||
| 334 | val = LEGACY_RUNNING; | ||
| 335 | else | ||
| 336 | val = WMAX_RUNNING; | ||
| 337 | lighting_control_state = val; | ||
| 338 | pr_debug("alienware-wmi: updated control state to %d\n", | ||
| 339 | lighting_control_state); | ||
| 340 | return count; | ||
| 341 | } | ||
| 342 | |||
| 343 | static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, | ||
| 344 | store_control_state); | ||
| 345 | |||
| 346 | static int alienware_zone_init(struct platform_device *dev) | ||
| 347 | { | ||
| 348 | int i; | ||
| 349 | char buffer[10]; | ||
| 350 | char *name; | ||
| 351 | |||
| 352 | if (interface == WMAX) { | ||
| 353 | global_led.max_brightness = 100; | ||
| 354 | lighting_control_state = WMAX_RUNNING; | ||
| 355 | } else if (interface == LEGACY) { | ||
| 356 | global_led.max_brightness = 0x0F; | ||
| 357 | lighting_control_state = LEGACY_RUNNING; | ||
| 358 | } | ||
| 359 | global_brightness = global_led.max_brightness; | ||
| 360 | |||
| 361 | /* | ||
| 362 | * - zone_dev_attrs num_zones + 1 is for individual zones and then | ||
| 363 | * null terminated | ||
| 364 | * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + | ||
| 365 | * the lighting control + null terminated | ||
| 366 | * - zone_data num_zones is for the distinct zones | ||
| 367 | */ | ||
| 368 | zone_dev_attrs = | ||
| 369 | kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1), | ||
| 370 | GFP_KERNEL); | ||
| 371 | zone_attrs = | ||
| 372 | kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2), | ||
| 373 | GFP_KERNEL); | ||
| 374 | zone_data = | ||
| 375 | kzalloc(sizeof(struct platform_zone) * (quirks->num_zones), | ||
| 376 | GFP_KERNEL); | ||
| 377 | |||
| 378 | for (i = 0; i < quirks->num_zones; i++) { | ||
| 379 | sprintf(buffer, "zone%02X", i); | ||
| 380 | name = kstrdup(buffer, GFP_KERNEL); | ||
| 381 | if (name == NULL) | ||
| 382 | return 1; | ||
| 383 | sysfs_attr_init(&zone_dev_attrs[i].attr); | ||
| 384 | zone_dev_attrs[i].attr.name = name; | ||
| 385 | zone_dev_attrs[i].attr.mode = 0644; | ||
| 386 | zone_dev_attrs[i].show = zone_show; | ||
| 387 | zone_dev_attrs[i].store = zone_set; | ||
| 388 | zone_data[i].location = i; | ||
| 389 | zone_attrs[i] = &zone_dev_attrs[i].attr; | ||
| 390 | zone_data[i].attr = &zone_dev_attrs[i]; | ||
| 391 | } | ||
| 392 | zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; | ||
| 393 | zone_attribute_group.attrs = zone_attrs; | ||
| 394 | |||
| 395 | led_classdev_register(&dev->dev, &global_led); | ||
| 396 | |||
| 397 | return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); | ||
| 398 | } | ||
| 399 | |||
| 400 | static void alienware_zone_exit(struct platform_device *dev) | ||
| 401 | { | ||
| 402 | sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); | ||
| 403 | led_classdev_unregister(&global_led); | ||
| 404 | if (zone_dev_attrs) { | ||
| 405 | int i; | ||
| 406 | for (i = 0; i < quirks->num_zones; i++) | ||
| 407 | kfree(zone_dev_attrs[i].attr.name); | ||
| 408 | } | ||
| 409 | kfree(zone_dev_attrs); | ||
| 410 | kfree(zone_data); | ||
| 411 | kfree(zone_attrs); | ||
| 412 | } | ||
| 413 | |||
| 414 | /* | ||
| 415 | The HDMI mux sysfs node indicates the status of the HDMI input mux. | ||
| 416 | It can toggle between standard system GPU output and HDMI input. | ||
| 417 | */ | ||
| 418 | static ssize_t show_hdmi(struct device *dev, struct device_attribute *attr, | ||
| 419 | char *buf) | ||
| 420 | { | ||
| 421 | acpi_status status; | ||
| 422 | struct acpi_buffer input; | ||
| 423 | union acpi_object *obj; | ||
| 424 | u32 tmp = 0; | ||
| 425 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
| 426 | struct hdmi_args in_args = { | ||
| 427 | .arg = 0, | ||
| 428 | }; | ||
| 429 | input.length = (acpi_size) sizeof(in_args); | ||
| 430 | input.pointer = &in_args; | ||
| 431 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
| 432 | WMAX_METHOD_HDMI_STATUS, &input, &output); | ||
| 433 | |||
| 434 | if (ACPI_SUCCESS(status)) { | ||
| 435 | obj = (union acpi_object *)output.pointer; | ||
| 436 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
| 437 | tmp = (u32) obj->integer.value; | ||
| 438 | if (tmp == 1) | ||
| 439 | return scnprintf(buf, PAGE_SIZE, | ||
| 440 | "[input] gpu unknown\n"); | ||
| 441 | else if (tmp == 2) | ||
| 442 | return scnprintf(buf, PAGE_SIZE, | ||
| 443 | "input [gpu] unknown\n"); | ||
| 444 | } | ||
| 445 | pr_err("alienware-wmi: unknown HDMI status: %d\n", status); | ||
| 446 | return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); | ||
| 447 | } | ||
| 448 | |||
| 449 | static ssize_t toggle_hdmi(struct device *dev, struct device_attribute *attr, | ||
| 450 | const char *buf, size_t count) | ||
| 451 | { | ||
| 452 | struct acpi_buffer input; | ||
| 453 | acpi_status status; | ||
| 454 | struct hdmi_args args; | ||
| 455 | if (strcmp(buf, "gpu\n") == 0) | ||
| 456 | args.arg = 1; | ||
| 457 | else if (strcmp(buf, "input\n") == 0) | ||
| 458 | args.arg = 2; | ||
| 459 | else | ||
| 460 | args.arg = 3; | ||
| 461 | pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); | ||
| 462 | input.length = (acpi_size) sizeof(args); | ||
| 463 | input.pointer = &args; | ||
| 464 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, | ||
| 465 | WMAX_METHOD_HDMI_SOURCE, &input, NULL); | ||
| 466 | if (ACPI_FAILURE(status)) | ||
| 467 | pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", | ||
| 468 | status); | ||
| 469 | return count; | ||
| 470 | } | ||
| 471 | |||
| 472 | static DEVICE_ATTR(hdmi, S_IRUGO | S_IWUSR, show_hdmi, toggle_hdmi); | ||
| 473 | |||
| 474 | static void remove_hdmi(struct platform_device *device) | ||
| 475 | { | ||
| 476 | device_remove_file(&device->dev, &dev_attr_hdmi); | ||
| 477 | } | ||
| 478 | |||
| 479 | static int create_hdmi(void) | ||
| 480 | { | ||
| 481 | int ret = -ENOMEM; | ||
| 482 | ret = device_create_file(&platform_device->dev, &dev_attr_hdmi); | ||
| 483 | if (ret) | ||
| 484 | goto error_create_hdmi; | ||
| 485 | return 0; | ||
| 486 | |||
| 487 | error_create_hdmi: | ||
| 488 | remove_hdmi(platform_device); | ||
| 489 | return ret; | ||
| 490 | } | ||
| 491 | |||
| 492 | static int __init alienware_wmi_init(void) | ||
| 493 | { | ||
| 494 | int ret; | ||
| 495 | |||
| 496 | if (wmi_has_guid(LEGACY_CONTROL_GUID)) | ||
| 497 | interface = LEGACY; | ||
| 498 | else if (wmi_has_guid(WMAX_CONTROL_GUID)) | ||
| 499 | interface = WMAX; | ||
| 500 | else { | ||
| 501 | pr_warn("alienware-wmi: No known WMI GUID found\n"); | ||
| 502 | return -ENODEV; | ||
| 503 | } | ||
| 504 | |||
| 505 | dmi_check_system(alienware_quirks); | ||
| 506 | if (quirks == NULL) | ||
| 507 | quirks = &quirk_unknown; | ||
| 508 | |||
| 509 | ret = platform_driver_register(&platform_driver); | ||
| 510 | if (ret) | ||
| 511 | goto fail_platform_driver; | ||
| 512 | platform_device = platform_device_alloc("alienware-wmi", -1); | ||
| 513 | if (!platform_device) { | ||
| 514 | ret = -ENOMEM; | ||
| 515 | goto fail_platform_device1; | ||
| 516 | } | ||
| 517 | ret = platform_device_add(platform_device); | ||
| 518 | if (ret) | ||
| 519 | goto fail_platform_device2; | ||
| 520 | |||
| 521 | if (interface == WMAX) { | ||
| 522 | ret = create_hdmi(); | ||
| 523 | if (ret) | ||
| 524 | goto fail_prep_hdmi; | ||
| 525 | } | ||
| 526 | |||
| 527 | ret = alienware_zone_init(platform_device); | ||
| 528 | if (ret) | ||
| 529 | goto fail_prep_zones; | ||
| 530 | |||
| 531 | return 0; | ||
| 532 | |||
| 533 | fail_prep_zones: | ||
| 534 | alienware_zone_exit(platform_device); | ||
| 535 | fail_prep_hdmi: | ||
| 536 | platform_device_del(platform_device); | ||
| 537 | fail_platform_device2: | ||
| 538 | platform_device_put(platform_device); | ||
| 539 | fail_platform_device1: | ||
| 540 | platform_driver_unregister(&platform_driver); | ||
| 541 | fail_platform_driver: | ||
| 542 | return ret; | ||
| 543 | } | ||
| 544 | |||
| 545 | module_init(alienware_wmi_init); | ||
| 546 | |||
| 547 | static void __exit alienware_wmi_exit(void) | ||
| 548 | { | ||
| 549 | alienware_zone_exit(platform_device); | ||
| 550 | remove_hdmi(platform_device); | ||
| 551 | if (platform_device) { | ||
| 552 | platform_device_unregister(platform_device); | ||
| 553 | platform_driver_unregister(&platform_driver); | ||
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | module_exit(alienware_wmi_exit); | ||
