Commit 0f0ac158 authored by Luke D. Jones's avatar Luke D. Jones Committed by Hans de Goede
Browse files

platform/x86: asus-wmi: Add support for custom fan curves



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: default avatarLuke D. Jones <luke@ljones.dev>
Link: https://lore.kernel.org/r/20211024033705.5595-2-luke@ljones.dev


Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 79f960e2
Loading
Loading
Loading
Loading
+562 −5
Original line number Diff line number Diff line
@@ -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)
+2 −0
Original line number Diff line number Diff line
@@ -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