1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Alienware special feature control 4 * 5 * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> 6 * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> 7 */ 8 9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 11 #include <linux/acpi.h> 12 #include <linux/cleanup.h> 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/dmi.h> 16 #include <linux/leds.h> 17 #include "alienware-wmi.h" 18 19 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); 20 MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>"); 21 MODULE_DESCRIPTION("Alienware special feature control"); 22 MODULE_LICENSE("GPL"); 23 24 struct alienfx_quirks *alienfx; 25 26 static struct alienfx_quirks quirk_inspiron5675 = { 27 .num_zones = 2, 28 .hdmi_mux = false, 29 .amplifier = false, 30 .deepslp = false, 31 }; 32 33 static struct alienfx_quirks quirk_unknown = { 34 .num_zones = 2, 35 .hdmi_mux = false, 36 .amplifier = false, 37 .deepslp = false, 38 }; 39 40 static struct alienfx_quirks quirk_x51_r1_r2 = { 41 .num_zones = 3, 42 .hdmi_mux = false, 43 .amplifier = false, 44 .deepslp = false, 45 }; 46 47 static struct alienfx_quirks quirk_x51_r3 = { 48 .num_zones = 4, 49 .hdmi_mux = false, 50 .amplifier = true, 51 .deepslp = false, 52 }; 53 54 static struct alienfx_quirks quirk_asm100 = { 55 .num_zones = 2, 56 .hdmi_mux = true, 57 .amplifier = false, 58 .deepslp = false, 59 }; 60 61 static struct alienfx_quirks quirk_asm200 = { 62 .num_zones = 2, 63 .hdmi_mux = true, 64 .amplifier = false, 65 .deepslp = true, 66 }; 67 68 static struct alienfx_quirks quirk_asm201 = { 69 .num_zones = 2, 70 .hdmi_mux = true, 71 .amplifier = true, 72 .deepslp = true, 73 }; 74 75 static int __init dmi_matched(const struct dmi_system_id *dmi) 76 { 77 alienfx = dmi->driver_data; 78 return 1; 79 } 80 81 static const struct dmi_system_id alienware_quirks[] __initconst = { 82 { 83 .callback = dmi_matched, 84 .ident = "Alienware ASM100", 85 .matches = { 86 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 87 DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), 88 }, 89 .driver_data = &quirk_asm100, 90 }, 91 { 92 .callback = dmi_matched, 93 .ident = "Alienware ASM200", 94 .matches = { 95 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 96 DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), 97 }, 98 .driver_data = &quirk_asm200, 99 }, 100 { 101 .callback = dmi_matched, 102 .ident = "Alienware ASM201", 103 .matches = { 104 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 105 DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), 106 }, 107 .driver_data = &quirk_asm201, 108 }, 109 { 110 .callback = dmi_matched, 111 .ident = "Alienware X51 R1", 112 .matches = { 113 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 114 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), 115 }, 116 .driver_data = &quirk_x51_r1_r2, 117 }, 118 { 119 .callback = dmi_matched, 120 .ident = "Alienware X51 R2", 121 .matches = { 122 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 123 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), 124 }, 125 .driver_data = &quirk_x51_r1_r2, 126 }, 127 { 128 .callback = dmi_matched, 129 .ident = "Alienware X51 R3", 130 .matches = { 131 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), 132 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), 133 }, 134 .driver_data = &quirk_x51_r3, 135 }, 136 { 137 .callback = dmi_matched, 138 .ident = "Dell Inc. Inspiron 5675", 139 .matches = { 140 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 141 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), 142 }, 143 .driver_data = &quirk_inspiron5675, 144 }, 145 {} 146 }; 147 148 u8 alienware_interface; 149 150 int alienware_wmi_command(struct wmi_device *wdev, u32 method_id, 151 void *in_args, size_t in_size, u32 *out_data) 152 { 153 struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; 154 struct acpi_buffer in = {in_size, in_args}; 155 acpi_status ret; 156 157 ret = wmidev_evaluate_method(wdev, 0, method_id, &in, out_data ? &out : NULL); 158 if (ACPI_FAILURE(ret)) 159 return -EIO; 160 161 union acpi_object *obj __free(kfree) = out.pointer; 162 163 if (out_data) { 164 if (obj && obj->type == ACPI_TYPE_INTEGER) 165 *out_data = (u32)obj->integer.value; 166 else 167 return -ENOMSG; 168 } 169 170 return 0; 171 } 172 173 /* 174 * Helpers used for zone control 175 */ 176 static int parse_rgb(const char *buf, struct color_platform *colors) 177 { 178 long unsigned int rgb; 179 int ret; 180 union color_union { 181 struct color_platform cp; 182 int package; 183 } repackager; 184 185 ret = kstrtoul(buf, 16, &rgb); 186 if (ret) 187 return ret; 188 189 /* RGB triplet notation is 24-bit hexadecimal */ 190 if (rgb > 0xFFFFFF) 191 return -EINVAL; 192 193 repackager.package = rgb & 0x0f0f0f0f; 194 pr_debug("alienware-wmi: r: %d g:%d b: %d\n", 195 repackager.cp.red, repackager.cp.green, repackager.cp.blue); 196 *colors = repackager.cp; 197 return 0; 198 } 199 200 /* 201 * Individual RGB zone control 202 */ 203 static ssize_t zone_show(struct device *dev, struct device_attribute *attr, 204 char *buf, u8 location) 205 { 206 struct alienfx_priv *priv = dev_get_drvdata(dev); 207 struct color_platform *colors = &priv->colors[location]; 208 209 return sprintf(buf, "red: %d, green: %d, blue: %d\n", 210 colors->red, colors->green, colors->blue); 211 212 } 213 214 static ssize_t zone_store(struct device *dev, struct device_attribute *attr, 215 const char *buf, size_t count, u8 location) 216 { 217 struct alienfx_priv *priv = dev_get_drvdata(dev); 218 struct color_platform *colors = &priv->colors[location]; 219 struct alienfx_platdata *pdata = dev_get_platdata(dev); 220 int ret; 221 222 ret = parse_rgb(buf, colors); 223 if (ret) 224 return ret; 225 226 ret = pdata->ops.upd_led(priv, pdata->wdev, location); 227 228 return ret ? ret : count; 229 } 230 231 static ssize_t zone00_show(struct device *dev, struct device_attribute *attr, 232 char *buf) 233 { 234 return zone_show(dev, attr, buf, 0); 235 } 236 237 static ssize_t zone00_store(struct device *dev, struct device_attribute *attr, 238 const char *buf, size_t count) 239 { 240 return zone_store(dev, attr, buf, count, 0); 241 } 242 243 static DEVICE_ATTR_RW(zone00); 244 245 static ssize_t zone01_show(struct device *dev, struct device_attribute *attr, 246 char *buf) 247 { 248 return zone_show(dev, attr, buf, 1); 249 } 250 251 static ssize_t zone01_store(struct device *dev, struct device_attribute *attr, 252 const char *buf, size_t count) 253 { 254 return zone_store(dev, attr, buf, count, 1); 255 } 256 257 static DEVICE_ATTR_RW(zone01); 258 259 static ssize_t zone02_show(struct device *dev, struct device_attribute *attr, 260 char *buf) 261 { 262 return zone_show(dev, attr, buf, 2); 263 } 264 265 static ssize_t zone02_store(struct device *dev, struct device_attribute *attr, 266 const char *buf, size_t count) 267 { 268 return zone_store(dev, attr, buf, count, 2); 269 } 270 271 static DEVICE_ATTR_RW(zone02); 272 273 static ssize_t zone03_show(struct device *dev, struct device_attribute *attr, 274 char *buf) 275 { 276 return zone_show(dev, attr, buf, 3); 277 } 278 279 static ssize_t zone03_store(struct device *dev, struct device_attribute *attr, 280 const char *buf, size_t count) 281 { 282 return zone_store(dev, attr, buf, count, 3); 283 } 284 285 static DEVICE_ATTR_RW(zone03); 286 287 /* 288 * Lighting control state device attribute (Global) 289 */ 290 static ssize_t lighting_control_state_show(struct device *dev, 291 struct device_attribute *attr, 292 char *buf) 293 { 294 struct alienfx_priv *priv = dev_get_drvdata(dev); 295 296 if (priv->lighting_control_state == LEGACY_BOOTING) 297 return sysfs_emit(buf, "[booting] running suspend\n"); 298 else if (priv->lighting_control_state == LEGACY_SUSPEND) 299 return sysfs_emit(buf, "booting running [suspend]\n"); 300 301 return sysfs_emit(buf, "booting [running] suspend\n"); 302 } 303 304 static ssize_t lighting_control_state_store(struct device *dev, 305 struct device_attribute *attr, 306 const char *buf, size_t count) 307 { 308 struct alienfx_priv *priv = dev_get_drvdata(dev); 309 u8 val; 310 311 if (strcmp(buf, "booting\n") == 0) 312 val = LEGACY_BOOTING; 313 else if (strcmp(buf, "suspend\n") == 0) 314 val = LEGACY_SUSPEND; 315 else if (alienware_interface == LEGACY) 316 val = LEGACY_RUNNING; 317 else 318 val = WMAX_RUNNING; 319 320 priv->lighting_control_state = val; 321 pr_debug("alienware-wmi: updated control state to %d\n", 322 priv->lighting_control_state); 323 324 return count; 325 } 326 327 static DEVICE_ATTR_RW(lighting_control_state); 328 329 static umode_t zone_attr_visible(struct kobject *kobj, 330 struct attribute *attr, int n) 331 { 332 if (n < alienfx->num_zones + 1) 333 return attr->mode; 334 335 return 0; 336 } 337 338 static bool zone_group_visible(struct kobject *kobj) 339 { 340 return alienfx->num_zones > 0; 341 } 342 DEFINE_SYSFS_GROUP_VISIBLE(zone); 343 344 static struct attribute *zone_attrs[] = { 345 &dev_attr_lighting_control_state.attr, 346 &dev_attr_zone00.attr, 347 &dev_attr_zone01.attr, 348 &dev_attr_zone02.attr, 349 &dev_attr_zone03.attr, 350 NULL 351 }; 352 353 static struct attribute_group zone_attribute_group = { 354 .name = "rgb_zones", 355 .is_visible = SYSFS_GROUP_VISIBLE(zone), 356 .attrs = zone_attrs, 357 }; 358 359 /* 360 * LED Brightness (Global) 361 */ 362 static void global_led_set(struct led_classdev *led_cdev, 363 enum led_brightness brightness) 364 { 365 struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv, 366 global_led); 367 struct alienfx_platdata *pdata = dev_get_platdata(&priv->pdev->dev); 368 int ret; 369 370 priv->global_brightness = brightness; 371 372 ret = pdata->ops.upd_brightness(priv, pdata->wdev, brightness); 373 if (ret) 374 pr_err("LED brightness update failed\n"); 375 } 376 377 static enum led_brightness global_led_get(struct led_classdev *led_cdev) 378 { 379 struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv, 380 global_led); 381 382 return priv->global_brightness; 383 } 384 385 /* 386 * Platform Driver 387 */ 388 static int alienfx_probe(struct platform_device *pdev) 389 { 390 struct alienfx_priv *priv; 391 392 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 393 if (!priv) 394 return -ENOMEM; 395 396 if (alienware_interface == WMAX) 397 priv->lighting_control_state = WMAX_RUNNING; 398 else 399 priv->lighting_control_state = LEGACY_RUNNING; 400 401 priv->pdev = pdev; 402 priv->global_led.name = "alienware::global_brightness"; 403 priv->global_led.brightness_set = global_led_set; 404 priv->global_led.brightness_get = global_led_get; 405 priv->global_led.max_brightness = 0x0F; 406 priv->global_brightness = priv->global_led.max_brightness; 407 platform_set_drvdata(pdev, priv); 408 409 return devm_led_classdev_register(&pdev->dev, &priv->global_led); 410 } 411 412 static const struct attribute_group *alienfx_groups[] = { 413 &zone_attribute_group, 414 WMAX_DEV_GROUPS 415 NULL 416 }; 417 418 static struct platform_driver platform_driver = { 419 .driver = { 420 .name = "alienware-wmi", 421 .dev_groups = alienfx_groups, 422 }, 423 .probe = alienfx_probe, 424 }; 425 426 static void alienware_alienfx_remove(void *data) 427 { 428 struct platform_device *pdev = data; 429 430 platform_device_unregister(pdev); 431 } 432 433 int alienware_alienfx_setup(struct alienfx_platdata *pdata) 434 { 435 struct device *dev = &pdata->wdev->dev; 436 struct platform_device *pdev; 437 int ret; 438 439 pdev = platform_device_register_data(NULL, "alienware-wmi", 440 PLATFORM_DEVID_NONE, pdata, 441 sizeof(*pdata)); 442 if (IS_ERR(pdev)) 443 return PTR_ERR(pdev); 444 445 dev_set_drvdata(dev, pdev); 446 ret = devm_add_action_or_reset(dev, alienware_alienfx_remove, pdev); 447 if (ret) 448 return ret; 449 450 return 0; 451 } 452 453 static int __init alienware_wmi_init(void) 454 { 455 int ret; 456 457 dmi_check_system(alienware_quirks); 458 if (!alienfx) 459 alienfx = &quirk_unknown; 460 461 ret = platform_driver_register(&platform_driver); 462 if (ret < 0) 463 return ret; 464 465 if (wmi_has_guid(WMAX_CONTROL_GUID)) { 466 alienware_interface = WMAX; 467 ret = alienware_wmax_wmi_init(); 468 } else { 469 alienware_interface = LEGACY; 470 ret = alienware_legacy_wmi_init(); 471 } 472 473 if (ret < 0) 474 platform_driver_unregister(&platform_driver); 475 476 return ret; 477 } 478 479 module_init(alienware_wmi_init); 480 481 static void __exit alienware_wmi_exit(void) 482 { 483 if (alienware_interface == WMAX) 484 alienware_wmax_wmi_exit(); 485 else 486 alienware_legacy_wmi_exit(); 487 488 platform_driver_unregister(&platform_driver); 489 } 490 491 module_exit(alienware_wmi_exit); 492