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);