diff mbox series

[3/3] ACPI: thermal: Allow userspace applications to change the cooling mode

Message ID 20250410165456.4173-4-W_Armin@gmx.de
State New
Headers show
Series ACPI: thermal: Properly support the _SCP control method | expand

Commit Message

Armin Wolf April 10, 2025, 4:54 p.m. UTC
Users might want to signal the ACPI firmware whether active or passive
cooling is preferred. This is already possible under Windows using the
Windows power settings.

Add a new "cooling_mode" sysfs attribute which can be used by users to
change the cooling mode of a given thermal zone. Only thermal zones
supporting the _SCP control method will expose this new attribute.

Signed-off-by: Armin Wolf <W_Armin@gmx.de>
---
 .../ABI/testing/sysfs-driver-thermal          |  14 ++
 MAINTAINERS                                   |   1 +
 drivers/acpi/thermal.c                        | 129 ++++++++++++++++--
 3 files changed, 132 insertions(+), 12 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-thermal

--
2.39.5
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-driver-thermal b/Documentation/ABI/testing/sysfs-driver-thermal
new file mode 100644
index 000000000000..bf2349f31863
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-thermal
@@ -0,0 +1,14 @@ 
+What:		/sys/bus/acpi/devices/LNXTHERM:*/cooling_mode
+Date:		April 2025
+KernelVersion:	6.16
+Contact:	Armin Wolf <W_Armin@gmx.de>
+Description:
+		A string representing the preferred cooling mode of the
+		associated ACPI thermal zone:
+
+		- "active" for preferring active cooling
+
+		- "passive" for preferring passive cooling
+
+		The exact characteristics of both cooling modes depend
+		on the underlying ACPI firmware implementation.
diff --git a/MAINTAINERS b/MAINTAINERS
index 96b827049501..fd3102723518 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -382,6 +382,7 @@  R:	Zhang Rui <rui.zhang@intel.com>
 L:	linux-acpi@vger.kernel.org
 S:	Supported
 B:	https://bugzilla.kernel.org
+F:	Documentation/ABI/testing/sysfs-driver-thermal
 F:	drivers/acpi/*thermal*

 ACPI VIOT DRIVER
diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c
index 5c2defe55898..52d0c777a93a 100644
--- a/drivers/acpi/thermal.c
+++ b/drivers/acpi/thermal.c
@@ -15,11 +15,14 @@ 

 #define pr_fmt(fmt) "ACPI: thermal: " fmt

+#include <linux/cleanup.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/dmi.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
 #include <linux/types.h>
 #include <linux/jiffies.h>
 #include <linux/kmod.h>
@@ -40,7 +43,6 @@ 
 #define ACPI_THERMAL_NOTIFY_DEVICES	0x82
 #define ACPI_THERMAL_NOTIFY_CRITICAL	0xF0
 #define ACPI_THERMAL_NOTIFY_HOT		0xF1
-#define ACPI_THERMAL_MODE_ACTIVE	0x00

 #define ACPI_THERMAL_MAX_ACTIVE		10
 #define ACPI_THERMAL_MAX_LIMIT_STR_LEN	65
@@ -85,6 +87,16 @@  MODULE_PARM_DESC(psv, "Disable or override all passive trip points.");

 static struct workqueue_struct *acpi_thermal_pm_queue;

+enum acpi_thermal_cooling_mode {
+	ACPI_THERMAL_MODE_ACTIVE	= 0x00,
+	ACPI_THERMAL_MODE_PASSIVE	= 0x01,
+};
+
+static const char * const acpi_thermal_cooling_mode_strings[] = {
+	[ACPI_THERMAL_MODE_ACTIVE]	= "active",
+	[ACPI_THERMAL_MODE_PASSIVE]	= "passive",
+};
+
 struct acpi_thermal_trip {
 	unsigned long temp_dk;
 	struct acpi_handle_list devices;
@@ -119,6 +131,9 @@  struct acpi_thermal {
 	struct work_struct thermal_check_work;
 	struct mutex thermal_check_lock;
 	refcount_t thermal_check_count;
+	bool supports_cooling_mode;
+	struct mutex cooling_mode_lock;       /* Protects cooling mode updates */
+	enum acpi_thermal_cooling_mode cooling_mode;
 };

 /* --------------------------------------------------------------------------
@@ -328,7 +343,6 @@  static void acpi_queue_thermal_check(struct acpi_thermal *tz)
 static void acpi_thermal_trips_update(struct acpi_thermal *tz, u32 event)
 {
 	struct adjust_trip_data atd = { .tz = tz, .event = event };
-	struct acpi_device *adev = tz->device;

 	/*
 	 * Use thermal_zone_for_each_trip() to carry out the trip points
@@ -340,8 +354,6 @@  static void acpi_thermal_trips_update(struct acpi_thermal *tz, u32 event)
 	thermal_zone_for_each_trip(tz->thermal_zone,
 				   acpi_thermal_adjust_trip, &atd);
 	acpi_queue_thermal_check(tz);
-	acpi_bus_generate_netlink_event(adev->pnp.device_class,
-					dev_name(&adev->dev), event, 0);
 }

 static int acpi_thermal_get_critical_trip(struct acpi_thermal *tz)
@@ -473,6 +485,18 @@  static void acpi_thermal_get_trip_points(struct acpi_thermal *tz)
 		tz->trips.active[i].trip.temp_dk = THERMAL_TEMP_INVALID;
 }

+static int acpi_thermal_set_cooling_mode(struct acpi_thermal *tz,
+					 enum acpi_thermal_cooling_mode mode)
+{
+	acpi_status status;
+
+	status = acpi_execute_simple_method(tz->device->handle, "_SCP", mode);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	return 0;
+}
+
 /* sys I/F for generic thermal sysfs support */

 static int thermal_get_temp(struct thermal_zone_device *thermal, int *temp)
@@ -683,6 +707,8 @@  static void acpi_thermal_notify(acpi_handle handle, u32 event, void *data)
 	case ACPI_THERMAL_NOTIFY_THRESHOLDS:
 	case ACPI_THERMAL_NOTIFY_DEVICES:
 		acpi_thermal_trips_update(tz, event);
+		acpi_bus_generate_netlink_event(device->pnp.device_class, dev_name(&device->dev),
+						event, 0);
 		break;
 	default:
 		acpi_handle_debug(device->handle, "Unsupported event [0x%x]\n",
@@ -777,6 +803,65 @@  static void acpi_thermal_free_thermal_zone(struct acpi_thermal *tz)
 	kfree(tz);
 }

+static ssize_t cooling_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct acpi_thermal *tz = acpi_driver_data(to_acpi_device(dev));
+
+	guard(mutex)(&tz->cooling_mode_lock);
+
+	return sysfs_emit(buf, "%s\n", acpi_thermal_cooling_mode_strings[tz->cooling_mode]);
+}
+
+static ssize_t cooling_mode_store(struct device *dev, struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct acpi_thermal *tz = acpi_driver_data(to_acpi_device(dev));
+	int ret, mode;
+
+	mode = sysfs_match_string(acpi_thermal_cooling_mode_strings, buf);
+	if (mode < 0)
+		return mode;
+
+	guard(mutex)(&tz->cooling_mode_lock);
+
+	ret = acpi_thermal_set_cooling_mode(tz, mode);
+	if (ret < 0)
+		return ret;
+
+	tz->cooling_mode = mode;
+	acpi_thermal_trips_update(tz, ACPI_THERMAL_NOTIFY_THRESHOLDS);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(cooling_mode);
+
+static struct attribute *acpi_thermal_attrs[] = {
+	&dev_attr_cooling_mode.attr,
+	NULL
+};
+
+static umode_t acpi_thermal_group_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct acpi_thermal *tz = acpi_driver_data(to_acpi_device(dev));
+
+	if (tz->supports_cooling_mode)
+		return attr->mode;
+
+	return 0;
+}
+
+static const struct attribute_group acpi_thermal_group = {
+	.is_visible = acpi_thermal_group_is_visible,
+	.attrs = acpi_thermal_attrs,
+};
+
+static const struct attribute_group *acpi_thermal_groups[] = {
+	&acpi_thermal_group,
+	NULL
+};
+
 static int acpi_thermal_add(struct acpi_device *device)
 {
 	struct thermal_trip trip_table[ACPI_THERMAL_MAX_NR_TRIPS] = { 0 };
@@ -786,7 +871,7 @@  static int acpi_thermal_add(struct acpi_device *device)
 	int crit_temp, hot_temp;
 	int passive_delay = 0;
 	int result;
-	int i;
+	int ret, i;

 	if (!device)
 		return -EINVAL;
@@ -795,6 +880,10 @@  static int acpi_thermal_add(struct acpi_device *device)
 	if (!tz)
 		return -ENOMEM;

+	ret = devm_mutex_init(&device->dev, &tz->cooling_mode_lock);
+	if (ret < 0)
+		return ret;
+
 	tz->device = device;
 	strscpy(tz->name, device->pnp.bus_id);
 	strscpy(acpi_device_name(device), ACPI_THERMAL_DEVICE_NAME);
@@ -803,11 +892,18 @@  static int acpi_thermal_add(struct acpi_device *device)

 	acpi_thermal_aml_dependency_fix(tz);

-	/*
-	 * Set the cooling mode [_SCP] to active cooling. This needs to happen before
-	 * we retrieve the trip point values.
-	 */
-	acpi_execute_simple_method(tz->device->handle, "_SCP", ACPI_THERMAL_MODE_ACTIVE);
+	tz->supports_cooling_mode = acpi_has_method(tz->device->handle, "_SCP");
+	if (tz->supports_cooling_mode) {
+		/*
+		 * Set the initial cooling mode to active cooling. This needs to happen
+		 * before we retrieve the trip point values.
+		 */
+		ret = acpi_thermal_set_cooling_mode(tz, ACPI_THERMAL_MODE_ACTIVE);
+		if (ret < 0)
+			dev_err(&tz->device->dev, "Failed to set initial cooling mode\n");
+
+		tz->cooling_mode = ACPI_THERMAL_MODE_ACTIVE;
+	}

 	/* Get trip points [_ACi, _PSV, etc.] (required). */
 	acpi_thermal_get_trip_points(tz);
@@ -924,7 +1020,7 @@  static int acpi_thermal_suspend(struct device *dev)
 static int acpi_thermal_resume(struct device *dev)
 {
 	struct acpi_thermal *tz;
-	int i, j, power_state;
+	int ret, i, j, power_state;

 	if (!dev)
 		return -EINVAL;
@@ -933,6 +1029,12 @@  static int acpi_thermal_resume(struct device *dev)
 	if (!tz)
 		return -EINVAL;

+	if (tz->supports_cooling_mode) {
+		ret = acpi_thermal_set_cooling_mode(tz, tz->cooling_mode);
+		if (ret < 0)
+			dev_err(&tz->device->dev, "Failed to restore cooling mode\n");
+	}
+
 	for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
 		struct acpi_thermal_trip *acpi_trip = &tz->trips.active[i].trip;

@@ -969,7 +1071,10 @@  static struct acpi_driver acpi_thermal_driver = {
 		.add = acpi_thermal_add,
 		.remove = acpi_thermal_remove,
 		},
-	.drv.pm = &acpi_thermal_pm,
+	.drv = {
+		.dev_groups = acpi_thermal_groups,
+		.pm = &acpi_thermal_pm,
+	},
 };

 static int thermal_act(const struct dmi_system_id *d)