]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
platform/x86: hp-wmi: Add fan and thermal profile support for Victus 16-s1000
authorJulien ROBIN <julien.robin28@free.fr>
Thu, 16 Jan 2025 22:20:23 +0000 (23:20 +0100)
committerIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Mon, 20 Jan 2025 12:13:14 +0000 (14:13 +0200)
The following patch adds support for HP Victus 16-s1000 laptop series,
by adding and fixing the following functionalities, which can be
accessed through hwmon and platform_profile sysfs:

 - Functional measured fan speed reading
 - Ability to enable and disable maximum fan speed
 - Platform profiles full setting ability for CPU and GPU

It sets appropriates CPU and GPU power settings both on AC and battery
power sources, for low-power, balanced and performance modes.

It has been thoroughly tested on a 16-s1034nf laptop based on a 8C9C DMI
board name, and behavior of the driver on previous boards is left
untouched thanks to the separated lists of DMI board names.

Signed-off-by: Julien ROBIN <julien.robin28@free.fr>
Link: https://lore.kernel.org/r/1c00f906-8500-41d5-be80-f9092b6a49f1@free.fr
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
drivers/platform/x86/hp/hp-wmi.c

index 435e3ef2e3e7839a27062e31aeb3f3872dff7319..ee3a915dd6cefdcb09e702ce889715327c626fc6 100644 (file)
@@ -45,6 +45,10 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
 #define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63
 #define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
 
+#define HP_FAN_SPEED_AUTOMATIC  0x00
+#define HP_POWER_LIMIT_DEFAULT  0x00
+#define HP_POWER_LIMIT_NO_CHANGE 0xFF
+
 #define ACPI_AC_CLASS "ac_adapter"
 
 #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
@@ -83,11 +87,16 @@ static const char * const omen_timed_thermal_profile_boards[] = {
        "8BAD", "8A42"
 };
 
-/* DMI Board names of Victus laptops */
+/* DMI Board names of Victus 16-d1xxx laptops */
 static const char * const victus_thermal_profile_boards[] = {
        "8A25"
 };
 
+/* DMI Board names of Victus 16-s1000 laptops */
+static const char * const victus_s_thermal_profile_boards[] = {
+       "8C9C"
+};
+
 enum hp_wmi_radio {
        HPWMI_WIFI      = 0x0,
        HPWMI_BLUETOOTH = 0x1,
@@ -147,12 +156,32 @@ enum hp_wmi_commandtype {
        HPWMI_THERMAL_PROFILE_QUERY     = 0x4c,
 };
 
+struct victus_power_limits {
+       u8 pl1;
+       u8 pl2;
+       u8 pl4;
+       u8 cpu_gpu_concurrent_limit;
+};
+
+struct victus_gpu_power_modes {
+       u8 ctgp_enable;
+       u8 ppab_enable;
+       u8 dstate;
+       u8 gpu_slowdown_temp;
+};
+
 enum hp_wmi_gm_commandtype {
-       HPWMI_FAN_SPEED_GET_QUERY = 0x11,
-       HPWMI_SET_PERFORMANCE_MODE = 0x1A,
-       HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26,
-       HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27,
-       HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28,
+       HPWMI_FAN_SPEED_GET_QUERY               = 0x11,
+       HPWMI_SET_PERFORMANCE_MODE              = 0x1A,
+       HPWMI_FAN_SPEED_MAX_GET_QUERY           = 0x26,
+       HPWMI_FAN_SPEED_MAX_SET_QUERY           = 0x27,
+       HPWMI_GET_SYSTEM_DESIGN_DATA            = 0x28,
+       HPWMI_FAN_COUNT_GET_QUERY               = 0x10,
+       HPWMI_GET_GPU_THERMAL_MODES_QUERY       = 0x21,
+       HPWMI_SET_GPU_THERMAL_MODES_QUERY       = 0x22,
+       HPWMI_SET_POWER_LIMITS_QUERY            = 0x29,
+       HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY      = 0x2D,
+       HPWMI_FAN_SPEED_SET_QUERY               = 0x2E,
 };
 
 enum hp_wmi_command {
@@ -211,6 +240,11 @@ enum hp_thermal_profile_victus {
        HP_VICTUS_THERMAL_PROFILE_QUIET                 = 0x03,
 };
 
+enum hp_thermal_profile_victus_s {
+       HP_VICTUS_S_THERMAL_PROFILE_DEFAULT             = 0x00,
+       HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE         = 0x01,
+};
+
 enum hp_thermal_profile {
        HP_THERMAL_PROFILE_PERFORMANCE  = 0x00,
        HP_THERMAL_PROFILE_DEFAULT              = 0x01,
@@ -411,6 +445,26 @@ out_free:
        return ret;
 }
 
+/*
+ * Calling this hp_wmi_get_fan_count_userdefine_trigger function also enables
+ * and/or maintains the laptop in user defined thermal and fan states, instead
+ * of using a fallback state. After a 120 seconds timeout however, the laptop
+ * goes back to its fallback state.
+ */
+static int hp_wmi_get_fan_count_userdefine_trigger(void)
+{
+       u8 fan_data[4] = {};
+       int ret;
+
+       ret = hp_wmi_perform_query(HPWMI_FAN_COUNT_GET_QUERY, HPWMI_GM,
+                                  &fan_data, sizeof(u8),
+                                  sizeof(fan_data));
+       if (ret != 0)
+               return -EINVAL;
+
+       return fan_data[0]; /* Others bytes aren't providing fan count */
+}
+
 static int hp_wmi_get_fan_speed(int fan)
 {
        u8 fsh, fsl;
@@ -429,6 +483,23 @@ static int hp_wmi_get_fan_speed(int fan)
        return (fsh << 8) | fsl;
 }
 
+static int hp_wmi_get_fan_speed_victus_s(int fan)
+{
+       u8 fan_data[128] = {};
+       int ret;
+
+       if (fan < 0 || fan >= sizeof(fan_data))
+               return -EINVAL;
+
+       ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY,
+                                  HPWMI_GM, &fan_data, sizeof(u8),
+                                  sizeof(fan_data));
+       if (ret != 0)
+               return -EINVAL;
+
+       return fan_data[fan] * 100;
+}
+
 static int hp_wmi_read_int(int query)
 {
        int val = 0, ret;
@@ -557,6 +628,30 @@ static int hp_wmi_fan_speed_max_set(int enabled)
        return enabled;
 }
 
+static int hp_wmi_fan_speed_reset(void)
+{
+       u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC };
+       int ret;
+
+       ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM,
+                                  &fan_speed, sizeof(fan_speed), 0);
+
+       return ret;
+}
+
+static int hp_wmi_fan_speed_max_reset(void)
+{
+       int ret;
+
+       ret = hp_wmi_fan_speed_max_set(0);
+       if (ret)
+               return ret;
+
+       /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
+       ret = hp_wmi_fan_speed_reset();
+       return ret;
+}
+
 static int hp_wmi_fan_speed_max_get(void)
 {
        int val = 0, ret;
@@ -1472,6 +1567,161 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile)
        return 0;
 }
 
+static bool is_victus_s_thermal_profile(void)
+{
+       const char *board_name;
+
+       board_name = dmi_get_system_info(DMI_BOARD_NAME);
+       if (!board_name)
+               return false;
+
+       return match_string(victus_s_thermal_profile_boards,
+                           ARRAY_SIZE(victus_s_thermal_profile_boards),
+                           board_name) >= 0;
+}
+
+static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
+                                           bool *ppab_enable,
+                                           u8 *dstate,
+                                           u8 *gpu_slowdown_temp)
+{
+       struct victus_gpu_power_modes gpu_power_modes;
+       int ret;
+
+       ret = hp_wmi_perform_query(HPWMI_GET_GPU_THERMAL_MODES_QUERY, HPWMI_GM,
+                                  &gpu_power_modes, sizeof(gpu_power_modes),
+                                  sizeof(gpu_power_modes));
+       if (ret == 0) {
+               *ctgp_enable = gpu_power_modes.ctgp_enable ? true : false;
+               *ppab_enable = gpu_power_modes.ppab_enable ? true : false;
+               *dstate = gpu_power_modes.dstate;
+               *gpu_slowdown_temp = gpu_power_modes.gpu_slowdown_temp;
+       }
+
+       return ret;
+}
+
+static int victus_s_gpu_thermal_profile_set(bool ctgp_enable,
+                                           bool ppab_enable,
+                                           u8 dstate)
+{
+       struct victus_gpu_power_modes gpu_power_modes;
+       int ret;
+
+       bool current_ctgp_state, current_ppab_state;
+       u8 current_dstate, current_gpu_slowdown_temp;
+
+       /* Retrieving GPU slowdown temperature, in order to keep it unchanged */
+       ret = victus_s_gpu_thermal_profile_get(&current_ctgp_state,
+                                              &current_ppab_state,
+                                              &current_dstate,
+                                              &current_gpu_slowdown_temp);
+       if (ret < 0) {
+               pr_warn("GPU modes not updated, unable to get slowdown temp\n");
+               return ret;
+       }
+
+       gpu_power_modes.ctgp_enable = ctgp_enable ? 0x01 : 0x00;
+       gpu_power_modes.ppab_enable = ppab_enable ? 0x01 : 0x00;
+       gpu_power_modes.dstate = dstate;
+       gpu_power_modes.gpu_slowdown_temp = current_gpu_slowdown_temp;
+
+
+       ret = hp_wmi_perform_query(HPWMI_SET_GPU_THERMAL_MODES_QUERY, HPWMI_GM,
+                                  &gpu_power_modes, sizeof(gpu_power_modes), 0);
+
+       return ret;
+}
+
+/* Note: HP_POWER_LIMIT_DEFAULT can be used to restore default PL1 and PL2 */
+static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2)
+{
+       struct victus_power_limits power_limits;
+       int ret;
+
+       /* We need to know both PL1 and PL2 values in order to check them */
+       if (pl1 == HP_POWER_LIMIT_NO_CHANGE || pl2 == HP_POWER_LIMIT_NO_CHANGE)
+               return -EINVAL;
+
+       /* PL2 is not supposed to be lower than PL1 */
+       if (pl2 < pl1)
+               return -EINVAL;
+
+       power_limits.pl1 = pl1;
+       power_limits.pl2 = pl2;
+       power_limits.pl4 = HP_POWER_LIMIT_NO_CHANGE;
+       power_limits.cpu_gpu_concurrent_limit = HP_POWER_LIMIT_NO_CHANGE;
+
+       ret = hp_wmi_perform_query(HPWMI_SET_POWER_LIMITS_QUERY, HPWMI_GM,
+                                  &power_limits, sizeof(power_limits), 0);
+
+       return ret;
+}
+
+static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
+{
+       bool gpu_ctgp_enable, gpu_ppab_enable;
+       u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
+       int err, tp;
+
+       switch (profile) {
+       case PLATFORM_PROFILE_PERFORMANCE:
+               tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE;
+               gpu_ctgp_enable = true;
+               gpu_ppab_enable = true;
+               gpu_dstate = 1;
+               break;
+       case PLATFORM_PROFILE_BALANCED:
+               tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+               gpu_ctgp_enable = false;
+               gpu_ppab_enable = true;
+               gpu_dstate = 1;
+               break;
+       case PLATFORM_PROFILE_LOW_POWER:
+               tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+               gpu_ctgp_enable = false;
+               gpu_ppab_enable = false;
+               gpu_dstate = 1;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       hp_wmi_get_fan_count_userdefine_trigger();
+
+       err = omen_thermal_profile_set(tp);
+       if (err < 0) {
+               pr_err("Failed to set platform profile %d: %d\n", profile, err);
+               return err;
+       }
+
+       err = victus_s_gpu_thermal_profile_set(gpu_ctgp_enable,
+                                              gpu_ppab_enable,
+                                              gpu_dstate);
+       if (err < 0) {
+               pr_err("Failed to set GPU profile %d: %d\n", profile, err);
+               return err;
+       }
+
+       return 0;
+}
+
+static int platform_profile_victus_s_set(struct device *dev,
+                                        enum platform_profile_option profile)
+{
+       int err;
+
+       guard(mutex)(&active_platform_profile_lock);
+
+       err = platform_profile_victus_s_set_ec(profile);
+       if (err < 0)
+               return err;
+
+       active_platform_profile = profile;
+
+       return 0;
+}
+
 static int platform_profile_victus_set(struct device *dev,
                                       enum platform_profile_option profile)
 {
@@ -1494,6 +1744,9 @@ static int hp_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
                set_bit(PLATFORM_PROFILE_COOL, choices);
        } else if (is_victus_thermal_profile()) {
                set_bit(PLATFORM_PROFILE_QUIET, choices);
+       } else if (is_victus_s_thermal_profile()) {
+               /* Adding an equivalent to HP Omen software ECO mode: */
+               set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
        } else {
                set_bit(PLATFORM_PROFILE_QUIET, choices);
                set_bit(PLATFORM_PROFILE_COOL, choices);
@@ -1562,6 +1815,39 @@ static int omen_powersource_event(struct notifier_block *nb,
        return NOTIFY_OK;
 }
 
+static int victus_s_powersource_event(struct notifier_block *nb,
+                                     unsigned long value,
+                                     void *data)
+{
+       struct acpi_bus_event *event_entry = data;
+       int err;
+
+       if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0)
+               return NOTIFY_DONE;
+
+       pr_debug("Received power source device event\n");
+
+       /*
+        * Switching to battery power source while Performance mode is active
+        * needs manual triggering of CPU power limits. Same goes when switching
+        * to AC power source while Performance mode is active. Other modes
+        * however are automatically behaving without any manual action.
+        * Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions.
+        */
+
+       if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) {
+               pr_debug("Triggering CPU PL1/PL2 actualization\n");
+               err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT,
+                                              HP_POWER_LIMIT_DEFAULT);
+               if (err)
+                       pr_warn("Failed to actualize power limits: %d\n", err);
+
+               return NOTIFY_DONE;
+       }
+
+       return NOTIFY_OK;
+}
+
 static int omen_register_powersource_event_handler(void)
 {
        int err;
@@ -1577,11 +1863,30 @@ static int omen_register_powersource_event_handler(void)
        return 0;
 }
 
+static int victus_s_register_powersource_event_handler(void)
+{
+       int err;
+
+       platform_power_source_nb.notifier_call = victus_s_powersource_event;
+       err = register_acpi_notifier(&platform_power_source_nb);
+       if (err < 0) {
+               pr_warn("Failed to install ACPI power source notify handler\n");
+               return err;
+       }
+
+       return 0;
+}
+
 static inline void omen_unregister_powersource_event_handler(void)
 {
        unregister_acpi_notifier(&platform_power_source_nb);
 }
 
+static inline void victus_s_unregister_powersource_event_handler(void)
+{
+       unregister_acpi_notifier(&platform_power_source_nb);
+}
+
 static const struct platform_profile_ops platform_profile_omen_ops = {
        .probe = hp_wmi_platform_profile_probe,
        .profile_get = platform_profile_omen_get,
@@ -1594,6 +1899,12 @@ static const struct platform_profile_ops platform_profile_victus_ops = {
        .profile_set = platform_profile_victus_set,
 };
 
+static const struct platform_profile_ops platform_profile_victus_s_ops = {
+       .probe = hp_wmi_platform_profile_probe,
+       .profile_get = platform_profile_omen_get,
+       .profile_set = platform_profile_victus_s_set,
+};
+
 static const struct platform_profile_ops hp_wmi_platform_profile_ops = {
        .probe = hp_wmi_platform_profile_probe,
        .profile_get = hp_wmi_platform_profile_get,
@@ -1633,6 +1944,18 @@ static int thermal_profile_setup(struct platform_device *device)
                        return err;
 
                ops = &platform_profile_victus_ops;
+       } else if (is_victus_s_thermal_profile()) {
+               /*
+                * Being unable to retrieve laptop's current thermal profile,
+                * during this setup, we set it to Balanced by default.
+                */
+               active_platform_profile = PLATFORM_PROFILE_BALANCED;
+
+               err = platform_profile_victus_s_set_ec(active_platform_profile);
+               if (err < 0)
+                       return err;
+
+               ops = &platform_profile_victus_s_ops;
        } else {
                tp = thermal_profile_get();
 
@@ -1655,6 +1978,7 @@ static int thermal_profile_setup(struct platform_device *device)
        if (IS_ERR(platform_profile_device))
                return PTR_ERR(platform_profile_device);
 
+       pr_info("Registered as platform profile handler\n");
        platform_profile_support = true;
 
        return 0;
@@ -1780,8 +2104,13 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
        case hwmon_pwm:
                return 0644;
        case hwmon_fan:
-               if (hp_wmi_get_fan_speed(channel) >= 0)
-                       return 0444;
+               if (is_victus_s_thermal_profile()) {
+                       if (hp_wmi_get_fan_speed_victus_s(channel) >= 0)
+                               return 0444;
+               } else {
+                       if (hp_wmi_get_fan_speed(channel) >= 0)
+                               return 0444;
+               }
                break;
        default:
                return 0;
@@ -1797,8 +2126,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
 
        switch (type) {
        case hwmon_fan:
-               ret = hp_wmi_get_fan_speed(channel);
-
+               if (is_victus_s_thermal_profile())
+                       ret = hp_wmi_get_fan_speed_victus_s(channel);
+               else
+                       ret = hp_wmi_get_fan_speed(channel);
                if (ret < 0)
                        return ret;
                *val = ret;
@@ -1831,11 +2162,17 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
        case hwmon_pwm:
                switch (val) {
                case 0:
+                       if (is_victus_s_thermal_profile())
+                               hp_wmi_get_fan_count_userdefine_trigger();
                        /* 0 is no fan speed control (max), which is 1 for us */
                        return hp_wmi_fan_speed_max_set(1);
                case 2:
                        /* 2 is automatic speed control, which is 0 for us */
-                       return hp_wmi_fan_speed_max_set(0);
+                       if (is_victus_s_thermal_profile()) {
+                               hp_wmi_get_fan_count_userdefine_trigger();
+                               return hp_wmi_fan_speed_max_reset();
+                       } else
+                               return hp_wmi_fan_speed_max_set(0);
                default:
                        /* we don't support manual fan speed control */
                        return -EINVAL;
@@ -1914,6 +2251,10 @@ static int __init hp_wmi_init(void)
                err = omen_register_powersource_event_handler();
                if (err)
                        goto err_unregister_device;
+       } else if (is_victus_s_thermal_profile()) {
+               err = victus_s_register_powersource_event_handler();
+               if (err)
+                       goto err_unregister_device;
        }
 
        return 0;
@@ -1933,6 +2274,9 @@ static void __exit hp_wmi_exit(void)
        if (is_omen_thermal_profile() || is_victus_thermal_profile())
                omen_unregister_powersource_event_handler();
 
+       if (is_victus_s_thermal_profile())
+               victus_s_unregister_powersource_event_handler();
+
        if (wmi_has_guid(HPWMI_EVENT_GUID))
                hp_wmi_input_destroy();