This patch adds support for HP Omen laptops. It adds support for most things that can be controlled via the Windows Omen Command Center application. - Fan speed monitoring through hwmon - Platform Profile support (cool, balanced, performance) - Max fan speed function toggle This patch has been tested on a 2020 HP Omen 15 (AMD) 15-en0023dx. Signed-off-by: Enver Balalic <balalic.enver@xxxxxxxxx> --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/hp-wmi.c | 292 ++++++++++++++++++++++++++++++++-- 2 files changed, 282 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index d12db6c316ea..f0b3d94e182b 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -431,6 +431,7 @@ config HP_WMI tristate "HP WMI extras" depends on ACPI_WMI depends on INPUT + depends on HWMON depends on RFKILL || RFKILL = n select INPUT_SPARSEKMAP select ACPI_PLATFORM_PROFILE diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 027a1467d009..39d26602376d 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -22,6 +22,7 @@ #include <linux/input/sparse-keymap.h> #include <linux/platform_device.h> #include <linux/platform_profile.h> +#include <linux/hwmon.h> #include <linux/acpi.h> #include <linux/rfkill.h> #include <linux/string.h> @@ -39,6 +40,7 @@ MODULE_PARM_DESC(enable_tablet_mode_sw, "Enable SW_TABLET_MODE reporting (-1=aut #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" #define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" +#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 enum hp_wmi_radio { HPWMI_WIFI = 0x0, @@ -89,10 +91,18 @@ enum hp_wmi_commandtype { HPWMI_THERMAL_PROFILE_QUERY = 0x4c, }; +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, +}; + enum hp_wmi_command { HPWMI_READ = 0x01, HPWMI_WRITE = 0x02, HPWMI_ODM = 0x03, + HPWMI_GM = 0x20008, }; enum hp_wmi_hardware_mask { @@ -120,6 +130,13 @@ enum hp_wireless2_bits { HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, }; + +enum hp_thermal_profile_omen { + HP_OMEN_THERMAL_PROFILE_DEFAULT = 0x00, + HP_OMEN_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_OMEN_THERMAL_PROFILE_COOL = 0x02, +}; + enum hp_thermal_profile { HP_THERMAL_PROFILE_PERFORMANCE = 0x00, HP_THERMAL_PROFILE_DEFAULT = 0x01, @@ -279,6 +296,27 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, return ret; } +static int hp_wmi_get_fan_speed(int fan, int *data) +{ + int fsh, fsl; + char fan_data[4] = { fan, 0, 0, 0 }; + + int ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM, + &fan_data, sizeof(fan_data), + sizeof(fan_data)); + + if (ret != 0) + return -EINVAL; + + fsh = fan_data[2]; + fsl = fan_data[3]; + + // sometimes one of these can be negative + *data = ((fsh > 0 ? fsh : 0) << 8) | (fsl > 0 ? fsl : 0); + + return 0; +} + static int hp_wmi_read_int(int query) { int val = 0, ret; @@ -302,6 +340,61 @@ static int hp_wmi_hw_state(int mask) return !!(state & mask); } +static int omen_thermal_profile_set(int mode) +{ + char buffer[2] = {0, mode}; + int ret; + + if (mode < 0 || mode > 2) + return -EINVAL; + + ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, + &buffer, sizeof(buffer), sizeof(buffer)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return mode; +} + +static int hp_wmi_fan_speed_max_set(int enabled) +{ + int ret; + + ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM, + &enabled, sizeof(enabled), sizeof(enabled)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return enabled; +} + +static int hp_wmi_fan_speed_max_get(void) +{ + int val = 0, ret; + + ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, + &val, sizeof(val), sizeof(val)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return val; +} + +static int omen_thermal_profile_get(void) +{ + u8 data; + + int ret = ec_read(HP_OMEN_EC_THERMAL_PROFILE_OFFSET, &data); + + if (ret) + return ret; + + return data; +} + static int __init hp_wmi_bios_2008_later(void) { int state = 0; @@ -473,6 +566,42 @@ static ssize_t postcode_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "0x%x\n", value); } +static ssize_t max_fan_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_fan_speed_max_get(); + + if (value < 0) + return value; + return sprintf(buf, "%d\n", value); +} + +static ssize_t fan1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int fan_speed; + + int ret = hp_wmi_get_fan_speed(0, &fan_speed); + + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", fan_speed); +} + +static ssize_t fan2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int fan_speed; + + int ret = hp_wmi_get_fan_speed(1, &fan_speed); + + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", fan_speed); +} + static ssize_t als_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -514,12 +643,33 @@ static ssize_t postcode_store(struct device *dev, struct device_attribute *attr, return count; } +static ssize_t max_fan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 tmp; + int ret; + + ret = kstrtou32(buf, 10, &tmp); + if (ret) + return ret; + + ret = hp_wmi_fan_speed_max_set(tmp); + + if (ret < 0) + return ret; + + return count; +} + static DEVICE_ATTR_RO(display); static DEVICE_ATTR_RO(hddtemp); static DEVICE_ATTR_RW(als); static DEVICE_ATTR_RO(dock); static DEVICE_ATTR_RO(tablet); static DEVICE_ATTR_RW(postcode); +static DEVICE_ATTR_RW(max_fan); +static DEVICE_ATTR_RO(fan1); +static DEVICE_ATTR_RO(fan2); static struct attribute *hp_wmi_attrs[] = { &dev_attr_display.attr, @@ -528,6 +678,7 @@ static struct attribute *hp_wmi_attrs[] = { &dev_attr_dock.attr, &dev_attr_tablet.attr, &dev_attr_postcode.attr, + &dev_attr_max_fan.attr, NULL, }; ATTRIBUTE_GROUPS(hp_wmi); @@ -878,6 +1029,58 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device) return err; } +static int platform_profile_omen_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + int tp; + + tp = omen_thermal_profile_get(); + if (tp < 0) + return tp; + + switch (tp) { + case HP_OMEN_THERMAL_PROFILE_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case HP_OMEN_THERMAL_PROFILE_DEFAULT: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case HP_OMEN_THERMAL_PROFILE_COOL: + *profile = PLATFORM_PROFILE_COOL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int platform_profile_omen_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int err, tp; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp = HP_OMEN_THERMAL_PROFILE_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: + tp = HP_OMEN_THERMAL_PROFILE_DEFAULT; + break; + case PLATFORM_PROFILE_COOL: + tp = HP_OMEN_THERMAL_PROFILE_COOL; + break; + default: + return -EOPNOTSUPP; + } + + err = omen_thermal_profile_set(tp); + if (err < 0) + return err; + + return 0; +} + static int thermal_profile_get(void) { return hp_wmi_read_int(HPWMI_THERMAL_PROFILE_QUERY); @@ -946,19 +1149,34 @@ static int thermal_profile_setup(void) int err, tp; tp = thermal_profile_get(); - if (tp < 0) - return tp; + if (tp >= 0) { + /* + * call thermal profile write command to ensure that the firmware correctly + * sets the OEM variables for the DPTF + */ + err = thermal_profile_set(tp); + if (err) + return err; - /* - * call thermal profile write command to ensure that the firmware correctly - * sets the OEM variables for the DPTF - */ - err = thermal_profile_set(tp); - if (err) - return err; + platform_profile_handler.profile_get = platform_profile_get; + platform_profile_handler.profile_set = platform_profile_set; + } + + tp = omen_thermal_profile_get(); + if (tp >= 0) { + /* + * call thermal profile write command to ensure that the firmware correctly + * sets the OEM variables for the DPTF + */ + err = omen_thermal_profile_set(tp); + if (err < 0) + return err; - platform_profile_handler.profile_get = platform_profile_get, - platform_profile_handler.profile_set = platform_profile_set, + platform_profile_handler.profile_get = platform_profile_omen_get; + platform_profile_handler.profile_set = platform_profile_omen_set; + } else { + return tp; + } set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices); set_bit(PLATFORM_PROFILE_BALANCED, platform_profile_handler.choices); @@ -973,6 +1191,8 @@ static int thermal_profile_setup(void) return 0; } +static int hp_wmi_hwmon_init(void); + static int __init hp_wmi_bios_setup(struct platform_device *device) { /* clear detected rfkill devices */ @@ -984,6 +1204,8 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) if (hp_wmi_rfkill_setup(device)) hp_wmi_rfkill2_setup(device); + hp_wmi_hwmon_init(); + thermal_profile_setup(); return 0; @@ -1068,6 +1290,54 @@ static struct platform_driver hp_wmi_driver = { .remove = __exit_p(hp_wmi_bios_remove), }; +static struct attribute *hwmon_attributes[] = { + &dev_attr_fan1.attr, + &dev_attr_fan2.attr, + NULL +}; + +static umode_t hp_wmi_hwmon_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + int fan_speed; + + if (attr == &dev_attr_fan1.attr) { + int ret = hp_wmi_get_fan_speed(0, &fan_speed); + + if (ret < 0) + return 0; + } else if (attr == &dev_attr_fan2.attr) { + int ret = hp_wmi_get_fan_speed(1, &fan_speed); + + if (ret < 0) + return 0; + } + + return attr->mode; +} + +static const struct attribute_group hwmon_attribute_group = { + .is_visible = hp_wmi_hwmon_sysfs_is_visible, + .attrs = hwmon_attributes +}; +__ATTRIBUTE_GROUPS(hwmon_attribute); + +static int hp_wmi_hwmon_init(void) +{ + struct device *dev = &hp_wmi_platform_dev->dev; + struct device *hwmon; + + hwmon = devm_hwmon_device_register_with_groups(dev, "hp", &hp_wmi_driver, + hwmon_attribute_groups); + + if (IS_ERR(hwmon)) { + pr_err("Could not register hp hwmon device\n"); + return PTR_ERR(hwmon); + } + + return 0; +} + static int __init hp_wmi_init(void) { int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); -- 2.33.0