platform/x86: hp-wmi: add support for omen laptops
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 Also exposes the existing HDD temperature through hwmon since this driver didn't use hwmon before this patch. This patch has been tested on a 2020 HP Omen 15 (AMD) 15-en0023dx. - V1 Initial Patch - V2 Use standard hwmon ABI attributes Add existing non-standard "hddtemp" to hwmon - V3 Fix overflow issue in "hp_wmi_get_fan_speed" Map max fan speed value back to hwmon values on read Code style fixes Fix issue with returning values from "hp_wmi_hwmon_read", the value to return should be written to val and not just returned from the function - V4 Use DMI Board names to detect if a device should use the omen specific thermal profile method. Select HWMON instead of depending on it. Code style fixes. Replace some error codes with more specific/meaningful ones. Remove the HDD temperature from HWMON since we don't know what unit it's expressed in. Handle error from hp_wmi_hwmon_init - V5 Handle possible NULL from dmi_get_system_info() Use match_string function instead of manually checking Directly use is_omen_thermal_profile() without the static variable. Signed-off-by: Enver Balalic <balalic.enver@gmail.com> Acked-by: Guenter Roeck <linux@roeck-us.net> Link: https://lore.kernel.org/r/20210902182234.vtwl72n5rjql22qa@omen.localdomain Reviewed-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
parent
3c3c8e88c8
commit
4c51ba9af4
|
@ -426,6 +426,7 @@ config HP_WMI
|
|||
depends on RFKILL || RFKILL = n
|
||||
select INPUT_SPARSEKMAP
|
||||
select ACPI_PLATFORM_PROFILE
|
||||
select HWMON
|
||||
help
|
||||
Say Y here if you want to support WMI-based hotkeys on HP laptops and
|
||||
to read data from WMI such as docking or ambient light sensor state.
|
||||
|
|
|
@ -22,9 +22,11 @@
|
|||
#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>
|
||||
#include <linux/dmi.h>
|
||||
|
||||
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
|
||||
MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
|
||||
|
@ -39,6 +41,25 @@ 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
|
||||
|
||||
/* DMI board names of devices that should use the omen specific path for
|
||||
* thermal profiles.
|
||||
* This was obtained by taking a look in the windows omen command center
|
||||
* app and parsing a json file that they use to figure out what capabilities
|
||||
* the device should have.
|
||||
* A device is considered an omen if the DisplayName in that list contains
|
||||
* "OMEN", and it can use the thermal profile stuff if the "Feature" array
|
||||
* contains "PerformanceControl".
|
||||
*/
|
||||
static const char * const omen_thermal_profile_boards[] = {
|
||||
"84DA", "84DB", "84DC", "8574", "8575", "860A", "87B5", "8572", "8573",
|
||||
"8600", "8601", "8602", "8605", "8606", "8607", "8746", "8747", "8749",
|
||||
"874A", "8603", "8604", "8748", "886B", "886C", "878A", "878B", "878C",
|
||||
"88C8", "88CB", "8786", "8787", "8788", "88D1", "88D2", "88F4", "88FD",
|
||||
"88F5", "88F6", "88F7", "88FE", "88FF", "8900", "8901", "8902", "8912",
|
||||
"8917", "8918", "8949", "894A", "89EB"
|
||||
};
|
||||
|
||||
enum hp_wmi_radio {
|
||||
HPWMI_WIFI = 0x0,
|
||||
|
@ -89,10 +110,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 +149,12 @@ 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 +314,24 @@ out_free:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int hp_wmi_get_fan_speed(int fan)
|
||||
{
|
||||
u8 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];
|
||||
|
||||
return (fsh << 8) | fsl;
|
||||
}
|
||||
|
||||
static int hp_wmi_read_int(int query)
|
||||
{
|
||||
int val = 0, ret;
|
||||
|
@ -302,6 +355,73 @@ 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 bool is_omen_thermal_profile(void)
|
||||
{
|
||||
const char *board_name = dmi_get_system_info(DMI_BOARD_NAME);
|
||||
|
||||
if (!board_name)
|
||||
return false;
|
||||
|
||||
return match_string(omen_thermal_profile_boards,
|
||||
ARRAY_SIZE(omen_thermal_profile_boards),
|
||||
board_name) >= 0;
|
||||
}
|
||||
|
||||
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 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 __init hp_wmi_bios_2008_later(void)
|
||||
{
|
||||
int state = 0;
|
||||
|
@ -878,6 +998,58 @@ fail:
|
|||
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);
|
||||
|
@ -945,20 +1117,39 @@ static int thermal_profile_setup(void)
|
|||
{
|
||||
int err, tp;
|
||||
|
||||
tp = thermal_profile_get();
|
||||
if (tp < 0)
|
||||
return tp;
|
||||
if (is_omen_thermal_profile()) {
|
||||
tp = omen_thermal_profile_get();
|
||||
if (tp < 0)
|
||||
return tp;
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
platform_profile_handler.profile_get = platform_profile_get,
|
||||
platform_profile_handler.profile_set = platform_profile_set,
|
||||
err = omen_thermal_profile_set(tp);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
platform_profile_handler.profile_get = platform_profile_omen_get;
|
||||
platform_profile_handler.profile_set = platform_profile_omen_set;
|
||||
} else {
|
||||
tp = thermal_profile_get();
|
||||
|
||||
if (tp < 0)
|
||||
return tp;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices);
|
||||
set_bit(PLATFORM_PROFILE_BALANCED, platform_profile_handler.choices);
|
||||
|
@ -973,8 +1164,11 @@ 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)
|
||||
{
|
||||
int err;
|
||||
/* clear detected rfkill devices */
|
||||
wifi_rfkill = NULL;
|
||||
bluetooth_rfkill = NULL;
|
||||
|
@ -984,6 +1178,11 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
|
|||
if (hp_wmi_rfkill_setup(device))
|
||||
hp_wmi_rfkill2_setup(device);
|
||||
|
||||
err = hp_wmi_hwmon_init();
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
thermal_profile_setup();
|
||||
|
||||
return 0;
|
||||
|
@ -1068,6 +1267,112 @@ static struct platform_driver hp_wmi_driver = {
|
|||
.remove = __exit_p(hp_wmi_bios_remove),
|
||||
};
|
||||
|
||||
static umode_t hp_wmi_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
return 0644;
|
||||
case hwmon_fan:
|
||||
if (hp_wmi_get_fan_speed(channel) >= 0)
|
||||
return 0444;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
ret = hp_wmi_get_fan_speed(channel);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return 0;
|
||||
case hwmon_pwm:
|
||||
switch (hp_wmi_fan_speed_max_get()) {
|
||||
case 0:
|
||||
/* 0 is automatic fan, which is 2 for hwmon */
|
||||
*val = 2;
|
||||
return 0;
|
||||
case 1:
|
||||
/* 1 is max fan, which is 0
|
||||
* (no fan speed control) for hwmon
|
||||
*/
|
||||
*val = 0;
|
||||
return 0;
|
||||
default:
|
||||
/* shouldn't happen */
|
||||
return -ENODATA;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
switch (val) {
|
||||
case 0:
|
||||
/* 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);
|
||||
default:
|
||||
/* we don't support manual fan speed control */
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *info[] = {
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops ops = {
|
||||
.is_visible = hp_wmi_hwmon_is_visible,
|
||||
.read = hp_wmi_hwmon_read,
|
||||
.write = hp_wmi_hwmon_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info chip_info = {
|
||||
.ops = &ops,
|
||||
.info = info,
|
||||
};
|
||||
|
||||
static int hp_wmi_hwmon_init(void)
|
||||
{
|
||||
struct device *dev = &hp_wmi_platform_dev->dev;
|
||||
struct device *hwmon;
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver,
|
||||
&chip_info, NULL);
|
||||
|
||||
if (IS_ERR(hwmon)) {
|
||||
dev_err(dev, "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);
|
||||
|
|
Loading…
Reference in New Issue