Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: alienware-wmi-wmax: Add support for manual fan control

All models with the "AWCC" WMAX device support a way of manually
controlling fans.

The PWM duty cycle of a fan can't be controlled directly. Instead the
AWCC interface let's us tune a fan `boost` value, which has the
following empirically discovered, approximate behavior over the PWM
value:

pwm = pwm_base + (fan_boost / 255) * (pwm_max - pwm_base)

Where the pwm_base is the locked PWM value controlled by the FW and
fan_boost is a value between 0 and 255.

Expose this fan_boost knob as a custom HWMON attribute.

Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Jean Delvare <jdelvare@suse.com>
Cc: linux-hwmon@vger.kernel.org
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
Link: https://lore.kernel.org/r/20250329-hwm-v7-8-a14ea39d8a94@gmail.com
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Kurt Borja and committed by
Ilpo Järvinen
07ac2759 d6999078

+172 -2
+172 -2
drivers/platform/x86/dell/alienware-wmi-wmax.c
··· 14 14 #include <linux/bits.h> 15 15 #include <linux/dmi.h> 16 16 #include <linux/hwmon.h> 17 + #include <linux/hwmon-sysfs.h> 18 + #include <linux/kstrtox.h> 19 + #include <linux/minmax.h> 17 20 #include <linux/moduleparam.h> 18 21 #include <linux/platform_profile.h> 22 + #include <linux/pm.h> 19 23 #include <linux/units.h> 20 24 #include <linux/wmi.h> 21 25 #include "alienware-wmi.h" ··· 187 183 AWCC_OP_GET_FAN_MIN_RPM = 0x08, 188 184 AWCC_OP_GET_FAN_MAX_RPM = 0x09, 189 185 AWCC_OP_GET_CURRENT_PROFILE = 0x0B, 186 + AWCC_OP_GET_FAN_BOOST = 0x0C, 190 187 }; 191 188 192 189 enum AWCC_THERMAL_CONTROL_OPERATIONS { 193 190 AWCC_OP_ACTIVATE_PROFILE = 0x01, 191 + AWCC_OP_SET_FAN_BOOST = 0x02, 194 192 }; 195 193 196 194 enum AWCC_GAME_SHIFT_STATUS_OPERATIONS { ··· 256 250 const char *label; 257 251 u32 min_rpm; 258 252 u32 max_rpm; 253 + u8 suspend_cache; 259 254 u8 id; 260 255 }; 261 256 ··· 645 638 return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); 646 639 } 647 640 641 + static int awcc_op_get_fan_boost(struct wmi_device *wdev, u8 fan_id, u32 *out) 642 + { 643 + struct wmax_u32_args args = { 644 + .operation = AWCC_OP_GET_FAN_BOOST, 645 + .arg1 = fan_id, 646 + .arg2 = 0, 647 + .arg3 = 0, 648 + }; 649 + 650 + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); 651 + } 652 + 648 653 static int awcc_op_get_current_profile(struct wmi_device *wdev, u32 *out) 649 654 { 650 655 struct wmax_u32_args args = { ··· 675 656 .operation = AWCC_OP_ACTIVATE_PROFILE, 676 657 .arg1 = profile, 677 658 .arg2 = 0, 659 + .arg3 = 0, 660 + }; 661 + u32 out; 662 + 663 + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out); 664 + } 665 + 666 + static int awcc_op_set_fan_boost(struct wmi_device *wdev, u8 fan_id, u8 boost) 667 + { 668 + struct wmax_u32_args args = { 669 + .operation = AWCC_OP_SET_FAN_BOOST, 670 + .arg1 = fan_id, 671 + .arg2 = boost, 678 672 .arg3 = 0, 679 673 }; 680 674 u32 out; ··· 859 827 .info = awcc_hwmon_info, 860 828 }; 861 829 830 + static ssize_t fan_boost_show(struct device *dev, struct device_attribute *attr, 831 + char *buf) 832 + { 833 + struct awcc_priv *priv = dev_get_drvdata(dev); 834 + int index = to_sensor_dev_attr(attr)->index; 835 + struct awcc_fan_data *fan = priv->fan_data[index]; 836 + u32 boost; 837 + int ret; 838 + 839 + ret = awcc_op_get_fan_boost(priv->wdev, fan->id, &boost); 840 + if (ret) 841 + return ret; 842 + 843 + return sysfs_emit(buf, "%u\n", boost); 844 + } 845 + 846 + static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr, 847 + const char *buf, size_t count) 848 + { 849 + struct awcc_priv *priv = dev_get_drvdata(dev); 850 + int index = to_sensor_dev_attr(attr)->index; 851 + struct awcc_fan_data *fan = priv->fan_data[index]; 852 + unsigned long val; 853 + int ret; 854 + 855 + ret = kstrtoul(buf, 0, &val); 856 + if (ret) 857 + return ret; 858 + 859 + ret = awcc_op_set_fan_boost(priv->wdev, fan->id, clamp_val(val, 0, 255)); 860 + 861 + return ret ? ret : count; 862 + } 863 + 864 + static SENSOR_DEVICE_ATTR_RW(fan1_boost, fan_boost, 0); 865 + static SENSOR_DEVICE_ATTR_RW(fan2_boost, fan_boost, 1); 866 + static SENSOR_DEVICE_ATTR_RW(fan3_boost, fan_boost, 2); 867 + static SENSOR_DEVICE_ATTR_RW(fan4_boost, fan_boost, 3); 868 + static SENSOR_DEVICE_ATTR_RW(fan5_boost, fan_boost, 4); 869 + static SENSOR_DEVICE_ATTR_RW(fan6_boost, fan_boost, 5); 870 + 871 + static umode_t fan_boost_attr_visible(struct kobject *kobj, struct attribute *attr, int n) 872 + { 873 + struct awcc_priv *priv = dev_get_drvdata(kobj_to_dev(kobj)); 874 + 875 + return n < priv->fan_count ? attr->mode : 0; 876 + } 877 + 878 + static bool fan_boost_group_visible(struct kobject *kobj) 879 + { 880 + return true; 881 + } 882 + 883 + DEFINE_SYSFS_GROUP_VISIBLE(fan_boost); 884 + 885 + static struct attribute *fan_boost_attrs[] = { 886 + &sensor_dev_attr_fan1_boost.dev_attr.attr, 887 + &sensor_dev_attr_fan2_boost.dev_attr.attr, 888 + &sensor_dev_attr_fan3_boost.dev_attr.attr, 889 + &sensor_dev_attr_fan4_boost.dev_attr.attr, 890 + &sensor_dev_attr_fan5_boost.dev_attr.attr, 891 + &sensor_dev_attr_fan6_boost.dev_attr.attr, 892 + NULL 893 + }; 894 + 895 + static const struct attribute_group fan_boost_group = { 896 + .attrs = fan_boost_attrs, 897 + .is_visible = SYSFS_GROUP_VISIBLE(fan_boost), 898 + }; 899 + 900 + static const struct attribute_group *awcc_hwmon_groups[] = { 901 + &fan_boost_group, 902 + NULL 903 + }; 904 + 862 905 static int awcc_hwmon_temps_init(struct wmi_device *wdev) 863 906 { 864 907 struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); ··· 1071 964 if (ret) 1072 965 return ret; 1073 966 1074 - priv->hwdev = devm_hwmon_device_register_with_info(&wdev->dev, "alienware_wmi", priv, 1075 - &awcc_hwmon_chip_info, NULL); 967 + priv->hwdev = devm_hwmon_device_register_with_info(&wdev->dev, "alienware_wmi", 968 + priv, &awcc_hwmon_chip_info, 969 + awcc_hwmon_groups); 1076 970 1077 971 return PTR_ERR_OR_ZERO(priv->hwdev); 972 + } 973 + 974 + static void awcc_hwmon_suspend(struct device *dev) 975 + { 976 + struct awcc_priv *priv = dev_get_drvdata(dev); 977 + struct awcc_fan_data *fan; 978 + unsigned int i; 979 + u32 boost; 980 + int ret; 981 + 982 + for (i = 0; i < priv->fan_count; i++) { 983 + fan = priv->fan_data[i]; 984 + 985 + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST, 986 + fan->id, &boost); 987 + if (ret) 988 + dev_err(dev, "Failed to store Fan %u boost while suspending\n", i); 989 + 990 + fan->suspend_cache = ret ? 0 : clamp_val(boost, 0, 255); 991 + 992 + awcc_op_set_fan_boost(priv->wdev, fan->id, 0); 993 + if (ret) 994 + dev_err(dev, "Failed to set Fan %u boost to 0 while suspending\n", i); 995 + } 996 + } 997 + 998 + static void awcc_hwmon_resume(struct device *dev) 999 + { 1000 + struct awcc_priv *priv = dev_get_drvdata(dev); 1001 + struct awcc_fan_data *fan; 1002 + unsigned int i; 1003 + int ret; 1004 + 1005 + for (i = 0; i < priv->fan_count; i++) { 1006 + fan = priv->fan_data[i]; 1007 + 1008 + if (!fan->suspend_cache) 1009 + continue; 1010 + 1011 + ret = awcc_op_set_fan_boost(priv->wdev, fan->id, fan->suspend_cache); 1012 + if (ret) 1013 + dev_err(dev, "Failed to restore Fan %u boost while resuming\n", i); 1014 + } 1078 1015 } 1079 1016 1080 1017 /* ··· 1347 1196 return ret; 1348 1197 } 1349 1198 1199 + static int wmax_wmi_suspend(struct device *dev) 1200 + { 1201 + if (awcc->hwmon) 1202 + awcc_hwmon_suspend(dev); 1203 + 1204 + return 0; 1205 + } 1206 + 1207 + static int wmax_wmi_resume(struct device *dev) 1208 + { 1209 + if (awcc->hwmon) 1210 + awcc_hwmon_resume(dev); 1211 + 1212 + return 0; 1213 + } 1214 + 1215 + static DEFINE_SIMPLE_DEV_PM_OPS(wmax_wmi_pm_ops, wmax_wmi_suspend, wmax_wmi_resume); 1216 + 1350 1217 static const struct wmi_device_id alienware_wmax_device_id_table[] = { 1351 1218 { WMAX_CONTROL_GUID, NULL }, 1352 1219 { }, ··· 1375 1206 .driver = { 1376 1207 .name = "alienware-wmi-wmax", 1377 1208 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 1209 + .pm = pm_sleep_ptr(&wmax_wmi_pm_ops), 1378 1210 }, 1379 1211 .id_table = alienware_wmax_device_id_table, 1380 1212 .probe = wmax_wmi_probe,