// SPDX-License-Identifier: GPL-2.0-or-later /* * Alienware special feature control * * Copyright (C) 2014 Dell Inc * Copyright (C) 2025 Kurt Borja */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include "alienware-wmi.h" MODULE_AUTHOR("Mario Limonciello "); MODULE_AUTHOR("Kurt Borja "); MODULE_DESCRIPTION("Alienware special feature control"); MODULE_LICENSE("GPL"); struct alienfx_quirks *alienfx; static struct alienfx_quirks quirk_inspiron5675 = { .num_zones = 2, .hdmi_mux = false, .amplifier = false, .deepslp = false, }; static struct alienfx_quirks quirk_unknown = { .num_zones = 2, .hdmi_mux = false, .amplifier = false, .deepslp = false, }; static struct alienfx_quirks quirk_x51_r1_r2 = { .num_zones = 3, .hdmi_mux = false, .amplifier = false, .deepslp = false, }; static struct alienfx_quirks quirk_x51_r3 = { .num_zones = 4, .hdmi_mux = false, .amplifier = true, .deepslp = false, }; static struct alienfx_quirks quirk_asm100 = { .num_zones = 2, .hdmi_mux = true, .amplifier = false, .deepslp = false, }; static struct alienfx_quirks quirk_asm200 = { .num_zones = 2, .hdmi_mux = true, .amplifier = false, .deepslp = true, }; static struct alienfx_quirks quirk_asm201 = { .num_zones = 2, .hdmi_mux = true, .amplifier = true, .deepslp = true, }; static int __init dmi_matched(const struct dmi_system_id *dmi) { alienfx = dmi->driver_data; return 1; } static const struct dmi_system_id alienware_quirks[] __initconst = { { .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 ASM201", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), }, .driver_data = &quirk_asm201, }, { .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 X51 R2", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), }, .driver_data = &quirk_x51_r1_r2, }, { .callback = dmi_matched, .ident = "Alienware X51 R3", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), }, .driver_data = &quirk_x51_r3, }, { .callback = dmi_matched, .ident = "Dell Inc. Inspiron 5675", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), }, .driver_data = &quirk_inspiron5675, }, {} }; u8 alienware_interface; int alienware_wmi_command(struct wmi_device *wdev, u32 method_id, void *in_args, size_t in_size, u32 *out_data) { struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; struct acpi_buffer in = {in_size, in_args}; acpi_status ret; ret = wmidev_evaluate_method(wdev, 0, method_id, &in, out_data ? &out : NULL); if (ACPI_FAILURE(ret)) return -EIO; union acpi_object *obj __free(kfree) = out.pointer; if (out_data) { if (obj && obj->type == ACPI_TYPE_INTEGER) *out_data = (u32)obj->integer.value; else return -ENOMSG; } return 0; } /* * Helpers used for zone control */ static int parse_rgb(const char *buf, struct color_platform *colors) { long unsigned int rgb; int ret; union color_union { struct color_platform cp; int package; } repackager; ret = kstrtoul(buf, 16, &rgb); if (ret) return ret; /* RGB triplet notation is 24-bit hexadecimal */ if (rgb > 0xFFFFFF) return -EINVAL; repackager.package = rgb & 0x0f0f0f0f; pr_debug("alienware-wmi: r: %d g:%d b: %d\n", repackager.cp.red, repackager.cp.green, repackager.cp.blue); *colors = repackager.cp; return 0; } /* * Individual RGB zone control */ static ssize_t zone_show(struct device *dev, struct device_attribute *attr, char *buf, u8 location) { struct alienfx_priv *priv = dev_get_drvdata(dev); struct color_platform *colors = &priv->colors[location]; return sprintf(buf, "red: %d, green: %d, blue: %d\n", colors->red, colors->green, colors->blue); } static ssize_t zone_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count, u8 location) { struct alienfx_priv *priv = dev_get_drvdata(dev); struct color_platform *colors = &priv->colors[location]; struct alienfx_platdata *pdata = dev_get_platdata(dev); int ret; ret = parse_rgb(buf, colors); if (ret) return ret; ret = pdata->ops.upd_led(priv, pdata->wdev, location); return ret ? ret : count; } static ssize_t zone00_show(struct device *dev, struct device_attribute *attr, char *buf) { return zone_show(dev, attr, buf, 0); } static ssize_t zone00_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return zone_store(dev, attr, buf, count, 0); } static DEVICE_ATTR_RW(zone00); static ssize_t zone01_show(struct device *dev, struct device_attribute *attr, char *buf) { return zone_show(dev, attr, buf, 1); } static ssize_t zone01_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return zone_store(dev, attr, buf, count, 1); } static DEVICE_ATTR_RW(zone01); static ssize_t zone02_show(struct device *dev, struct device_attribute *attr, char *buf) { return zone_show(dev, attr, buf, 2); } static ssize_t zone02_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return zone_store(dev, attr, buf, count, 2); } static DEVICE_ATTR_RW(zone02); static ssize_t zone03_show(struct device *dev, struct device_attribute *attr, char *buf) { return zone_show(dev, attr, buf, 3); } static ssize_t zone03_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return zone_store(dev, attr, buf, count, 3); } static DEVICE_ATTR_RW(zone03); /* * Lighting control state device attribute (Global) */ static ssize_t lighting_control_state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct alienfx_priv *priv = dev_get_drvdata(dev); if (priv->lighting_control_state == LEGACY_BOOTING) return sysfs_emit(buf, "[booting] running suspend\n"); else if (priv->lighting_control_state == LEGACY_SUSPEND) return sysfs_emit(buf, "booting running [suspend]\n"); return sysfs_emit(buf, "booting [running] suspend\n"); } static ssize_t lighting_control_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct alienfx_priv *priv = dev_get_drvdata(dev); u8 val; if (strcmp(buf, "booting\n") == 0) val = LEGACY_BOOTING; else if (strcmp(buf, "suspend\n") == 0) val = LEGACY_SUSPEND; else if (alienware_interface == LEGACY) val = LEGACY_RUNNING; else val = WMAX_RUNNING; priv->lighting_control_state = val; pr_debug("alienware-wmi: updated control state to %d\n", priv->lighting_control_state); return count; } static DEVICE_ATTR_RW(lighting_control_state); static umode_t zone_attr_visible(struct kobject *kobj, struct attribute *attr, int n) { if (n < alienfx->num_zones + 1) return attr->mode; return 0; } static bool zone_group_visible(struct kobject *kobj) { return alienfx->num_zones > 0; } DEFINE_SYSFS_GROUP_VISIBLE(zone); static struct attribute *zone_attrs[] = { &dev_attr_lighting_control_state.attr, &dev_attr_zone00.attr, &dev_attr_zone01.attr, &dev_attr_zone02.attr, &dev_attr_zone03.attr, NULL }; static struct attribute_group zone_attribute_group = { .name = "rgb_zones", .is_visible = SYSFS_GROUP_VISIBLE(zone), .attrs = zone_attrs, }; /* * LED Brightness (Global) */ static void global_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv, global_led); struct alienfx_platdata *pdata = dev_get_platdata(&priv->pdev->dev); int ret; priv->global_brightness = brightness; ret = pdata->ops.upd_brightness(priv, pdata->wdev, brightness); if (ret) pr_err("LED brightness update failed\n"); } static enum led_brightness global_led_get(struct led_classdev *led_cdev) { struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv, global_led); return priv->global_brightness; } /* * Platform Driver */ static int alienfx_probe(struct platform_device *pdev) { struct alienfx_priv *priv; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; if (alienware_interface == WMAX) priv->lighting_control_state = WMAX_RUNNING; else priv->lighting_control_state = LEGACY_RUNNING; priv->pdev = pdev; priv->global_led.name = "alienware::global_brightness"; priv->global_led.brightness_set = global_led_set; priv->global_led.brightness_get = global_led_get; priv->global_led.max_brightness = 0x0F; priv->global_brightness = priv->global_led.max_brightness; platform_set_drvdata(pdev, priv); return devm_led_classdev_register(&pdev->dev, &priv->global_led); } static const struct attribute_group *alienfx_groups[] = { &zone_attribute_group, WMAX_DEV_GROUPS NULL }; static struct platform_driver platform_driver = { .driver = { .name = "alienware-wmi", .dev_groups = alienfx_groups, }, .probe = alienfx_probe, }; static void alienware_alienfx_remove(void *data) { struct platform_device *pdev = data; platform_device_unregister(pdev); } int alienware_alienfx_setup(struct alienfx_platdata *pdata) { struct device *dev = &pdata->wdev->dev; struct platform_device *pdev; int ret; pdev = platform_device_register_data(NULL, "alienware-wmi", PLATFORM_DEVID_NONE, pdata, sizeof(*pdata)); if (IS_ERR(pdev)) return PTR_ERR(pdev); dev_set_drvdata(dev, pdev); ret = devm_add_action_or_reset(dev, alienware_alienfx_remove, pdev); if (ret) return ret; return 0; } static int __init alienware_wmi_init(void) { int ret; dmi_check_system(alienware_quirks); if (!alienfx) alienfx = &quirk_unknown; ret = platform_driver_register(&platform_driver); if (ret < 0) return ret; if (wmi_has_guid(WMAX_CONTROL_GUID)) { alienware_interface = WMAX; ret = alienware_wmax_wmi_init(); } else { alienware_interface = LEGACY; ret = alienware_legacy_wmi_init(); } if (ret < 0) platform_driver_unregister(&platform_driver); return ret; } module_init(alienware_wmi_init); static void __exit alienware_wmi_exit(void) { if (alienware_interface == WMAX) alienware_wmax_wmi_exit(); else alienware_legacy_wmi_exit(); platform_driver_unregister(&platform_driver); } module_exit(alienware_wmi_exit);