1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Alienware AlienFX control 4 * 5 * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> 6 */ 7 8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 10 #include <linux/acpi.h> 11 #include <linux/bitfield.h> 12 #include <linux/bits.h> 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/platform_profile.h> 16 #include <linux/dmi.h> 17 #include <linux/leds.h> 18 19 #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" 20 #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" 21 #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" 22 23 #define WMAX_METHOD_HDMI_SOURCE 0x1 24 #define WMAX_METHOD_HDMI_STATUS 0x2 25 #define WMAX_METHOD_BRIGHTNESS 0x3 26 #define WMAX_METHOD_ZONE_CONTROL 0x4 27 #define WMAX_METHOD_HDMI_CABLE 0x5 28 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6 29 #define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B 30 #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C 31 #define WMAX_METHOD_THERMAL_INFORMATION 0x14 32 #define WMAX_METHOD_THERMAL_CONTROL 0x15 33 #define WMAX_METHOD_GAME_SHIFT_STATUS 0x25 34 35 #define WMAX_THERMAL_MODE_GMODE 0xAB 36 37 #define WMAX_FAILURE_CODE 0xFFFFFFFF 38 39 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); 40 MODULE_DESCRIPTION("Alienware special feature control"); 41 MODULE_LICENSE("GPL"); 42 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); 43 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); 44 45 static bool force_platform_profile; 46 module_param_unsafe(force_platform_profile, bool, 0); 47 MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available"); 48 49 static bool force_gmode; 50 module_param_unsafe(force_gmode, bool, 0); 51 MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected"); 52 53 enum INTERFACE_FLAGS { 54 LEGACY, 55 WMAX, 56 }; 57 58 enum LEGACY_CONTROL_STATES { 59 LEGACY_RUNNING = 1, 60 LEGACY_BOOTING = 0, 61 LEGACY_SUSPEND = 3, 62 }; 63 64 enum WMAX_CONTROL_STATES { 65 WMAX_RUNNING = 0xFF, 66 WMAX_BOOTING = 0, 67 WMAX_SUSPEND = 3, 68 }; 69 70 enum WMAX_THERMAL_INFORMATION_OPERATIONS { 71 WMAX_OPERATION_SYS_DESCRIPTION = 0x02, 72 WMAX_OPERATION_LIST_IDS = 0x03, 73 WMAX_OPERATION_CURRENT_PROFILE = 0x0B, 74 }; 75 76 enum WMAX_THERMAL_CONTROL_OPERATIONS { 77 WMAX_OPERATION_ACTIVATE_PROFILE = 0x01, 78 }; 79 80 enum WMAX_GAME_SHIFT_STATUS_OPERATIONS { 81 WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01, 82 WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02, 83 }; 84 85 enum WMAX_THERMAL_TABLES { 86 WMAX_THERMAL_TABLE_BASIC = 0x90, 87 WMAX_THERMAL_TABLE_USTT = 0xA0, 88 }; 89 90 enum wmax_thermal_mode { 91 THERMAL_MODE_USTT_BALANCED, 92 THERMAL_MODE_USTT_BALANCED_PERFORMANCE, 93 THERMAL_MODE_USTT_COOL, 94 THERMAL_MODE_USTT_QUIET, 95 THERMAL_MODE_USTT_PERFORMANCE, 96 THERMAL_MODE_USTT_LOW_POWER, 97 THERMAL_MODE_BASIC_QUIET, 98 THERMAL_MODE_BASIC_BALANCED, 99 THERMAL_MODE_BASIC_BALANCED_PERFORMANCE, 100 THERMAL_MODE_BASIC_PERFORMANCE, 101 THERMAL_MODE_LAST, 102 }; 103 104 static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = { 105 [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, 106 [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, 107 [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL, 108 [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, 109 [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, 110 [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, 111 [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET, 112 [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED, 113 [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, 114 [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, 115 }; 116 117 struct quirk_entry { 118 u8 num_zones; 119 u8 hdmi_mux; 120 u8 amplifier; 121 u8 deepslp; 122 bool thermal; 123 bool gmode; 124 }; 125 126 static struct quirk_entry *quirks; 127 128 129 static struct quirk_entry quirk_inspiron5675 = { 130 .num_zones = 2, 131 .hdmi_mux = 0, 132 .amplifier = 0, 133 .deepslp = 0, 134 .thermal = false, 135 .gmode = false, 136 }; 137 138 static struct quirk_entry quirk_unknown = { 139 .num_zones = 2, 140 .hdmi_mux = 0, 141 .amplifier = 0, 142 .deepslp = 0, 143 .thermal = false, 144 .gmode = false, 145 }; 146 147 static struct quirk_entry quirk_x51_r1_r2 = { 148 .num_zones = 3, 149 .hdmi_mux = 0, 150 .amplifier = 0, 151 .deepslp = 0, 152 .thermal = false, 153 .gmode = false, 154 }; 155 156 static struct quirk_entry quirk_x51_r3 = { 157 .num_zones = 4, 158 .hdmi_mux = 0, 159 .amplifier = 1, 160 .deepslp = 0, 161 .thermal = false, 162 .gmode = false, 163 }; 164 165 static struct quirk_entry quirk_asm100 = { 166 .num_zones = 2, 167 .hdmi_mux = 1, 168 .amplifier = 0, 169 .deepslp = 0, 170 .thermal = false, 171 .gmode = false, 172 }; 173 174 static struct quirk_entry quirk_asm200 = { 175 .num_zones = 2, 176 .hdmi_mux = 1, 177 .amplifier = 0, 178 .deepslp = 1, 179 .thermal = false, 180 .gmode = false, 181 }; 182 183 static struct quirk_entry quirk_asm201 = { 184 .num_zones = 2, 185 .hdmi_mux = 1, 186 .amplifier = 1, 187 .deepslp = 1, 188 .thermal = false, 189 .gmode = false, 190 }; 191 192 static struct quirk_entry quirk_g_series = { 193 .num_zones = 2, 194 .hdmi_mux = 0, 195 .amplifier = 0, 196 .deepslp = 0, 197 .thermal = true, 198 .gmode = true, 199 }; 200 201 static struct quirk_entry quirk_x_series = { 202 .num_zones = 2, 203 .hdmi_mux = 0, 204 .amplifier = 0, 205 .deepslp = 0, 206 .thermal = true, 207 .gmode = false, 208 }; 209 210 static int __init dmi_matched(const struct dmi_system_id *dmi) 211 { 212 quirks = dmi->driver_data; 213 return 1; 214 } 215 216 static const struct dmi_system_id alienware_quirks[] __initconst = { 217 { 218 .callback = dmi_matched, 219 .ident = "Alienware ASM100", 220 .matches = { 221 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 222 DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), 223 }, 224 .driver_data = &quirk_asm100, 225 }, 226 { 227 .callback = dmi_matched, 228 .ident = "Alienware ASM200", 229 .matches = { 230 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 231 DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), 232 }, 233 .driver_data = &quirk_asm200, 234 }, 235 { 236 .callback = dmi_matched, 237 .ident = "Alienware ASM201", 238 .matches = { 239 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 240 DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), 241 }, 242 .driver_data = &quirk_asm201, 243 }, 244 { 245 .callback = dmi_matched, 246 .ident = "Alienware m17 R5", 247 .matches = { 248 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 249 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), 250 }, 251 .driver_data = &quirk_x_series, 252 }, 253 { 254 .callback = dmi_matched, 255 .ident = "Alienware m18 R2", 256 .matches = { 257 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 258 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"), 259 }, 260 .driver_data = &quirk_x_series, 261 }, 262 { 263 .callback = dmi_matched, 264 .ident = "Alienware x15 R1", 265 .matches = { 266 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 267 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"), 268 }, 269 .driver_data = &quirk_x_series, 270 }, 271 { 272 .callback = dmi_matched, 273 .ident = "Alienware x17 R2", 274 .matches = { 275 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 276 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"), 277 }, 278 .driver_data = &quirk_x_series, 279 }, 280 { 281 .callback = dmi_matched, 282 .ident = "Alienware X51 R1", 283 .matches = { 284 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 285 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), 286 }, 287 .driver_data = &quirk_x51_r1_r2, 288 }, 289 { 290 .callback = dmi_matched, 291 .ident = "Alienware X51 R2", 292 .matches = { 293 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 294 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), 295 }, 296 .driver_data = &quirk_x51_r1_r2, 297 }, 298 { 299 .callback = dmi_matched, 300 .ident = "Alienware X51 R3", 301 .matches = { 302 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 303 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), 304 }, 305 .driver_data = &quirk_x51_r3, 306 }, 307 { 308 .callback = dmi_matched, 309 .ident = "Dell Inc. G15 5510", 310 .matches = { 311 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 312 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"), 313 }, 314 .driver_data = &quirk_g_series, 315 }, 316 { 317 .callback = dmi_matched, 318 .ident = "Dell Inc. G15 5511", 319 .matches = { 320 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 321 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"), 322 }, 323 .driver_data = &quirk_g_series, 324 }, 325 { 326 .callback = dmi_matched, 327 .ident = "Dell Inc. G15 5515", 328 .matches = { 329 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 330 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"), 331 }, 332 .driver_data = &quirk_g_series, 333 }, 334 { 335 .callback = dmi_matched, 336 .ident = "Dell Inc. G3 3500", 337 .matches = { 338 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 339 DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"), 340 }, 341 .driver_data = &quirk_g_series, 342 }, 343 { 344 .callback = dmi_matched, 345 .ident = "Dell Inc. G3 3590", 346 .matches = { 347 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 348 DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"), 349 }, 350 .driver_data = &quirk_g_series, 351 }, 352 { 353 .callback = dmi_matched, 354 .ident = "Dell Inc. G5 5500", 355 .matches = { 356 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 357 DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"), 358 }, 359 .driver_data = &quirk_g_series, 360 }, 361 { 362 .callback = dmi_matched, 363 .ident = "Dell Inc. Inspiron 5675", 364 .matches = { 365 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 366 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), 367 }, 368 .driver_data = &quirk_inspiron5675, 369 }, 370 {} 371 }; 372 373 struct color_platform { 374 u8 blue; 375 u8 green; 376 u8 red; 377 } __packed; 378 379 struct platform_zone { 380 u8 location; 381 struct device_attribute *attr; 382 struct color_platform colors; 383 }; 384 385 struct wmax_brightness_args { 386 u32 led_mask; 387 u32 percentage; 388 }; 389 390 struct wmax_basic_args { 391 u8 arg; 392 }; 393 394 struct legacy_led_args { 395 struct color_platform colors; 396 u8 brightness; 397 u8 state; 398 } __packed; 399 400 struct wmax_led_args { 401 u32 led_mask; 402 struct color_platform colors; 403 u8 state; 404 } __packed; 405 406 struct wmax_u32_args { 407 u8 operation; 408 u8 arg1; 409 u8 arg2; 410 u8 arg3; 411 }; 412 413 static struct platform_device *platform_device; 414 static struct device_attribute *zone_dev_attrs; 415 static struct attribute **zone_attrs; 416 static struct platform_zone *zone_data; 417 static struct platform_profile_handler pp_handler; 418 static enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST]; 419 420 static struct platform_driver platform_driver = { 421 .driver = { 422 .name = "alienware-wmi", 423 } 424 }; 425 426 static struct attribute_group zone_attribute_group = { 427 .name = "rgb_zones", 428 }; 429 430 static u8 interface; 431 static u8 lighting_control_state; 432 static u8 global_brightness; 433 434 /* 435 * Helpers used for zone control 436 */ 437 static int parse_rgb(const char *buf, struct platform_zone *zone) 438 { 439 long unsigned int rgb; 440 int ret; 441 union color_union { 442 struct color_platform cp; 443 int package; 444 } repackager; 445 446 ret = kstrtoul(buf, 16, &rgb); 447 if (ret) 448 return ret; 449 450 /* RGB triplet notation is 24-bit hexadecimal */ 451 if (rgb > 0xFFFFFF) 452 return -EINVAL; 453 454 repackager.package = rgb & 0x0f0f0f0f; 455 pr_debug("alienware-wmi: r: %d g:%d b: %d\n", 456 repackager.cp.red, repackager.cp.green, repackager.cp.blue); 457 zone->colors = repackager.cp; 458 return 0; 459 } 460 461 static struct platform_zone *match_zone(struct device_attribute *attr) 462 { 463 u8 zone; 464 465 for (zone = 0; zone < quirks->num_zones; zone++) { 466 if ((struct device_attribute *)zone_data[zone].attr == attr) { 467 pr_debug("alienware-wmi: matched zone location: %d\n", 468 zone_data[zone].location); 469 return &zone_data[zone]; 470 } 471 } 472 return NULL; 473 } 474 475 /* 476 * Individual RGB zone control 477 */ 478 static int alienware_update_led(struct platform_zone *zone) 479 { 480 int method_id; 481 acpi_status status; 482 char *guid; 483 struct acpi_buffer input; 484 struct legacy_led_args legacy_args; 485 struct wmax_led_args wmax_basic_args; 486 if (interface == WMAX) { 487 wmax_basic_args.led_mask = 1 << zone->location; 488 wmax_basic_args.colors = zone->colors; 489 wmax_basic_args.state = lighting_control_state; 490 guid = WMAX_CONTROL_GUID; 491 method_id = WMAX_METHOD_ZONE_CONTROL; 492 493 input.length = sizeof(wmax_basic_args); 494 input.pointer = &wmax_basic_args; 495 } else { 496 legacy_args.colors = zone->colors; 497 legacy_args.brightness = global_brightness; 498 legacy_args.state = 0; 499 if (lighting_control_state == LEGACY_BOOTING || 500 lighting_control_state == LEGACY_SUSPEND) { 501 guid = LEGACY_POWER_CONTROL_GUID; 502 legacy_args.state = lighting_control_state; 503 } else 504 guid = LEGACY_CONTROL_GUID; 505 method_id = zone->location + 1; 506 507 input.length = sizeof(legacy_args); 508 input.pointer = &legacy_args; 509 } 510 pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); 511 512 status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); 513 if (ACPI_FAILURE(status)) 514 pr_err("alienware-wmi: zone set failure: %u\n", status); 515 return ACPI_FAILURE(status); 516 } 517 518 static ssize_t zone_show(struct device *dev, struct device_attribute *attr, 519 char *buf) 520 { 521 struct platform_zone *target_zone; 522 target_zone = match_zone(attr); 523 if (target_zone == NULL) 524 return sprintf(buf, "red: -1, green: -1, blue: -1\n"); 525 return sprintf(buf, "red: %d, green: %d, blue: %d\n", 526 target_zone->colors.red, 527 target_zone->colors.green, target_zone->colors.blue); 528 529 } 530 531 static ssize_t zone_set(struct device *dev, struct device_attribute *attr, 532 const char *buf, size_t count) 533 { 534 struct platform_zone *target_zone; 535 int ret; 536 target_zone = match_zone(attr); 537 if (target_zone == NULL) { 538 pr_err("alienware-wmi: invalid target zone\n"); 539 return 1; 540 } 541 ret = parse_rgb(buf, target_zone); 542 if (ret) 543 return ret; 544 ret = alienware_update_led(target_zone); 545 return ret ? ret : count; 546 } 547 548 /* 549 * LED Brightness (Global) 550 */ 551 static int wmax_brightness(int brightness) 552 { 553 acpi_status status; 554 struct acpi_buffer input; 555 struct wmax_brightness_args args = { 556 .led_mask = 0xFF, 557 .percentage = brightness, 558 }; 559 input.length = sizeof(args); 560 input.pointer = &args; 561 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, 562 WMAX_METHOD_BRIGHTNESS, &input, NULL); 563 if (ACPI_FAILURE(status)) 564 pr_err("alienware-wmi: brightness set failure: %u\n", status); 565 return ACPI_FAILURE(status); 566 } 567 568 static void global_led_set(struct led_classdev *led_cdev, 569 enum led_brightness brightness) 570 { 571 int ret; 572 global_brightness = brightness; 573 if (interface == WMAX) 574 ret = wmax_brightness(brightness); 575 else 576 ret = alienware_update_led(&zone_data[0]); 577 if (ret) 578 pr_err("LED brightness update failed\n"); 579 } 580 581 static enum led_brightness global_led_get(struct led_classdev *led_cdev) 582 { 583 return global_brightness; 584 } 585 586 static struct led_classdev global_led = { 587 .brightness_set = global_led_set, 588 .brightness_get = global_led_get, 589 .name = "alienware::global_brightness", 590 }; 591 592 /* 593 * Lighting control state device attribute (Global) 594 */ 595 static ssize_t show_control_state(struct device *dev, 596 struct device_attribute *attr, char *buf) 597 { 598 if (lighting_control_state == LEGACY_BOOTING) 599 return sysfs_emit(buf, "[booting] running suspend\n"); 600 else if (lighting_control_state == LEGACY_SUSPEND) 601 return sysfs_emit(buf, "booting running [suspend]\n"); 602 return sysfs_emit(buf, "booting [running] suspend\n"); 603 } 604 605 static ssize_t store_control_state(struct device *dev, 606 struct device_attribute *attr, 607 const char *buf, size_t count) 608 { 609 long unsigned int val; 610 if (strcmp(buf, "booting\n") == 0) 611 val = LEGACY_BOOTING; 612 else if (strcmp(buf, "suspend\n") == 0) 613 val = LEGACY_SUSPEND; 614 else if (interface == LEGACY) 615 val = LEGACY_RUNNING; 616 else 617 val = WMAX_RUNNING; 618 lighting_control_state = val; 619 pr_debug("alienware-wmi: updated control state to %d\n", 620 lighting_control_state); 621 return count; 622 } 623 624 static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, 625 store_control_state); 626 627 static int alienware_zone_init(struct platform_device *dev) 628 { 629 u8 zone; 630 char *name; 631 632 if (interface == WMAX) { 633 lighting_control_state = WMAX_RUNNING; 634 } else if (interface == LEGACY) { 635 lighting_control_state = LEGACY_RUNNING; 636 } 637 global_led.max_brightness = 0x0F; 638 global_brightness = global_led.max_brightness; 639 640 /* 641 * - zone_dev_attrs num_zones + 1 is for individual zones and then 642 * null terminated 643 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + 644 * the lighting control + null terminated 645 * - zone_data num_zones is for the distinct zones 646 */ 647 zone_dev_attrs = 648 kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), 649 GFP_KERNEL); 650 if (!zone_dev_attrs) 651 return -ENOMEM; 652 653 zone_attrs = 654 kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), 655 GFP_KERNEL); 656 if (!zone_attrs) 657 return -ENOMEM; 658 659 zone_data = 660 kcalloc(quirks->num_zones, sizeof(struct platform_zone), 661 GFP_KERNEL); 662 if (!zone_data) 663 return -ENOMEM; 664 665 for (zone = 0; zone < quirks->num_zones; zone++) { 666 name = kasprintf(GFP_KERNEL, "zone%02hhX", zone); 667 if (name == NULL) 668 return 1; 669 sysfs_attr_init(&zone_dev_attrs[zone].attr); 670 zone_dev_attrs[zone].attr.name = name; 671 zone_dev_attrs[zone].attr.mode = 0644; 672 zone_dev_attrs[zone].show = zone_show; 673 zone_dev_attrs[zone].store = zone_set; 674 zone_data[zone].location = zone; 675 zone_attrs[zone] = &zone_dev_attrs[zone].attr; 676 zone_data[zone].attr = &zone_dev_attrs[zone]; 677 } 678 zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; 679 zone_attribute_group.attrs = zone_attrs; 680 681 led_classdev_register(&dev->dev, &global_led); 682 683 return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); 684 } 685 686 static void alienware_zone_exit(struct platform_device *dev) 687 { 688 u8 zone; 689 690 sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); 691 led_classdev_unregister(&global_led); 692 if (zone_dev_attrs) { 693 for (zone = 0; zone < quirks->num_zones; zone++) 694 kfree(zone_dev_attrs[zone].attr.name); 695 } 696 kfree(zone_dev_attrs); 697 kfree(zone_data); 698 kfree(zone_attrs); 699 } 700 701 static acpi_status alienware_wmax_command(void *in_args, size_t in_size, 702 u32 command, u32 *out_data) 703 { 704 acpi_status status; 705 union acpi_object *obj; 706 struct acpi_buffer input; 707 struct acpi_buffer output; 708 709 input.length = in_size; 710 input.pointer = in_args; 711 if (out_data) { 712 output.length = ACPI_ALLOCATE_BUFFER; 713 output.pointer = NULL; 714 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, 715 command, &input, &output); 716 if (ACPI_SUCCESS(status)) { 717 obj = (union acpi_object *)output.pointer; 718 if (obj && obj->type == ACPI_TYPE_INTEGER) 719 *out_data = (u32)obj->integer.value; 720 } 721 kfree(output.pointer); 722 } else { 723 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, 724 command, &input, NULL); 725 } 726 return status; 727 } 728 729 /* 730 * The HDMI mux sysfs node indicates the status of the HDMI input mux. 731 * It can toggle between standard system GPU output and HDMI input. 732 */ 733 static ssize_t show_hdmi_cable(struct device *dev, 734 struct device_attribute *attr, char *buf) 735 { 736 acpi_status status; 737 u32 out_data; 738 struct wmax_basic_args in_args = { 739 .arg = 0, 740 }; 741 status = 742 alienware_wmax_command(&in_args, sizeof(in_args), 743 WMAX_METHOD_HDMI_CABLE, &out_data); 744 if (ACPI_SUCCESS(status)) { 745 if (out_data == 0) 746 return sysfs_emit(buf, "[unconnected] connected unknown\n"); 747 else if (out_data == 1) 748 return sysfs_emit(buf, "unconnected [connected] unknown\n"); 749 } 750 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); 751 return sysfs_emit(buf, "unconnected connected [unknown]\n"); 752 } 753 754 static ssize_t show_hdmi_source(struct device *dev, 755 struct device_attribute *attr, char *buf) 756 { 757 acpi_status status; 758 u32 out_data; 759 struct wmax_basic_args in_args = { 760 .arg = 0, 761 }; 762 status = 763 alienware_wmax_command(&in_args, sizeof(in_args), 764 WMAX_METHOD_HDMI_STATUS, &out_data); 765 766 if (ACPI_SUCCESS(status)) { 767 if (out_data == 1) 768 return sysfs_emit(buf, "[input] gpu unknown\n"); 769 else if (out_data == 2) 770 return sysfs_emit(buf, "input [gpu] unknown\n"); 771 } 772 pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); 773 return sysfs_emit(buf, "input gpu [unknown]\n"); 774 } 775 776 static ssize_t toggle_hdmi_source(struct device *dev, 777 struct device_attribute *attr, 778 const char *buf, size_t count) 779 { 780 acpi_status status; 781 struct wmax_basic_args args; 782 if (strcmp(buf, "gpu\n") == 0) 783 args.arg = 1; 784 else if (strcmp(buf, "input\n") == 0) 785 args.arg = 2; 786 else 787 args.arg = 3; 788 pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); 789 790 status = alienware_wmax_command(&args, sizeof(args), 791 WMAX_METHOD_HDMI_SOURCE, NULL); 792 793 if (ACPI_FAILURE(status)) 794 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", 795 status); 796 return count; 797 } 798 799 static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); 800 static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, 801 toggle_hdmi_source); 802 803 static struct attribute *hdmi_attrs[] = { 804 &dev_attr_cable.attr, 805 &dev_attr_source.attr, 806 NULL, 807 }; 808 809 static const struct attribute_group hdmi_attribute_group = { 810 .name = "hdmi", 811 .attrs = hdmi_attrs, 812 }; 813 814 static void remove_hdmi(struct platform_device *dev) 815 { 816 if (quirks->hdmi_mux > 0) 817 sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); 818 } 819 820 static int create_hdmi(struct platform_device *dev) 821 { 822 int ret; 823 824 ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); 825 if (ret) 826 remove_hdmi(dev); 827 return ret; 828 } 829 830 /* 831 * Alienware GFX amplifier support 832 * - Currently supports reading cable status 833 * - Leaving expansion room to possibly support dock/undock events later 834 */ 835 static ssize_t show_amplifier_status(struct device *dev, 836 struct device_attribute *attr, char *buf) 837 { 838 acpi_status status; 839 u32 out_data; 840 struct wmax_basic_args in_args = { 841 .arg = 0, 842 }; 843 status = 844 alienware_wmax_command(&in_args, sizeof(in_args), 845 WMAX_METHOD_AMPLIFIER_CABLE, &out_data); 846 if (ACPI_SUCCESS(status)) { 847 if (out_data == 0) 848 return sysfs_emit(buf, "[unconnected] connected unknown\n"); 849 else if (out_data == 1) 850 return sysfs_emit(buf, "unconnected [connected] unknown\n"); 851 } 852 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); 853 return sysfs_emit(buf, "unconnected connected [unknown]\n"); 854 } 855 856 static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); 857 858 static struct attribute *amplifier_attrs[] = { 859 &dev_attr_status.attr, 860 NULL, 861 }; 862 863 static const struct attribute_group amplifier_attribute_group = { 864 .name = "amplifier", 865 .attrs = amplifier_attrs, 866 }; 867 868 static void remove_amplifier(struct platform_device *dev) 869 { 870 if (quirks->amplifier > 0) 871 sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); 872 } 873 874 static int create_amplifier(struct platform_device *dev) 875 { 876 int ret; 877 878 ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); 879 if (ret) 880 remove_amplifier(dev); 881 return ret; 882 } 883 884 /* 885 * Deep Sleep Control support 886 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events 887 */ 888 static ssize_t show_deepsleep_status(struct device *dev, 889 struct device_attribute *attr, char *buf) 890 { 891 acpi_status status; 892 u32 out_data; 893 struct wmax_basic_args in_args = { 894 .arg = 0, 895 }; 896 status = alienware_wmax_command(&in_args, sizeof(in_args), 897 WMAX_METHOD_DEEP_SLEEP_STATUS, &out_data); 898 if (ACPI_SUCCESS(status)) { 899 if (out_data == 0) 900 return sysfs_emit(buf, "[disabled] s5 s5_s4\n"); 901 else if (out_data == 1) 902 return sysfs_emit(buf, "disabled [s5] s5_s4\n"); 903 else if (out_data == 2) 904 return sysfs_emit(buf, "disabled s5 [s5_s4]\n"); 905 } 906 pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); 907 return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n"); 908 } 909 910 static ssize_t toggle_deepsleep(struct device *dev, 911 struct device_attribute *attr, 912 const char *buf, size_t count) 913 { 914 acpi_status status; 915 struct wmax_basic_args args; 916 917 if (strcmp(buf, "disabled\n") == 0) 918 args.arg = 0; 919 else if (strcmp(buf, "s5\n") == 0) 920 args.arg = 1; 921 else 922 args.arg = 2; 923 pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); 924 925 status = alienware_wmax_command(&args, sizeof(args), 926 WMAX_METHOD_DEEP_SLEEP_CONTROL, NULL); 927 928 if (ACPI_FAILURE(status)) 929 pr_err("alienware-wmi: deep sleep control failed: results: %u\n", 930 status); 931 return count; 932 } 933 934 static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); 935 936 static struct attribute *deepsleep_attrs[] = { 937 &dev_attr_deepsleep.attr, 938 NULL, 939 }; 940 941 static const struct attribute_group deepsleep_attribute_group = { 942 .name = "deepsleep", 943 .attrs = deepsleep_attrs, 944 }; 945 946 static void remove_deepsleep(struct platform_device *dev) 947 { 948 if (quirks->deepslp > 0) 949 sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); 950 } 951 952 static int create_deepsleep(struct platform_device *dev) 953 { 954 int ret; 955 956 ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); 957 if (ret) 958 remove_deepsleep(dev); 959 return ret; 960 } 961 962 /* 963 * Thermal Profile control 964 * - Provides thermal profile control through the Platform Profile API 965 */ 966 #define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4) 967 #define WMAX_THERMAL_MODE_MASK GENMASK(3, 0) 968 #define WMAX_SENSOR_ID_MASK BIT(8) 969 970 static bool is_wmax_thermal_code(u32 code) 971 { 972 if (code & WMAX_SENSOR_ID_MASK) 973 return false; 974 975 if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST) 976 return false; 977 978 if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC && 979 (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET) 980 return true; 981 982 if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT && 983 (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER) 984 return true; 985 986 return false; 987 } 988 989 static int wmax_thermal_information(u8 operation, u8 arg, u32 *out_data) 990 { 991 acpi_status status; 992 struct wmax_u32_args in_args = { 993 .operation = operation, 994 .arg1 = arg, 995 .arg2 = 0, 996 .arg3 = 0, 997 }; 998 999 status = alienware_wmax_command(&in_args, sizeof(in_args), 1000 WMAX_METHOD_THERMAL_INFORMATION, 1001 out_data); 1002 1003 if (ACPI_FAILURE(status)) 1004 return -EIO; 1005 1006 if (*out_data == WMAX_FAILURE_CODE) 1007 return -EBADRQC; 1008 1009 return 0; 1010 } 1011 1012 static int wmax_thermal_control(u8 profile) 1013 { 1014 acpi_status status; 1015 struct wmax_u32_args in_args = { 1016 .operation = WMAX_OPERATION_ACTIVATE_PROFILE, 1017 .arg1 = profile, 1018 .arg2 = 0, 1019 .arg3 = 0, 1020 }; 1021 u32 out_data; 1022 1023 status = alienware_wmax_command(&in_args, sizeof(in_args), 1024 WMAX_METHOD_THERMAL_CONTROL, 1025 &out_data); 1026 1027 if (ACPI_FAILURE(status)) 1028 return -EIO; 1029 1030 if (out_data == WMAX_FAILURE_CODE) 1031 return -EBADRQC; 1032 1033 return 0; 1034 } 1035 1036 static int wmax_game_shift_status(u8 operation, u32 *out_data) 1037 { 1038 acpi_status status; 1039 struct wmax_u32_args in_args = { 1040 .operation = operation, 1041 .arg1 = 0, 1042 .arg2 = 0, 1043 .arg3 = 0, 1044 }; 1045 1046 status = alienware_wmax_command(&in_args, sizeof(in_args), 1047 WMAX_METHOD_GAME_SHIFT_STATUS, 1048 out_data); 1049 1050 if (ACPI_FAILURE(status)) 1051 return -EIO; 1052 1053 if (*out_data == WMAX_FAILURE_CODE) 1054 return -EOPNOTSUPP; 1055 1056 return 0; 1057 } 1058 1059 static int thermal_profile_get(struct platform_profile_handler *pprof, 1060 enum platform_profile_option *profile) 1061 { 1062 u32 out_data; 1063 int ret; 1064 1065 ret = wmax_thermal_information(WMAX_OPERATION_CURRENT_PROFILE, 1066 0, &out_data); 1067 1068 if (ret < 0) 1069 return ret; 1070 1071 if (out_data == WMAX_THERMAL_MODE_GMODE) { 1072 *profile = PLATFORM_PROFILE_PERFORMANCE; 1073 return 0; 1074 } 1075 1076 if (!is_wmax_thermal_code(out_data)) 1077 return -ENODATA; 1078 1079 out_data &= WMAX_THERMAL_MODE_MASK; 1080 *profile = wmax_mode_to_platform_profile[out_data]; 1081 1082 return 0; 1083 } 1084 1085 static int thermal_profile_set(struct platform_profile_handler *pprof, 1086 enum platform_profile_option profile) 1087 { 1088 if (quirks->gmode) { 1089 u32 gmode_status; 1090 int ret; 1091 1092 ret = wmax_game_shift_status(WMAX_OPERATION_GET_GAME_SHIFT_STATUS, 1093 &gmode_status); 1094 1095 if (ret < 0) 1096 return ret; 1097 1098 if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) || 1099 (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) { 1100 ret = wmax_game_shift_status(WMAX_OPERATION_TOGGLE_GAME_SHIFT, 1101 &gmode_status); 1102 1103 if (ret < 0) 1104 return ret; 1105 } 1106 } 1107 1108 return wmax_thermal_control(supported_thermal_profiles[profile]); 1109 } 1110 1111 static int create_thermal_profile(void) 1112 { 1113 u32 out_data; 1114 u8 sys_desc[4]; 1115 u32 first_mode; 1116 enum wmax_thermal_mode mode; 1117 enum platform_profile_option profile; 1118 int ret; 1119 1120 ret = wmax_thermal_information(WMAX_OPERATION_SYS_DESCRIPTION, 1121 0, (u32 *) &sys_desc); 1122 if (ret < 0) 1123 return ret; 1124 1125 first_mode = sys_desc[0] + sys_desc[1]; 1126 1127 for (u32 i = 0; i < sys_desc[3]; i++) { 1128 ret = wmax_thermal_information(WMAX_OPERATION_LIST_IDS, 1129 i + first_mode, &out_data); 1130 1131 if (ret == -EIO) 1132 return ret; 1133 1134 if (ret == -EBADRQC) 1135 break; 1136 1137 if (!is_wmax_thermal_code(out_data)) 1138 continue; 1139 1140 mode = out_data & WMAX_THERMAL_MODE_MASK; 1141 profile = wmax_mode_to_platform_profile[mode]; 1142 supported_thermal_profiles[profile] = out_data; 1143 1144 set_bit(profile, pp_handler.choices); 1145 } 1146 1147 if (bitmap_empty(pp_handler.choices, PLATFORM_PROFILE_LAST)) 1148 return -ENODEV; 1149 1150 if (quirks->gmode) { 1151 supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] = 1152 WMAX_THERMAL_MODE_GMODE; 1153 1154 set_bit(PLATFORM_PROFILE_PERFORMANCE, pp_handler.choices); 1155 } 1156 1157 pp_handler.profile_get = thermal_profile_get; 1158 pp_handler.profile_set = thermal_profile_set; 1159 1160 return platform_profile_register(&pp_handler); 1161 } 1162 1163 static void remove_thermal_profile(void) 1164 { 1165 if (quirks->thermal) 1166 platform_profile_remove(); 1167 } 1168 1169 static int __init alienware_wmi_init(void) 1170 { 1171 int ret; 1172 1173 if (wmi_has_guid(LEGACY_CONTROL_GUID)) 1174 interface = LEGACY; 1175 else if (wmi_has_guid(WMAX_CONTROL_GUID)) 1176 interface = WMAX; 1177 else { 1178 pr_warn("alienware-wmi: No known WMI GUID found\n"); 1179 return -ENODEV; 1180 } 1181 1182 dmi_check_system(alienware_quirks); 1183 if (quirks == NULL) 1184 quirks = &quirk_unknown; 1185 1186 if (force_platform_profile) 1187 quirks->thermal = true; 1188 1189 if (force_gmode) { 1190 if (quirks->thermal) 1191 quirks->gmode = true; 1192 else 1193 pr_warn("force_gmode requires platform profile support\n"); 1194 } 1195 1196 ret = platform_driver_register(&platform_driver); 1197 if (ret) 1198 goto fail_platform_driver; 1199 platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE); 1200 if (!platform_device) { 1201 ret = -ENOMEM; 1202 goto fail_platform_device1; 1203 } 1204 ret = platform_device_add(platform_device); 1205 if (ret) 1206 goto fail_platform_device2; 1207 1208 if (quirks->hdmi_mux > 0) { 1209 ret = create_hdmi(platform_device); 1210 if (ret) 1211 goto fail_prep_hdmi; 1212 } 1213 1214 if (quirks->amplifier > 0) { 1215 ret = create_amplifier(platform_device); 1216 if (ret) 1217 goto fail_prep_amplifier; 1218 } 1219 1220 if (quirks->deepslp > 0) { 1221 ret = create_deepsleep(platform_device); 1222 if (ret) 1223 goto fail_prep_deepsleep; 1224 } 1225 1226 if (quirks->thermal) { 1227 ret = create_thermal_profile(); 1228 if (ret) 1229 goto fail_prep_thermal_profile; 1230 } 1231 1232 ret = alienware_zone_init(platform_device); 1233 if (ret) 1234 goto fail_prep_zones; 1235 1236 return 0; 1237 1238 fail_prep_zones: 1239 alienware_zone_exit(platform_device); 1240 remove_thermal_profile(); 1241 fail_prep_thermal_profile: 1242 fail_prep_deepsleep: 1243 fail_prep_amplifier: 1244 fail_prep_hdmi: 1245 platform_device_del(platform_device); 1246 fail_platform_device2: 1247 platform_device_put(platform_device); 1248 fail_platform_device1: 1249 platform_driver_unregister(&platform_driver); 1250 fail_platform_driver: 1251 return ret; 1252 } 1253 1254 module_init(alienware_wmi_init); 1255 1256 static void __exit alienware_wmi_exit(void) 1257 { 1258 if (platform_device) { 1259 alienware_zone_exit(platform_device); 1260 remove_hdmi(platform_device); 1261 remove_thermal_profile(); 1262 platform_device_unregister(platform_device); 1263 platform_driver_unregister(&platform_driver); 1264 } 1265 } 1266 1267 module_exit(alienware_wmi_exit); 1268