Hi +CC Guenter Roeck and Jean Delvare for hwmon parts 2021. augusztus 22., vasárnap 18:31 keltezéssel, Enver Balalic írta: > 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); I think you should use [devm_]hwmon_device_register_with_info() as it creates sysfs attributes for you, etc. You wouldn't need to manually create device attributes, and you wouldn't need fan{1,2}_show() at all. > + > + 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 Best regards, Barnabás Pőcze