diff options
Diffstat (limited to 'v16-asus-wmi-Add-support-for-custom-fan-curves.patch')
-rw-r--r-- | v16-asus-wmi-Add-support-for-custom-fan-curves.patch | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/v16-asus-wmi-Add-support-for-custom-fan-curves.patch b/v16-asus-wmi-Add-support-for-custom-fan-curves.patch new file mode 100644 index 000000000000..f8e2fe883137 --- /dev/null +++ b/v16-asus-wmi-Add-support-for-custom-fan-curves.patch @@ -0,0 +1,717 @@ +From: "Luke D. Jones" <luke@ljones.dev> +To: linux-kernel@vger.kernel.org +Cc: hdegoede@redhat.com, platform-driver-x86@vger.kernel.org, + hadess@hadess.net, pobrn@protonmail.com, linux@roeck-us.net, + "Luke D. Jones" <luke@ljones.dev> +Subject: [PATCH v16] asus-wmi: Add support for custom fan curves +Date: Sun, 24 Oct 2021 16:37:05 +1300 + +Add support for custom fan curves found on some ASUS ROG laptops. + +These laptops have the ability to set a custom curve for the CPU +and GPU fans via two ACPI methods. + +This patch adds two pwm<N> attributes to the hwmon sysfs, +pwm1 for CPU fan, pwm2 for GPU fan. Both are under the hwmon of the +name `asus_custom_fan_curve`. There is no safety check of the set +fan curves - this must be done in userspace. + +The fans have settings [1,2,3] under pwm<N>_enable: +1. Enable and write settings out +2. Disable and use factory fan mode +3. Same as 2, additionally restoring default factory curve. + +Use of 2 means that the curve the user has set is still stored and +won't be erased, but the laptop will be using its default auto-fan +mode. Re-enabling the manual mode then activates the curves again. + +Notes: +- pwm<N>_enable = 0 is an invalid setting. +- pwm is actually a percentage and is scaled on writing to device. + +Signed-off-by: Luke D. Jones <luke@ljones.dev> +--- + drivers/platform/x86/asus-wmi.c | 567 ++++++++++++++++++++- + include/linux/platform_data/x86/asus-wmi.h | 2 + + 2 files changed, 564 insertions(+), 5 deletions(-) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index e14fb5fa7324..680570b37c2f 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -106,8 +106,17 @@ module_param(fnlock_default, bool, 0444); + + #define WMI_EVENT_MASK 0xFFFF + ++#define FAN_CURVE_POINTS 8 ++#define FAN_CURVE_BUF_LEN (FAN_CURVE_POINTS * 2) ++#define FAN_CURVE_DEV_CPU 0x00 ++#define FAN_CURVE_DEV_GPU 0x01 ++/* Mask to determine if setting temperature or percentage */ ++#define FAN_CURVE_PWM_MASK 0x04 ++ + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + ++static int throttle_thermal_policy_write(struct asus_wmi *); ++ + static bool ashs_present(void) + { + int i = 0; +@@ -122,7 +131,8 @@ struct bios_args { + u32 arg0; + u32 arg1; + u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */ +- u32 arg4; ++ u32 arg3; ++ u32 arg4; /* Some ROG laptops require a full 5 input args */ + u32 arg5; + } __packed; + +@@ -173,6 +183,13 @@ enum fan_type { + FAN_TYPE_SPEC83, /* starting in Spec 8.3, use CPU_FAN_CTRL */ + }; + ++struct fan_curve_data { ++ bool enabled; ++ u32 device_id; ++ u8 temps[FAN_CURVE_POINTS]; ++ u8 percents[FAN_CURVE_POINTS]; ++}; ++ + struct asus_wmi { + int dsts_id; + int spec; +@@ -220,6 +237,10 @@ struct asus_wmi { + bool throttle_thermal_policy_available; + u8 throttle_thermal_policy_mode; + ++ bool cpu_fan_curve_available; ++ bool gpu_fan_curve_available; ++ struct fan_curve_data custom_fan_curves[2]; ++ + struct platform_profile_handler platform_profile_handler; + bool platform_profile_support; + +@@ -285,6 +306,103 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) + } + EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method); + ++static int asus_wmi_evaluate_method5(u32 method_id, ++ u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval) ++{ ++ struct bios_args args = { ++ .arg0 = arg0, ++ .arg1 = arg1, ++ .arg2 = arg2, ++ .arg3 = arg3, ++ .arg4 = arg4, ++ }; ++ struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ union acpi_object *obj; ++ u32 tmp = 0; ++ ++ status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, ++ &input, &output); ++ ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ obj = (union acpi_object *)output.pointer; ++ if (obj && obj->type == ACPI_TYPE_INTEGER) ++ tmp = (u32) obj->integer.value; ++ ++ if (retval) ++ *retval = tmp; ++ ++ kfree(obj); ++ ++ if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) ++ return -ENODEV; ++ ++ return 0; ++} ++ ++/* ++ * Returns as an error if the method output is not a buffer. Typically this ++ * means that the method called is unsupported. ++ */ ++static int asus_wmi_evaluate_method_buf(u32 method_id, ++ u32 arg0, u32 arg1, u8 *ret_buffer, size_t size) ++{ ++ struct bios_args args = { ++ .arg0 = arg0, ++ .arg1 = arg1, ++ .arg2 = 0, ++ }; ++ struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ union acpi_object *obj; ++ int err = 0; ++ ++ status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, ++ &input, &output); ++ ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ obj = (union acpi_object *)output.pointer; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_BUFFER: ++ if (obj->buffer.length > size) ++ err = -ENOSPC; ++ if (obj->buffer.length == 0) ++ err = -ENODATA; ++ ++ memcpy(ret_buffer, obj->buffer.pointer, obj->buffer.length); ++ break; ++ case ACPI_TYPE_INTEGER: ++ err = (u32)obj->integer.value; ++ ++ if (err == ASUS_WMI_UNSUPPORTED_METHOD) ++ err = -ENODEV; ++ /* ++ * At least one method returns a 0 with no buffer if no arg ++ * is provided, such as ASUS_WMI_DEVID_CPU_FAN_CURVE ++ */ ++ if (err == 0) ++ err = -ENODATA; ++ break; ++ default: ++ err = -ENODATA; ++ break; ++ } ++ ++ kfree(obj); ++ ++ if (err) ++ return err; ++ ++ return 0; ++} ++ + static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args) + { + struct acpi_buffer input; +@@ -1806,6 +1924,13 @@ static ssize_t pwm1_enable_store(struct device *dev, + } + + asus->fan_pwm_mode = state; ++ ++ /* Must set to disabled if mode is toggled */ ++ if (asus->cpu_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false; ++ if (asus->gpu_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false; ++ + return count; + } + +@@ -1953,9 +2078,9 @@ static int fan_boost_mode_check_present(struct asus_wmi *asus) + + static int fan_boost_mode_write(struct asus_wmi *asus) + { +- int err; +- u8 value; + u32 retval; ++ u8 value; ++ int err; + + value = asus->fan_boost_mode; + +@@ -2013,10 +2138,10 @@ static ssize_t fan_boost_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) + { +- int result; +- u8 new_mode; + struct asus_wmi *asus = dev_get_drvdata(dev); + u8 mask = asus->fan_boost_mode_mask; ++ u8 new_mode; ++ int result; + + result = kstrtou8(buf, 10, &new_mode); + if (result < 0) { +@@ -2043,6 +2168,426 @@ static ssize_t fan_boost_mode_store(struct device *dev, + // Fan boost mode: 0 - normal, 1 - overboost, 2 - silent + static DEVICE_ATTR_RW(fan_boost_mode); + ++/* Custom fan curves **********************************************************/ ++ ++static void fan_curve_copy_from_buf(struct fan_curve_data *data, u8 *buf) ++{ ++ int i; ++ ++ for (i = 0; i < FAN_CURVE_POINTS; i++) { ++ data->temps[i] = buf[i]; ++ } ++ ++ for (i = 0; i < FAN_CURVE_POINTS; i++) { ++ data->percents[i] = ++ 255 * buf[i + FAN_CURVE_POINTS] / 100; ++ } ++} ++ ++static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev) ++{ ++ struct fan_curve_data *curves; ++ u8 buf[FAN_CURVE_BUF_LEN]; ++ int fan_idx = 0; ++ u8 mode = 0; ++ int err; ++ ++ if (asus->throttle_thermal_policy_available) ++ mode = asus->throttle_thermal_policy_mode; ++ /* DEVID_<C/G>PU_FAN_CURVE is switched for OVERBOOST vs SILENT */ ++ if (mode == 2) ++ mode = 1; ++ else if (mode == 1) ++ mode = 2; ++ ++ if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE) ++ fan_idx = FAN_CURVE_DEV_GPU; ++ ++ curves = &asus->custom_fan_curves[fan_idx]; ++ err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf, ++ FAN_CURVE_BUF_LEN); ++ if (err) ++ return err; ++ ++ fan_curve_copy_from_buf(curves, buf); ++ curves->device_id = fan_dev; ++ ++ return 0; ++} ++ ++/* Check if capability exists, and populate defaults */ ++static int fan_curve_check_present(struct asus_wmi *asus, bool *available, ++ u32 fan_dev) ++{ ++ int err; ++ ++ *available = false; ++ ++ err = fan_curve_get_factory_default(asus, fan_dev); ++ if (err) { ++ if (err == -ENODEV) ++ return 0; ++ return err; ++ } ++ ++ *available = true; ++ return 0; ++} ++ ++/* Determine which fan the attribute is for if SENSOR_ATTR */ ++static struct fan_curve_data *fan_curve_attr_select(struct asus_wmi *asus, ++ struct device_attribute *attr) ++{ ++ int index = to_sensor_dev_attr(attr)->index; ++ ++ return &asus->custom_fan_curves[index & FAN_CURVE_DEV_GPU]; ++} ++ ++/* Determine which fan the attribute is for if SENSOR_ATTR_2 */ ++static struct fan_curve_data *fan_curve_attr_2_select(struct asus_wmi *asus, ++ struct device_attribute *attr) ++{ ++ int nr = to_sensor_dev_attr_2(attr)->nr; ++ ++ return &asus->custom_fan_curves[nr & FAN_CURVE_DEV_GPU]; ++} ++ ++static ssize_t fan_curve_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ struct fan_curve_data *data; ++ int value, index, nr; ++ ++ data = fan_curve_attr_2_select(asus, attr); ++ index = dev_attr->index; ++ nr = dev_attr->nr; ++ ++ if (nr & FAN_CURVE_PWM_MASK) ++ value = data->percents[index]; ++ else ++ value = data->temps[index]; ++ ++ return sysfs_emit(buf, "%d\n", value); ++} ++ ++/* ++ * "fan_dev" is the related WMI method such as ASUS_WMI_DEVID_CPU_FAN_CURVE. ++ */ ++static int fan_curve_write(struct asus_wmi *asus, ++ struct fan_curve_data *data) ++{ ++ u32 arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0; ++ u8 *percents = data->percents; ++ u8 *temps = data->temps; ++ int ret, i, shift = 0; ++ ++ if (!data->enabled) ++ return 0; ++ ++ for (i = 0; i < FAN_CURVE_POINTS / 2; i++) { ++ arg1 += (temps[i]) << shift; ++ arg2 += (temps[i + 4]) << shift; ++ /* Scale to percentage for device */ ++ arg3 += (100 * percents[i] / 255) << shift; ++ arg4 += (100 * percents[i + 4] / 255) << shift; ++ shift += 8; ++ } ++ ++ return asus_wmi_evaluate_method5(ASUS_WMI_METHODID_DEVS, ++ data->device_id, ++ arg1, arg2, arg3, arg4, &ret); ++} ++ ++static ssize_t fan_curve_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ struct fan_curve_data *data; ++ u8 value; ++ int err; ++ ++ int pwm = dev_attr->nr & FAN_CURVE_PWM_MASK; ++ int index = dev_attr->index; ++ ++ data = fan_curve_attr_2_select(asus, attr); ++ ++ err = kstrtou8(buf, 10, &value); ++ if (err < 0) ++ return err; ++ ++ if (pwm) { ++ data->percents[index] = value; ++ } else { ++ data->temps[index] = value; ++ } ++ ++ /* ++ * Mark as disabled so the user has to explicitly enable to apply a ++ * changed fan curve. This prevents potential lockups from writing out ++ * many changes as one-write-per-change. ++ */ ++ data->enabled = false; ++ ++ return count; ++} ++ ++static ssize_t fan_curve_enable_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ struct fan_curve_data *data; ++ int out = 2; ++ ++ data = fan_curve_attr_select(asus, attr); ++ ++ if (data->enabled) ++ out = 1; ++ ++ return sysfs_emit(buf, "%d\n", out); ++} ++ ++static ssize_t fan_curve_enable_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(dev); ++ struct fan_curve_data *data; ++ int value, err; ++ ++ data = fan_curve_attr_select(asus, attr); ++ ++ err = kstrtoint(buf, 10, &value); ++ if (err < 0) ++ return err; ++ ++ switch (value) { ++ case 1: ++ data->enabled = true; ++ break; ++ case 2: ++ data->enabled = false; ++ break; ++ /* ++ * Auto + reset the fan curve data to defaults. Make it an explicit ++ * option so that users don't accidentally overwrite a set fan curve. ++ */ ++ case 3: ++ err = fan_curve_get_factory_default(asus, data->device_id); ++ if (err) ++ return err; ++ data->enabled = false; ++ break; ++ default: ++ return -EINVAL; ++ }; ++ ++ if (data->enabled) { ++ err = fan_curve_write(asus, data); ++ if (err) ++ return err; ++ } else { ++ /* ++ * For machines with throttle this is the only way to reset fans ++ * to default mode of operation (does not erase curve data). ++ */ ++ if (asus->throttle_thermal_policy_available) { ++ err = throttle_thermal_policy_write(asus); ++ if (err) ++ return err; ++ /* Similar is true for laptops with this fan */ ++ } else if (asus->fan_type == FAN_TYPE_SPEC83) { ++ err = asus_fan_set_auto(asus); ++ if (err) ++ return err; ++ } else { ++ /* Safeguard against fautly ACPI tables */ ++ err = fan_curve_get_factory_default(asus, data->device_id); ++ if (err) ++ return err; ++ err = fan_curve_write(asus, data); ++ if (err) ++ return err; ++ } ++ } ++ return count; ++} ++ ++/* CPU */ ++static SENSOR_DEVICE_ATTR_RW(pwm1_enable, fan_curve_enable, FAN_CURVE_DEV_CPU); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp, fan_curve, ++ FAN_CURVE_DEV_CPU, 7); ++ ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_pwm, fan_curve, ++ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 7); ++ ++/* GPU */ ++static SENSOR_DEVICE_ATTR_RW(pwm2_enable, fan_curve_enable, FAN_CURVE_DEV_GPU); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_temp, fan_curve, ++ FAN_CURVE_DEV_GPU, 7); ++ ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve, ++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7); ++ ++static struct attribute *asus_fan_curve_attr[] = { ++ /* CPU */ ++ &sensor_dev_attr_pwm1_enable.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr, ++ /* GPU */ ++ &sensor_dev_attr_pwm2_enable.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point7_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point8_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point7_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point8_pwm.dev_attr.attr, ++ NULL ++}; ++ ++static umode_t asus_fan_curve_is_visible(struct kobject *kobj, ++ struct attribute *attr, int idx) ++{ ++ struct device *dev = container_of(kobj, struct device, kobj); ++ struct asus_wmi *asus = dev_get_drvdata(dev->parent); ++ ++ /* ++ * Check the char instead of casting attr as there are two attr types ++ * involved here (attr1 and attr2) ++ */ ++ if (asus->cpu_fan_curve_available && attr->name[3] == '1') ++ return 0644; ++ ++ if (asus->gpu_fan_curve_available && attr->name[3] == '2') ++ return 0644; ++ ++ return 0; ++} ++ ++static const struct attribute_group asus_fan_curve_attr_group = { ++ .is_visible = asus_fan_curve_is_visible, ++ .attrs = asus_fan_curve_attr, ++}; ++__ATTRIBUTE_GROUPS(asus_fan_curve_attr); ++ ++/* ++ * Must be initialised after throttle_thermal_policy_check_present() as ++ * we check the status of throttle_thermal_policy_available during init. ++ */ ++static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) ++{ ++ struct device *dev = &asus->platform_device->dev; ++ struct device *hwmon; ++ int err; ++ ++ err = fan_curve_check_present(asus, &asus->cpu_fan_curve_available, ++ ASUS_WMI_DEVID_CPU_FAN_CURVE); ++ if (err) ++ return err; ++ ++ err = fan_curve_check_present(asus, &asus->gpu_fan_curve_available, ++ ASUS_WMI_DEVID_GPU_FAN_CURVE); ++ if (err) ++ return err; ++ ++ if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available) ++ return 0; ++ ++ hwmon = devm_hwmon_device_register_with_groups( ++ dev, "asus_custom_fan_curve", asus, asus_fan_curve_attr_groups); ++ ++ if (IS_ERR(hwmon)) { ++ dev_err(dev, ++ "Could not register asus_custom_fan_curve device\n"); ++ return PTR_ERR(hwmon); ++ } ++ ++ return 0; ++} ++ + /* Throttle thermal policy ****************************************************/ + + static int throttle_thermal_policy_check_present(struct asus_wmi *asus) +@@ -2092,6 +2637,12 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus) + return -EIO; + } + ++ /* Must set to disabled if mode is toggled */ ++ if (asus->cpu_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false; ++ if (asus->gpu_fan_curve_available) ++ asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false; ++ + return 0; + } + +@@ -3035,6 +3586,10 @@ static int asus_wmi_add(struct platform_device *pdev) + if (err) + goto fail_hwmon; + ++ err = asus_wmi_custom_fan_curve_init(asus); ++ if (err) ++ goto fail_custom_fan_curve; ++ + err = asus_wmi_led_init(asus); + if (err) + goto fail_leds; +@@ -3106,6 +3661,7 @@ static int asus_wmi_add(struct platform_device *pdev) + asus_wmi_sysfs_exit(asus->platform_device); + fail_sysfs: + fail_throttle_thermal_policy: ++fail_custom_fan_curve: + fail_platform_profile_setup: + if (asus->platform_profile_support) + platform_profile_remove(); +@@ -3131,6 +3687,7 @@ static int asus_wmi_remove(struct platform_device *device) + asus_wmi_debugfs_exit(asus); + asus_wmi_sysfs_exit(asus->platform_device); + asus_fan_set_auto(asus); ++ throttle_thermal_policy_set_default(asus); + asus_wmi_battery_exit(asus); + + if (asus->platform_profile_support) +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +index 17dc5cb6f3f2..a571b47ff362 100644 +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -77,6 +77,8 @@ + #define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011 + #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */ + #define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013 ++#define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024 ++#define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025 + + /* Power */ + #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 |