Split alienware-wmi WMI drivers into different files. This is done seamlessly by copying and pasting. Signed-off-by: Kurt Borja <kuurtb@xxxxxxxxx> --- drivers/platform/x86/dell/Makefile | 2 + .../platform/x86/dell/alienware-wmi-base.c | 596 ------------------ .../platform/x86/dell/alienware-wmi-legacy.c | 89 +++ .../platform/x86/dell/alienware-wmi-wmax.c | 526 ++++++++++++++++ 4 files changed, 617 insertions(+), 596 deletions(-) create mode 100644 drivers/platform/x86/dell/alienware-wmi-legacy.c create mode 100644 drivers/platform/x86/dell/alienware-wmi-wmax.c diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index f8aec8502c2f..03ba459f3d31 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -6,6 +6,8 @@ obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o alienware-wmi-objs := alienware-wmi-base.o +alienware-wmi-y += alienware-wmi-legacy.o +alienware-wmi-y += alienware-wmi-wmax.o obj-$(CONFIG_DCDBAS) += dcdbas.o obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o diff --git a/drivers/platform/x86/dell/alienware-wmi-base.c b/drivers/platform/x86/dell/alienware-wmi-base.c index e8d470bbb608..450ba0a48004 100644 --- a/drivers/platform/x86/dell/alienware-wmi-base.c +++ b/drivers/platform/x86/dell/alienware-wmi-base.c @@ -8,80 +8,21 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> -#include <linux/bitfield.h> -#include <linux/bits.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/platform_profile.h> #include <linux/dmi.h> #include <linux/leds.h> -#include <linux/wmi.h> #include "alienware-wmi.h" MODULE_AUTHOR("Mario Limonciello <mario.limonciello@xxxxxxxxxxx>"); MODULE_DESCRIPTION("Alienware special feature control"); MODULE_LICENSE("GPL"); -static bool force_platform_profile; -module_param_unsafe(force_platform_profile, bool, 0); -MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available"); - -static bool force_gmode; -module_param_unsafe(force_gmode, bool, 0); -MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected"); - enum INTERFACE_FLAGS { LEGACY, WMAX, }; -enum WMAX_THERMAL_INFORMATION_OPERATIONS { - WMAX_OPERATION_SYS_DESCRIPTION = 0x02, - WMAX_OPERATION_LIST_IDS = 0x03, - WMAX_OPERATION_CURRENT_PROFILE = 0x0B, -}; - -enum WMAX_THERMAL_CONTROL_OPERATIONS { - WMAX_OPERATION_ACTIVATE_PROFILE = 0x01, -}; - -enum WMAX_GAME_SHIFT_STATUS_OPERATIONS { - WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01, - WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02, -}; - -enum WMAX_THERMAL_TABLES { - WMAX_THERMAL_TABLE_BASIC = 0x90, - WMAX_THERMAL_TABLE_USTT = 0xA0, -}; - -enum wmax_thermal_mode { - THERMAL_MODE_USTT_BALANCED, - THERMAL_MODE_USTT_BALANCED_PERFORMANCE, - THERMAL_MODE_USTT_COOL, - THERMAL_MODE_USTT_QUIET, - THERMAL_MODE_USTT_PERFORMANCE, - THERMAL_MODE_USTT_LOW_POWER, - THERMAL_MODE_BASIC_QUIET, - THERMAL_MODE_BASIC_BALANCED, - THERMAL_MODE_BASIC_BALANCED_PERFORMANCE, - THERMAL_MODE_BASIC_PERFORMANCE, - THERMAL_MODE_LAST, -}; - -static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = { - [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, - [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, - [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL, - [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, - [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, - [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, - [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET, - [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED, - [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, - [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, -}; - struct quirk_entry { u8 num_zones; u8 hdmi_mux; @@ -213,120 +154,10 @@ static const struct dmi_system_id alienware_quirks[] __initconst = { {} }; -struct awcc_features { - bool gmode; -}; - -static struct awcc_features g_series_features = { - .gmode = true, -}; - -static struct awcc_features x_series_features = { - .gmode = false, -}; - -static const struct dmi_system_id awcc_dmi_table[] __initconst = { - { - .ident = "Alienware m17 R5", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), - }, - .driver_data = &x_series_features, - }, - { - .ident = "Alienware m18 R2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"), - }, - .driver_data = &x_series_features, - }, - { - .ident = "Alienware x15 R1", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"), - }, - .driver_data = &x_series_features, - }, - { - .ident = "Alienware x17 R2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"), - }, - .driver_data = &x_series_features, - }, - { - .ident = "Dell Inc. G15 5510", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"), - }, - .driver_data = &g_series_features, - }, - { - .ident = "Dell Inc. G15 5511", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"), - }, - .driver_data = &g_series_features, - }, - { - .ident = "Dell Inc. G15 5515", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"), - }, - .driver_data = &g_series_features, - }, - { - .ident = "Dell Inc. G3 3500", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"), - }, - .driver_data = &g_series_features, - }, - { - .ident = "Dell Inc. G3 3590", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"), - }, - .driver_data = &g_series_features, - }, - { - .ident = "Dell Inc. G5 5500", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"), - }, - .driver_data = &g_series_features, - }, -}; - -struct awcc_features *awcc; - struct wmax_basic_args { u8 arg; }; -struct wmax_u32_args { - u8 operation; - u8 arg1; - u8 arg2; - u8 arg3; -}; - -struct awcc_priv { - struct wmi_device *wdev; - struct platform_profile_handler pp_handler; - enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST]; -}; - static u8 interface; acpi_status alienware_wmi_command(struct wmi_device *wdev, u32 method_id, @@ -775,250 +606,6 @@ static const struct attribute_group deepsleep_attribute_group = { .attrs = deepsleep_attrs, }; -/* - * Thermal Profile control - * - Provides thermal profile control through the Platform Profile API - */ -#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4) -#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0) -#define WMAX_SENSOR_ID_MASK BIT(8) - -static bool is_wmax_thermal_code(u32 code) -{ - if (code & WMAX_SENSOR_ID_MASK) - return false; - - if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST) - return false; - - if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC && - (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET) - return true; - - if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT && - (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER) - return true; - - return false; -} - -static int wmax_thermal_information(struct wmi_device *wdev, u8 operation, - u8 arg, u32 *out_data) -{ - struct wmax_u32_args in_args = { - .operation = operation, - .arg1 = arg, - .arg2 = 0, - .arg3 = 0, - }; - acpi_status status; - - status = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION, - &in_args, sizeof(in_args), out_data); - - if (ACPI_FAILURE(status)) - return -EIO; - - if (*out_data == WMAX_FAILURE_CODE) - return -EBADRQC; - - return 0; -} - -static int wmax_thermal_control(struct wmi_device *wdev, u8 profile) -{ - struct wmax_u32_args in_args = { - .operation = WMAX_OPERATION_ACTIVATE_PROFILE, - .arg1 = profile, - .arg2 = 0, - .arg3 = 0, - }; - acpi_status status; - u32 out_data; - - status = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL, - &in_args, sizeof(in_args), &out_data); - - if (ACPI_FAILURE(status)) - return -EIO; - - if (out_data == WMAX_FAILURE_CODE) - return -EBADRQC; - - return 0; -} - -static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation, - u32 *out_data) -{ - struct wmax_u32_args in_args = { - .operation = operation, - .arg1 = 0, - .arg2 = 0, - .arg3 = 0, - }; - acpi_status status; - - status = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS, - &in_args, sizeof(in_args), out_data); - - if (ACPI_FAILURE(status)) - return -EIO; - - if (*out_data == WMAX_FAILURE_CODE) - return -EOPNOTSUPP; - - return 0; -} - -static int thermal_profile_get(struct platform_profile_handler *pprof, - enum platform_profile_option *profile) -{ - struct awcc_priv *priv; - u32 out_data; - int ret; - - priv = container_of(pprof, struct awcc_priv, pp_handler); - - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE, - 0, &out_data); - - if (ret < 0) - return ret; - - if (out_data == WMAX_THERMAL_MODE_GMODE) { - *profile = PLATFORM_PROFILE_PERFORMANCE; - return 0; - } - - if (!is_wmax_thermal_code(out_data)) - return -ENODATA; - - out_data &= WMAX_THERMAL_MODE_MASK; - *profile = wmax_mode_to_platform_profile[out_data]; - - return 0; -} - -static int thermal_profile_set(struct platform_profile_handler *pprof, - enum platform_profile_option profile) -{ - struct awcc_priv *priv; - - priv = container_of(pprof, struct awcc_priv, pp_handler); - - if (awcc->gmode) { - u32 gmode_status; - int ret; - - ret = wmax_game_shift_status(priv->wdev, - WMAX_OPERATION_GET_GAME_SHIFT_STATUS, - &gmode_status); - - if (ret < 0) - return ret; - - if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) || - (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) { - ret = wmax_game_shift_status(priv->wdev, - WMAX_OPERATION_TOGGLE_GAME_SHIFT, - &gmode_status); - - if (ret < 0) - return ret; - } - } - - return wmax_thermal_control(priv->wdev, - priv->supported_thermal_profiles[profile]); -} - -static int create_thermal_profile(struct wmi_device *wdev) -{ - enum platform_profile_option profile; - enum wmax_thermal_mode mode; - struct awcc_priv *priv; - u8 sys_desc[4]; - u32 first_mode; - u32 out_data; - int ret; - - priv = dev_get_drvdata(&wdev->dev); - - ret = wmax_thermal_information(wdev, WMAX_OPERATION_SYS_DESCRIPTION, - 0, (u32 *) &sys_desc); - if (ret < 0) - return ret; - - first_mode = sys_desc[0] + sys_desc[1]; - - for (u32 i = 0; i < sys_desc[3]; i++) { - ret = wmax_thermal_information(wdev, WMAX_OPERATION_LIST_IDS, - i + first_mode, &out_data); - - if (ret == -EIO) - return ret; - - if (ret == -EBADRQC) - break; - - if (!is_wmax_thermal_code(out_data)) - continue; - - mode = out_data & WMAX_THERMAL_MODE_MASK; - profile = wmax_mode_to_platform_profile[mode]; - priv->supported_thermal_profiles[profile] = out_data; - - set_bit(profile, priv->pp_handler.choices); - } - - if (bitmap_empty(priv->pp_handler.choices, PLATFORM_PROFILE_LAST)) - return -ENODEV; - - if (awcc->gmode) { - priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] = - WMAX_THERMAL_MODE_GMODE; - - set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pp_handler.choices); - } - - priv->pp_handler.profile_get = thermal_profile_get; - priv->pp_handler.profile_set = thermal_profile_set; - priv->pp_handler.name = "alienware-wmi"; - priv->pp_handler.dev = &wdev->dev; - - return platform_profile_register(&priv->pp_handler); -} - -static int alienware_awcc_setup(struct wmi_device *wdev) -{ - struct awcc_priv *priv; - int ret; - - priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - dev_set_drvdata(&wdev->dev, priv); - - priv->wdev = wdev; - - ret = create_thermal_profile(wdev); - if (ret < 0) - return ret; - - return 0; -} - -static void alienware_awcc_exit(struct wmi_device *wdev) -{ - struct awcc_priv *priv; - - priv = dev_get_drvdata(&wdev->dev); - - platform_profile_remove(&priv->pp_handler); -} - /* * Platform Driver */ @@ -1097,189 +684,6 @@ void alienware_alienfx_exit(struct wmi_device *wdev) platform_driver_unregister(&platform_driver); } -/* - * Legacy WMI driver - */ -static int legacy_wmi_update_led(struct alienfx_priv *priv, - struct wmi_device *wdev, u8 location) -{ - struct legacy_led_args legacy_args; - struct acpi_buffer input; - acpi_status status; - - legacy_args.colors = priv->colors[location]; - legacy_args.brightness = priv->global_brightness; - legacy_args.state = priv->lighting_control_state; - - input.length = sizeof(legacy_args); - input.pointer = &legacy_args; - - if (legacy_args.state == LEGACY_RUNNING) - status = alienware_wmi_command(wdev, location + 1, &legacy_args, - sizeof(legacy_args), NULL); - else - status = wmi_evaluate_method(LEGACY_POWER_CONTROL_GUID, 0, - location + 1, &input, NULL); - - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - -static int legacy_wmi_update_brightness(struct alienfx_priv *priv, - struct wmi_device *wdev, u8 brightness) -{ - return legacy_wmi_update_led(priv, wdev, 0); -} - -static int legacy_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct alienfx_platdata pdata = { - .wdev = wdev, - .ops = { - .upd_led = legacy_wmi_update_led, - .upd_brightness = legacy_wmi_update_brightness, - }, - }; - - return alienware_alienfx_setup(&pdata); -} - -static void legacy_wmi_remove(struct wmi_device *wdev) -{ - alienware_alienfx_exit(wdev); -} - -static struct wmi_device_id alienware_legacy_device_id_table[] = { - { LEGACY_CONTROL_GUID, NULL }, - { }, -}; -MODULE_DEVICE_TABLE(wmi, alienware_legacy_device_id_table); - -static struct wmi_driver alienware_legacy_wmi_driver = { - .driver = { - .name = "alienware-wmi-alienfx", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - .id_table = alienware_legacy_device_id_table, - .probe = legacy_wmi_probe, - .remove = legacy_wmi_remove, -}; - -int __init alienware_legacy_wmi_init(void) -{ - return wmi_driver_register(&alienware_legacy_wmi_driver); -} - -void __exit alienware_legacy_wmi_exit(void) -{ - wmi_driver_unregister(&alienware_legacy_wmi_driver); -} - -/* - * WMAX WMI driver - */ -static int wmax_wmi_update_led(struct alienfx_priv *priv, - struct wmi_device *wdev, u8 location) -{ - struct wmax_led_args in_args = { - .led_mask = 1 << location, - .colors = priv->colors[location], - .state = priv->lighting_control_state, - }; - acpi_status status; - - status = alienware_wmi_command(wdev, WMAX_METHOD_ZONE_CONTROL, - &in_args, sizeof(in_args), NULL); - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - -static int wmax_wmi_update_brightness(struct alienfx_priv *priv, - struct wmi_device *wdev, u8 brightness) -{ - struct wmax_brightness_args in_args = { - .led_mask = 0xFF, - .percentage = brightness, - }; - acpi_status status; - - status = alienware_wmi_command(wdev, WMAX_METHOD_BRIGHTNESS, &in_args, - sizeof(in_args), NULL); - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - -static int wmax_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct alienfx_platdata pdata = { - .wdev = wdev, - .ops = { - .upd_led = wmax_wmi_update_led, - .upd_brightness = wmax_wmi_update_brightness, - }, - }; - int ret = 0; - - if (awcc) - ret = alienware_awcc_setup(wdev); - else - ret = alienware_alienfx_setup(&pdata); - - return ret; -} - -static void wmax_wmi_remove(struct wmi_device *wdev) -{ - if (awcc) - alienware_awcc_exit(wdev); - else - alienware_alienfx_exit(wdev); -} - -static struct wmi_device_id alienware_wmax_device_id_table[] = { - { WMAX_CONTROL_GUID, NULL }, - { }, -}; -MODULE_DEVICE_TABLE(wmi, alienware_wmax_device_id_table); - -static struct wmi_driver alienware_wmax_wmi_driver = { - .driver = { - .name = "alienware-wmi-wmax", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - .id_table = alienware_wmax_device_id_table, - .probe = wmax_wmi_probe, - .remove = wmax_wmi_remove, -}; - -int __init alienware_wmax_wmi_init(void) -{ - const struct dmi_system_id *id; - - id = dmi_first_match(awcc_dmi_table); - if (id) - awcc = id->driver_data; - - if (force_platform_profile) - awcc = &x_series_features; - - if (force_gmode) - awcc = &g_series_features; - - return wmi_driver_register(&alienware_wmax_wmi_driver); -} - -void __exit alienware_wmax_wmi_exit(void) -{ - wmi_driver_unregister(&alienware_wmax_wmi_driver); -} - static int __init alienware_wmi_init(void) { int ret; diff --git a/drivers/platform/x86/dell/alienware-wmi-legacy.c b/drivers/platform/x86/dell/alienware-wmi-legacy.c new file mode 100644 index 000000000000..38dd2a602f34 --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi-legacy.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware LEGACY WMI device driver + * + * Copyright (C) 2024 Kurt Borja <kuurtb@xxxxxxxxx> + */ + +#include <linux/wmi.h> +#include "alienware-wmi.h" + +/* + * Legacy WMI driver + */ +static int legacy_wmi_update_led(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 location) +{ + struct legacy_led_args legacy_args; + struct acpi_buffer input; + acpi_status status; + + legacy_args.colors = priv->colors[location]; + legacy_args.brightness = priv->global_brightness; + legacy_args.state = priv->lighting_control_state; + + input.length = sizeof(legacy_args); + input.pointer = &legacy_args; + + if (legacy_args.state == LEGACY_RUNNING) + status = alienware_wmi_command(wdev, location + 1, &legacy_args, + sizeof(legacy_args), NULL); + else + status = wmi_evaluate_method(LEGACY_POWER_CONTROL_GUID, 0, + location + 1, &input, NULL); + + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +static int legacy_wmi_update_brightness(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 brightness) +{ + return legacy_wmi_update_led(priv, wdev, 0); +} + +static int legacy_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct alienfx_platdata pdata = { + .wdev = wdev, + .ops = { + .upd_led = legacy_wmi_update_led, + .upd_brightness = legacy_wmi_update_brightness, + }, + }; + + return alienware_alienfx_setup(&pdata); +} + +static void legacy_wmi_remove(struct wmi_device *wdev) +{ + alienware_alienfx_exit(wdev); +} + +static struct wmi_device_id alienware_legacy_device_id_table[] = { + { LEGACY_CONTROL_GUID, NULL }, + { }, +}; +MODULE_DEVICE_TABLE(wmi, alienware_legacy_device_id_table); + +static struct wmi_driver alienware_legacy_wmi_driver = { + .driver = { + .name = "alienware-wmi-alienfx", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = alienware_legacy_device_id_table, + .probe = legacy_wmi_probe, + .remove = legacy_wmi_remove, +}; + +int __init alienware_legacy_wmi_init(void) +{ + return wmi_driver_register(&alienware_legacy_wmi_driver); +} + +void __exit alienware_legacy_wmi_exit(void) +{ + wmi_driver_unregister(&alienware_legacy_wmi_driver); +} diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c new file mode 100644 index 000000000000..75b9a1f029b2 --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware WMAX WMI device driver + * + * Copyright (C) 2024 Kurt Borja <kuurtb@xxxxxxxxx> + */ + +#include <linux/dmi.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/moduleparam.h> +#include <linux/platform_profile.h> +#include <linux/wmi.h> +#include "alienware-wmi.h" + +#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4) +#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0) +#define WMAX_SENSOR_ID_MASK BIT(8) + +static bool force_platform_profile; +module_param_unsafe(force_platform_profile, bool, 0); +MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available"); + +static bool force_gmode; +module_param_unsafe(force_gmode, bool, 0); +MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected"); + +enum WMAX_THERMAL_INFORMATION_OPERATIONS { + WMAX_OPERATION_SYS_DESCRIPTION = 0x02, + WMAX_OPERATION_LIST_IDS = 0x03, + WMAX_OPERATION_CURRENT_PROFILE = 0x0B, +}; + +enum WMAX_THERMAL_CONTROL_OPERATIONS { + WMAX_OPERATION_ACTIVATE_PROFILE = 0x01, +}; + +enum WMAX_GAME_SHIFT_STATUS_OPERATIONS { + WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01, + WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02, +}; + +enum WMAX_THERMAL_TABLES { + WMAX_THERMAL_TABLE_BASIC = 0x90, + WMAX_THERMAL_TABLE_USTT = 0xA0, +}; + +enum wmax_thermal_mode { + THERMAL_MODE_USTT_BALANCED, + THERMAL_MODE_USTT_BALANCED_PERFORMANCE, + THERMAL_MODE_USTT_COOL, + THERMAL_MODE_USTT_QUIET, + THERMAL_MODE_USTT_PERFORMANCE, + THERMAL_MODE_USTT_LOW_POWER, + THERMAL_MODE_BASIC_QUIET, + THERMAL_MODE_BASIC_BALANCED, + THERMAL_MODE_BASIC_BALANCED_PERFORMANCE, + THERMAL_MODE_BASIC_PERFORMANCE, + THERMAL_MODE_LAST, +}; + +struct wmax_u32_args { + u8 operation; + u8 arg1; + u8 arg2; + u8 arg3; +}; + +struct awcc_priv { + struct wmi_device *wdev; + struct platform_profile_handler pp_handler; + enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST]; +}; + +struct awcc_features { + bool gmode; +}; + +static struct awcc_features g_series_features = { + .gmode = true, +}; + +static struct awcc_features x_series_features = { + .gmode = false, +}; + +static const struct dmi_system_id awcc_dmi_table[] __initconst = { + { + .ident = "Alienware m17 R5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), + }, + .driver_data = &x_series_features, + }, + { + .ident = "Alienware m18 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"), + }, + .driver_data = &x_series_features, + }, + { + .ident = "Alienware x15 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"), + }, + .driver_data = &x_series_features, + }, + { + .ident = "Alienware x17 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"), + }, + .driver_data = &x_series_features, + }, + { + .ident = "Dell Inc. G15 5510", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"), + }, + .driver_data = &g_series_features, + }, + { + .ident = "Dell Inc. G15 5511", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"), + }, + .driver_data = &g_series_features, + }, + { + .ident = "Dell Inc. G15 5515", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"), + }, + .driver_data = &g_series_features, + }, + { + .ident = "Dell Inc. G3 3500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"), + }, + .driver_data = &g_series_features, + }, + { + .ident = "Dell Inc. G3 3590", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"), + }, + .driver_data = &g_series_features, + }, + { + .ident = "Dell Inc. G5 5500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"), + }, + .driver_data = &g_series_features, + }, +}; + +static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = { + [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, + [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, + [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL, + [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, + [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, + [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, + [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET, + [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED, + [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, + [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, +}; + +struct awcc_features *awcc; + +/* + * Thermal Profile control + * - Provides thermal profile control through the Platform Profile API + */ +static bool is_wmax_thermal_code(u32 code) +{ + if (code & WMAX_SENSOR_ID_MASK) + return false; + + if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST) + return false; + + if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC && + (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET) + return true; + + if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT && + (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER) + return true; + + return false; +} + +static int wmax_thermal_information(struct wmi_device *wdev, u8 operation, + u8 arg, u32 *out_data) +{ + struct wmax_u32_args in_args = { + .operation = operation, + .arg1 = arg, + .arg2 = 0, + .arg3 = 0, + }; + acpi_status status; + + status = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION, + &in_args, sizeof(in_args), out_data); + + if (ACPI_FAILURE(status)) + return -EIO; + + if (*out_data == WMAX_FAILURE_CODE) + return -EBADRQC; + + return 0; +} + +static int wmax_thermal_control(struct wmi_device *wdev, u8 profile) +{ + struct wmax_u32_args in_args = { + .operation = WMAX_OPERATION_ACTIVATE_PROFILE, + .arg1 = profile, + .arg2 = 0, + .arg3 = 0, + }; + acpi_status status; + u32 out_data; + + status = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL, + &in_args, sizeof(in_args), &out_data); + + if (ACPI_FAILURE(status)) + return -EIO; + + if (out_data == WMAX_FAILURE_CODE) + return -EBADRQC; + + return 0; +} + +static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation, + u32 *out_data) +{ + struct wmax_u32_args in_args = { + .operation = operation, + .arg1 = 0, + .arg2 = 0, + .arg3 = 0, + }; + acpi_status status; + + status = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS, + &in_args, sizeof(in_args), out_data); + + if (ACPI_FAILURE(status)) + return -EIO; + + if (*out_data == WMAX_FAILURE_CODE) + return -EOPNOTSUPP; + + return 0; +} + +static int thermal_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + struct awcc_priv *priv; + u32 out_data; + int ret; + + priv = container_of(pprof, struct awcc_priv, pp_handler); + + ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE, + 0, &out_data); + + if (ret < 0) + return ret; + + if (out_data == WMAX_THERMAL_MODE_GMODE) { + *profile = PLATFORM_PROFILE_PERFORMANCE; + return 0; + } + + if (!is_wmax_thermal_code(out_data)) + return -ENODATA; + + out_data &= WMAX_THERMAL_MODE_MASK; + *profile = wmax_mode_to_platform_profile[out_data]; + + return 0; +} + +static int thermal_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + struct awcc_priv *priv; + + priv = container_of(pprof, struct awcc_priv, pp_handler); + + if (awcc->gmode) { + u32 gmode_status; + int ret; + + ret = wmax_game_shift_status(priv->wdev, + WMAX_OPERATION_GET_GAME_SHIFT_STATUS, + &gmode_status); + + if (ret < 0) + return ret; + + if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) || + (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) { + ret = wmax_game_shift_status(priv->wdev, + WMAX_OPERATION_TOGGLE_GAME_SHIFT, + &gmode_status); + + if (ret < 0) + return ret; + } + } + + return wmax_thermal_control(priv->wdev, + priv->supported_thermal_profiles[profile]); +} + +static int create_thermal_profile(struct wmi_device *wdev) +{ + enum platform_profile_option profile; + enum wmax_thermal_mode mode; + struct awcc_priv *priv; + u8 sys_desc[4]; + u32 first_mode; + u32 out_data; + int ret; + + priv = dev_get_drvdata(&wdev->dev); + + ret = wmax_thermal_information(wdev, WMAX_OPERATION_SYS_DESCRIPTION, + 0, (u32 *) &sys_desc); + if (ret < 0) + return ret; + + first_mode = sys_desc[0] + sys_desc[1]; + + for (u32 i = 0; i < sys_desc[3]; i++) { + ret = wmax_thermal_information(wdev, WMAX_OPERATION_LIST_IDS, + i + first_mode, &out_data); + + if (ret == -EIO) + return ret; + + if (ret == -EBADRQC) + break; + + if (!is_wmax_thermal_code(out_data)) + continue; + + mode = out_data & WMAX_THERMAL_MODE_MASK; + profile = wmax_mode_to_platform_profile[mode]; + priv->supported_thermal_profiles[profile] = out_data; + + set_bit(profile, priv->pp_handler.choices); + } + + if (bitmap_empty(priv->pp_handler.choices, PLATFORM_PROFILE_LAST)) + return -ENODEV; + + if (awcc->gmode) { + priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] = + WMAX_THERMAL_MODE_GMODE; + + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->pp_handler.choices); + } + + priv->pp_handler.profile_get = thermal_profile_get; + priv->pp_handler.profile_set = thermal_profile_set; + priv->pp_handler.name = "alienware-wmi"; + priv->pp_handler.dev = &wdev->dev; + + return platform_profile_register(&priv->pp_handler); +} + +static int alienware_awcc_setup(struct wmi_device *wdev) +{ + struct awcc_priv *priv; + int ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&wdev->dev, priv); + + priv->wdev = wdev; + + ret = create_thermal_profile(wdev); + if (ret < 0) + return ret; + + return 0; +} + +static void alienware_awcc_exit(struct wmi_device *wdev) +{ + struct awcc_priv *priv; + + priv = dev_get_drvdata(&wdev->dev); + + platform_profile_remove(&priv->pp_handler); +} + +/* + * WMAX WMI driver + */ +static int wmax_wmi_update_led(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 location) +{ + struct wmax_led_args in_args = { + .led_mask = 1 << location, + .colors = priv->colors[location], + .state = priv->lighting_control_state, + }; + acpi_status status; + + status = alienware_wmi_command(wdev, WMAX_METHOD_ZONE_CONTROL, + &in_args, sizeof(in_args), NULL); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +static int wmax_wmi_update_brightness(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 brightness) +{ + struct wmax_brightness_args in_args = { + .led_mask = 0xFF, + .percentage = brightness, + }; + acpi_status status; + + status = alienware_wmi_command(wdev, WMAX_METHOD_BRIGHTNESS, &in_args, + sizeof(in_args), NULL); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +static int wmax_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct alienfx_platdata pdata = { + .wdev = wdev, + .ops = { + .upd_led = wmax_wmi_update_led, + .upd_brightness = wmax_wmi_update_brightness, + }, + }; + int ret = 0; + + if (awcc) + ret = alienware_awcc_setup(wdev); + else + ret = alienware_alienfx_setup(&pdata); + + return ret; +} + +static void wmax_wmi_remove(struct wmi_device *wdev) +{ + if (awcc) + alienware_awcc_exit(wdev); + else + alienware_alienfx_exit(wdev); +} + +static struct wmi_device_id alienware_wmax_device_id_table[] = { + { WMAX_CONTROL_GUID, NULL }, + { }, +}; +MODULE_DEVICE_TABLE(wmi, alienware_wmax_device_id_table); + +static struct wmi_driver alienware_wmax_wmi_driver = { + .driver = { + .name = "alienware-wmi-wmax", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = alienware_wmax_device_id_table, + .probe = wmax_wmi_probe, + .remove = wmax_wmi_remove, +}; + +int __init alienware_wmax_wmi_init(void) +{ + const struct dmi_system_id *id; + + id = dmi_first_match(awcc_dmi_table); + if (id) + awcc = id->driver_data; + + if (force_platform_profile) + awcc = &x_series_features; + + if (force_gmode) + awcc = &g_series_features; + + return wmi_driver_register(&alienware_wmax_wmi_driver); +} + +void __exit alienware_wmax_wmi_exit(void) +{ + wmi_driver_unregister(&alienware_wmax_wmi_driver); +} -- 2.47.1