From 8c5281c91e1f6c719ee6db9e18ef5efb618425e7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 22 Apr 2026 17:37:14 +0200 Subject: [PATCH 01/54] thermal: core: Remove dead code from two functions Since thermal_register_governor() is only called by thermal_register_governors() which runs before clearing thermal_class_unavailable, thermal_tz_list is guaranteed to be empty when it runs, so the code walking that list in it is effectively dead. The same observation applies to the code walking thermal_tz_list in thermal_unregister_governor() which is also called only if thermal_class_unavailable hasn't been cleared. Remove the dead code from both functions mentioned above and rearrange thermal_register_governor() to reduce indentation level. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3950489.kQq0lBPeGt@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 55 +++++----------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 2f4e2dc46b8f..23d460a5da58 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -118,59 +118,28 @@ static int thermal_set_governor(struct thermal_zone_device *tz, int thermal_register_governor(struct thermal_governor *governor) { - int err; - const char *name; - struct thermal_zone_device *pos; if (!governor) return -EINVAL; guard(mutex)(&thermal_governor_lock); - err = -EBUSY; - if (!__find_governor(governor->name)) { - bool match_default; + if (__find_governor(governor->name)) + return -EBUSY; - err = 0; - list_add(&governor->governor_list, &thermal_governor_list); - match_default = !strncmp(governor->name, - DEFAULT_THERMAL_GOVERNOR, - THERMAL_NAME_LENGTH); + list_add(&governor->governor_list, &thermal_governor_list); - if (!def_governor && match_default) - def_governor = governor; - } + if (strncmp(governor->name, DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH)) + return 0; - guard(mutex)(&thermal_list_lock); + if (!def_governor) + def_governor = governor; - list_for_each_entry(pos, &thermal_tz_list, node) { - /* - * only thermal zones with specified tz->tzp->governor_name - * may run with tz->govenor unset - */ - if (pos->governor) - continue; - - name = pos->tzp->governor_name; - - if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) { - int ret; - - ret = thermal_set_governor(pos, governor); - if (ret) - dev_err(&pos->device, - "Failed to set governor %s for thermal zone %s: %d\n", - governor->name, pos->type, ret); - } - } - - return err; + return 0; } void thermal_unregister_governor(struct thermal_governor *governor) { - struct thermal_zone_device *pos; - if (!governor) return; @@ -180,14 +149,6 @@ void thermal_unregister_governor(struct thermal_governor *governor) return; list_del(&governor->governor_list); - - guard(mutex)(&thermal_list_lock); - - list_for_each_entry(pos, &thermal_tz_list, node) { - if (!strncasecmp(pos->governor->name, governor->name, - THERMAL_NAME_LENGTH)) - thermal_set_governor(pos, NULL); - } } int thermal_zone_device_set_policy(struct thermal_zone_device *tz, From 9e12b7be7f051f639b79a8c449c81519c975cd50 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 22 Apr 2026 17:39:19 +0200 Subject: [PATCH 02/54] thermal: core: Simplify unregistration of governors Thermal governors are only unregistered in the thermal_init() error path and they actually are only deleted from thermal_governor_list. Put the entire code needed to do that to thermal_unregister_governors() and rearrange thermal_init() to call that function also when thermal_register_governors() returns an error. This allows thermal_unregister_governor() to be dropped and thermal_register_governor() that is only called by thermal_register_governors() can be made static __init, so the headers of these two functions can be dropped from thermal_core.h. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/9615355.CDJkKcVGEf@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 50 ++++++++++------------------------ drivers/thermal/thermal_core.h | 2 -- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 23d460a5da58..427a76223c6f 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -116,14 +116,12 @@ static int thermal_set_governor(struct thermal_zone_device *tz, return ret; } -int thermal_register_governor(struct thermal_governor *governor) +static int __init thermal_register_governor(struct thermal_governor *governor) { if (!governor) return -EINVAL; - guard(mutex)(&thermal_governor_lock); - if (__find_governor(governor->name)) return -EBUSY; @@ -138,19 +136,6 @@ int thermal_register_governor(struct thermal_governor *governor) return 0; } -void thermal_unregister_governor(struct thermal_governor *governor) -{ - if (!governor) - return; - - guard(mutex)(&thermal_governor_lock); - - if (!__find_governor(governor->name)) - return; - - list_del(&governor->governor_list); -} - int thermal_zone_device_set_policy(struct thermal_zone_device *tz, char *policy) { @@ -186,40 +171,34 @@ int thermal_build_list_of_policies(char *buf) static void __init thermal_unregister_governors(void) { - struct thermal_governor **governor; + struct thermal_governor *gov, *pos; - for_each_governor_table(governor) - thermal_unregister_governor(*governor); + guard(mutex)(&thermal_governor_lock); + + list_for_each_entry_safe(gov, pos, &thermal_governor_list, governor_list) + list_del(&gov->governor_list); } static int __init thermal_register_governors(void) { - int ret = 0; struct thermal_governor **governor; + guard(mutex)(&thermal_governor_lock); + for_each_governor_table(governor) { + int ret; + ret = thermal_register_governor(*governor); if (ret) { pr_err("Failed to register governor: '%s'", (*governor)->name); - break; + return ret; } - pr_info("Registered thermal governor '%s'", - (*governor)->name); + pr_info("Registered thermal governor '%s'", (*governor)->name); } - if (ret) { - struct thermal_governor **gov; - - for_each_governor_table(gov) { - if (gov == governor) - break; - thermal_unregister_governor(*gov); - } - } - - return ret; + return 0; } static int __thermal_zone_device_set_mode(struct thermal_zone_device *tz, @@ -1858,7 +1837,7 @@ static int __init thermal_init(void) result = thermal_register_governors(); if (result) - goto destroy_workqueue; + goto unregister_governors; result = class_register(&thermal_class); if (result) @@ -1870,7 +1849,6 @@ static int __init thermal_init(void) unregister_governors: thermal_unregister_governors(); -destroy_workqueue: destroy_workqueue(thermal_wq); unregister_netlink: thermal_netlink_exit(); diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index d3acff602f9c..0acb7d9587ca 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -258,8 +258,6 @@ struct thermal_instance { #define to_cooling_device(_dev) \ container_of(_dev, struct thermal_cooling_device, device) -int thermal_register_governor(struct thermal_governor *); -void thermal_unregister_governor(struct thermal_governor *); int thermal_zone_device_set_policy(struct thermal_zone_device *, char *); int thermal_build_list_of_policies(char *buf); void __thermal_zone_device_update(struct thermal_zone_device *tz, From 11568924a9319c951f7086c002741c03d4d0f740 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 8 May 2026 20:05:08 +0200 Subject: [PATCH 03/54] thermal/core: Add dedicated release callback for cooling devices The thermal class release callback currently handles both thermal zones and cooling devices by checking the device name prefix. Move the cooling device cleanup to a dedicated struct device release callback. This avoids relying on device names to select the release path and keeps the cooling device lifetime handling local to the cooling device object. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260508180511.1306659-2-daniel.lezcano@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 427a76223c6f..207b86f54c4d 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -892,7 +892,6 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, static void thermal_release(struct device *dev) { struct thermal_zone_device *tz; - struct thermal_cooling_device *cdev; if (!strncmp(dev_name(dev), "thermal_zone", sizeof("thermal_zone") - 1)) { @@ -902,13 +901,6 @@ static void thermal_release(struct device *dev) ida_destroy(&tz->ida); mutex_destroy(&tz->lock); complete(&tz->removal); - } else if (!strncmp(dev_name(dev), "cooling_device", - sizeof("cooling_device") - 1)) { - cdev = to_cooling_device(dev); - thermal_cooling_device_destroy_sysfs(cdev); - kfree_const(cdev->type); - ida_free(&thermal_cdev_ida, cdev->id); - kfree(cdev); } } @@ -980,6 +972,16 @@ static void thermal_cooling_device_init_complete(struct thermal_cooling_device * thermal_zone_cdev_bind(tz, cdev); } +static void thermal_cdev_release(struct device *dev) +{ + struct thermal_cooling_device *cdev = to_cooling_device(dev); + + thermal_cooling_device_destroy_sysfs(cdev); + kfree_const(cdev->type); + ida_free(&thermal_cdev_ida, cdev->id); + kfree(cdev); +} + /** * __thermal_cooling_device_register() - register a new thermal cooling device * @np: a pointer to a device tree node. @@ -1033,6 +1035,7 @@ __thermal_cooling_device_register(struct device_node *np, cdev->ops = ops; cdev->updated = false; cdev->device.class = &thermal_class; + cdev->device.release = thermal_cdev_release; cdev->devdata = devdata; ret = cdev->ops->get_max_state(cdev, &cdev->max_state); From dc04e81fa2188d96af0c795ff45e4e5a209183b2 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 8 May 2026 20:05:09 +0200 Subject: [PATCH 04/54] thermal/core: Add dedicated release callback for thermal zones The thermal class release callback currently handles thermal zone cleanup by checking the device name prefix. Move the thermal zone cleanup to a dedicated struct device release callback. This avoids relying on device names to select the release path and keeps the thermal zone lifetime handling local to the thermal zone object. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260508180511.1306659-3-daniel.lezcano@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 207b86f54c4d..abbd20c8ac17 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -889,24 +889,8 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, kfree(pos); } -static void thermal_release(struct device *dev) -{ - struct thermal_zone_device *tz; - - if (!strncmp(dev_name(dev), "thermal_zone", - sizeof("thermal_zone") - 1)) { - tz = to_thermal_zone(dev); - thermal_zone_destroy_device_groups(tz); - thermal_set_governor(tz, NULL); - ida_destroy(&tz->ida); - mutex_destroy(&tz->lock); - complete(&tz->removal); - } -} - static const struct class thermal_class = { .name = "thermal", - .dev_release = thermal_release, }; static bool thermal_class_unavailable __ro_after_init = true; @@ -1413,6 +1397,17 @@ static void thermal_zone_init_complete(struct thermal_zone_device *tz) __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); } +static void thermal_zone_device_release(struct device *dev) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + + thermal_zone_destroy_device_groups(tz); + thermal_set_governor(tz, NULL); + ida_destroy(&tz->ida); + mutex_destroy(&tz->lock); + complete(&tz->removal); +} + /** * thermal_zone_device_register_with_trips() - register a new thermal zone device * @type: the thermal zone device type @@ -1520,6 +1515,7 @@ thermal_zone_device_register_with_trips(const char *type, tz->ops.critical = thermal_zone_device_critical; tz->device.class = &thermal_class; + tz->device.release = thermal_zone_device_release; tz->devdata = devdata; tz->num_trips = num_trips; for_each_trip_desc(tz, td) { From 34f54003643e907e2e5d0433e81a6fbfc419fd88 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 8 May 2026 20:05:10 +0200 Subject: [PATCH 05/54] thermal/core: Allocate the thermal class dynamically Use class_create() instead of a statically allocated struct class. This allows the thermal class to be managed through a dynamically allocated class object and avoids keeping a static class instance around. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba [ rjw: Added __ro_after_init to thermal_class ] [ rjw: Used temporary local var to store class_create() return value ] Link: https://patch.msgid.link/20260508180511.1306659-4-daniel.lezcano@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index abbd20c8ac17..06d8640819d2 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -889,9 +889,7 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, kfree(pos); } -static const struct class thermal_class = { - .name = "thermal", -}; +static struct class *thermal_class __ro_after_init; static bool thermal_class_unavailable __ro_after_init = true; static inline @@ -1018,7 +1016,7 @@ __thermal_cooling_device_register(struct device_node *np, cdev->np = np; cdev->ops = ops; cdev->updated = false; - cdev->device.class = &thermal_class; + cdev->device.class = thermal_class; cdev->device.release = thermal_cdev_release; cdev->devdata = devdata; @@ -1514,7 +1512,7 @@ thermal_zone_device_register_with_trips(const char *type, if (!tz->ops.critical) tz->ops.critical = thermal_zone_device_critical; - tz->device.class = &thermal_class; + tz->device.class = thermal_class; tz->device.release = thermal_zone_device_release; tz->devdata = devdata; tz->num_trips = num_trips; @@ -1820,6 +1818,7 @@ void thermal_pm_complete(void) static int __init thermal_init(void) { + struct class *tc; int result; thermal_debug_init(); @@ -1838,10 +1837,13 @@ static int __init thermal_init(void) if (result) goto unregister_governors; - result = class_register(&thermal_class); - if (result) + tc = class_create("thermal"); + if (IS_ERR(tc)) { + result = PTR_ERR(tc); goto unregister_governors; + } + thermal_class = tc; thermal_class_unavailable = false; return 0; From 499274d078d0c9299af671bcaaecbd9c90571539 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 8 May 2026 20:05:11 +0200 Subject: [PATCH 06/54] thermal/core: Use the thermal class pointer as init guard The thermal class is now dynamically allocated and stored as a pointer. Use the thermal_class pointer itself to check whether the thermal class has been created instead of keeping a separate thermal_class_unavailable flag. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260508180511.1306659-5-daniel.lezcano@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 06d8640819d2..ca3b6bf2f292 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -890,7 +890,6 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, } static struct class *thermal_class __ro_after_init; -static bool thermal_class_unavailable __ro_after_init = true; static inline void print_bind_err_msg(struct thermal_zone_device *tz, @@ -993,7 +992,7 @@ __thermal_cooling_device_register(struct device_node *np, !ops->set_cur_state) return ERR_PTR(-EINVAL); - if (thermal_class_unavailable) + if (!thermal_class) return ERR_PTR(-ENODEV); cdev = kzalloc_obj(*cdev); @@ -1476,7 +1475,7 @@ thermal_zone_device_register_with_trips(const char *type, if (polling_delay && passive_delay > polling_delay) return ERR_PTR(-EINVAL); - if (thermal_class_unavailable) + if (!thermal_class) return ERR_PTR(-ENODEV); tz = kzalloc_flex(*tz, trips, num_trips); @@ -1774,7 +1773,7 @@ static void __thermal_pm_prepare(void) void thermal_pm_prepare(void) { - if (thermal_class_unavailable) + if (!thermal_class) return; __thermal_pm_prepare(); @@ -1805,7 +1804,7 @@ void thermal_pm_complete(void) { struct thermal_zone_device *tz; - if (thermal_class_unavailable) + if (!thermal_class) return; guard(mutex)(&thermal_list_lock); @@ -1844,8 +1843,6 @@ static int __init thermal_init(void) } thermal_class = tc; - thermal_class_unavailable = false; - return 0; unregister_governors: From c2114dbda05354dbcf4dfbb30a2c623e8611c43a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 5 May 2026 13:36:56 +0200 Subject: [PATCH 07/54] thermal: hwmon: Fix critical temperature attribute removal Since the return value of thermal_zone_crit_temp_valid() depends on the behavior of the thermal zone .get_crit_temp() callback which may change over time in theory, thermal_remove_hwmon_sysfs() may attempt to remove a critical temperature attribute that has not been created, passing a pointer to an uninitialized attribute structure to device_remove_file(). To avoid that, set a flag in struct thermal_hwmon_temp after creating a critical temperature attribute and use the value of that flag to decide whether or not the attribute needs to be removed. Fixes: e8db5d6736a7 ("thermal: hwmon: Make the check for critical temp valid consistent") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2437056.ElGaqSPkdT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_hwmon.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index b624892bc6d6..597c33c8a555 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -40,6 +40,7 @@ struct thermal_hwmon_temp { struct thermal_zone_device *tz; struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ + bool temp_crit_present; }; static LIST_HEAD(thermal_hwmon_list); @@ -191,6 +192,8 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) &temp->temp_crit.attr); if (result) goto unregister_input; + + temp->temp_crit_present = true; } mutex_lock(&thermal_hwmon_list_lock); @@ -235,7 +238,7 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) } device_remove_file(hwmon->device, &temp->temp_input.attr); - if (thermal_zone_crit_temp_valid(tz)) + if (temp->temp_crit_present) device_remove_file(hwmon->device, &temp->temp_crit.attr); mutex_lock(&thermal_hwmon_list_lock); From d6323469bcfbda91f0aa89b7b39ad45fe822ca5d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 5 May 2026 13:44:01 +0200 Subject: [PATCH 08/54] thermal: hwmon: Register a hwmon device for each thermal zone The current code creates one hwmon device per thermal zone type and that device is registered under the first thermal zone of the given type. That turns out to be problematic when the thermal zone holding the hwmon device is removed. For example, say that there are two ACPI thermal zones on a system /sys/devices/virtual/thermal/thermal_zone0/ /sys/devices/virtual/thermal/thermal_zone1/ The current code registers a hwmon class device for thermal_zone0 only: /sys/devices/virtual/thermal/thermal_zone0/hwmon0/ because the type is "acpitz" for both of them, but it adds a sysfs attribute that belongs to thermal_zone1 under it: /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp2_input There is also /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp1_input which belongs to thermal_zone0. When thermal_zone0 is removed, say because the ACPI thermal driver is unbound from the underlying platform device, the removal code skips the removal of hwmon0 because of the temp2_input attribute belonging to thermal_zone1 which effectively prevents thermal_zone0 removal from making progress. To address this problem, rework the thermal hwmon code to register one hwmon device for each thermal zone, but since user space utilities produce confusing output in some cases when there are multiple hwmon devices with the same name attribute value present under thermal zones of the same type, append the thermal zone ID preceded by an underline character to the name of the hwmon device registered for that thermal zone. Link: https://lore.kernel.org/linux-pm/20260402021828.16556-1-liujia6264@gmail.com/ Fixes: f6b6b52ef7a5 ("thermal_hwmon: Pass the originating device down to hwmon_device_register_with_info") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3070412.e9J7NaK4W3@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_hwmon.c | 151 ++++++++++---------------------- 1 file changed, 47 insertions(+), 104 deletions(-) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index 597c33c8a555..223ae1571655 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -19,30 +19,33 @@ #include "thermal_hwmon.h" #include "thermal_core.h" -/* hwmon sys I/F */ -/* thermal zone devices with the same type share one hwmon device */ -struct thermal_hwmon_device { - char type[THERMAL_NAME_LENGTH]; - struct device *device; - int count; - struct list_head tz_list; - struct list_head node; -}; +/* + * Needs to be large enough to hold a thermal zone type string followed by an + * underline character and a 32-bit integer in decimal representation. + */ +#define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11) struct thermal_hwmon_attr { struct device_attribute attr; - char name[16]; }; /* one temperature input for each thermal zone */ struct thermal_hwmon_temp { - struct list_head hwmon_node; struct thermal_zone_device *tz; struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ bool temp_crit_present; }; +/* hwmon sys I/F */ +/* thermal zone devices with the same type share one hwmon device */ +struct thermal_hwmon_device { + char name[THERMAL_HWMON_NAME_LENGTH]; + struct device *device; + struct list_head node; + struct thermal_hwmon_temp tz_temp; +}; + static LIST_HEAD(thermal_hwmon_list); static DEFINE_MUTEX(thermal_hwmon_list_lock); @@ -88,45 +91,6 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) return sysfs_emit(buf, "%d\n", temperature); } - -static struct thermal_hwmon_device * -thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_device *hwmon; - char type[THERMAL_NAME_LENGTH]; - - mutex_lock(&thermal_hwmon_list_lock); - list_for_each_entry(hwmon, &thermal_hwmon_list, node) { - strscpy(type, tz->type); - strreplace(type, '-', '_'); - if (!strcmp(hwmon->type, type)) { - mutex_unlock(&thermal_hwmon_list_lock); - return hwmon; - } - } - mutex_unlock(&thermal_hwmon_list_lock); - - return NULL; -} - -/* Find the temperature input matching a given thermal zone */ -static struct thermal_hwmon_temp * -thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, - const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_temp *temp; - - mutex_lock(&thermal_hwmon_list_lock); - list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) - if (temp->tz == tz) { - mutex_unlock(&thermal_hwmon_list_lock); - return temp; - } - mutex_unlock(&thermal_hwmon_list_lock); - - return NULL; -} - static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) { int temp; @@ -137,54 +101,39 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; struct thermal_hwmon_temp *temp; - int new_hwmon_device = 1; int result; - hwmon = thermal_hwmon_lookup_by_type(tz); - if (hwmon) { - new_hwmon_device = 0; - goto register_sys_interface; - } - hwmon = kzalloc_obj(*hwmon); if (!hwmon) return -ENOMEM; - INIT_LIST_HEAD(&hwmon->tz_list); - strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); - strreplace(hwmon->type, '-', '_'); + /* + * Append the thermal zone ID preceded by an underline character to the + * type to disambiguate the sensors command output. + */ + scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id); + strreplace(hwmon->name, '-', '_'); hwmon->device = hwmon_device_register_for_thermal(&tz->device, - hwmon->type, hwmon); + hwmon->name, hwmon); if (IS_ERR(hwmon->device)) { result = PTR_ERR(hwmon->device); goto free_mem; } - register_sys_interface: - temp = kzalloc_obj(*temp); - if (!temp) { - result = -ENOMEM; - goto unregister_name; - } + temp = &hwmon->tz_temp; temp->tz = tz; - hwmon->count++; - snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), - "temp%d_input", hwmon->count); - temp->temp_input.attr.attr.name = temp->temp_input.name; + temp->temp_input.attr.attr.name = "temp1_input"; temp->temp_input.attr.attr.mode = 0444; temp->temp_input.attr.show = temp_input_show; sysfs_attr_init(&temp->temp_input.attr.attr); result = device_create_file(hwmon->device, &temp->temp_input.attr); if (result) - goto free_temp_mem; + goto unregister_name; if (thermal_zone_crit_temp_valid(tz)) { - snprintf(temp->temp_crit.name, - sizeof(temp->temp_crit.name), - "temp%d_crit", hwmon->count); - temp->temp_crit.attr.attr.name = temp->temp_crit.name; + temp->temp_crit.attr.attr.name = "temp1_crit"; temp->temp_crit.attr.attr.mode = 0444; temp->temp_crit.attr.show = temp_crit_show; sysfs_attr_init(&temp->temp_crit.attr.attr); @@ -196,21 +145,17 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) temp->temp_crit_present = true; } + /* The list is needed for hwmon lookup during removal. */ mutex_lock(&thermal_hwmon_list_lock); - if (new_hwmon_device) - list_add_tail(&hwmon->node, &thermal_hwmon_list); - list_add_tail(&temp->hwmon_node, &hwmon->tz_list); + list_add_tail(&hwmon->node, &thermal_hwmon_list); mutex_unlock(&thermal_hwmon_list_lock); return 0; unregister_input: device_remove_file(hwmon->device, &temp->temp_input.attr); - free_temp_mem: - kfree(temp); unregister_name: - if (new_hwmon_device) - hwmon_device_unregister(hwmon->device); + hwmon_device_unregister(hwmon->device); free_mem: kfree(hwmon); @@ -218,39 +163,37 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) } EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); +static struct thermal_hwmon_device * +thermal_hwmon_lookup(const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + + list_for_each_entry(hwmon, &thermal_hwmon_list, node) { + if (hwmon->tz_temp.tz == tz) + return hwmon; + } + return NULL; +} + void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; struct thermal_hwmon_temp *temp; - hwmon = thermal_hwmon_lookup_by_type(tz); - if (unlikely(!hwmon)) { - /* Should never happen... */ - dev_dbg(&tz->device, "hwmon device lookup failed!\n"); - return; + scoped_guard(mutex, &thermal_hwmon_list_lock) { + hwmon = thermal_hwmon_lookup(tz); + if (!hwmon) + return; + + list_del(&hwmon->node); } - temp = thermal_hwmon_lookup_temp(hwmon, tz); - if (unlikely(!temp)) { - /* Should never happen... */ - dev_dbg(&tz->device, "temperature input lookup failed!\n"); - return; - } + temp = &hwmon->tz_temp; device_remove_file(hwmon->device, &temp->temp_input.attr); if (temp->temp_crit_present) device_remove_file(hwmon->device, &temp->temp_crit.attr); - mutex_lock(&thermal_hwmon_list_lock); - list_del(&temp->hwmon_node); - kfree(temp); - if (!list_empty(&hwmon->tz_list)) { - mutex_unlock(&thermal_hwmon_list_lock); - return; - } - list_del(&hwmon->node); - mutex_unlock(&thermal_hwmon_list_lock); - hwmon_device_unregister(hwmon->device); kfree(hwmon); } From cfb5dc0f60fbe95a10cb307cc6dae5c47f160abb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 5 May 2026 13:47:01 +0200 Subject: [PATCH 09/54] thermal: hwmon: Use extra_groups for adding temperature attributes Instead of passing NULL as the last argument to __hwmon_device_register() in hwmon_device_register_for_thermal() and then adding each temperature sysfs attribute to the hwmon device via device_create_file(), redefine hwmon_device_register_for_thermal() to take an extra_groups argument that will be passed to __hwmon_device_register(), define an attribute group with a proper .is_visible() callback for the temperature attributes and a related attribute groups pointer, and pass the latter to hwmon_device_register_for_thermal(). This causes the code to be way more straightforward and closer to what the other users of the hwmon subsystem do. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/8704209.T7Z3S40VBb@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/hwmon/hwmon.c | 6 +- drivers/thermal/thermal_hwmon.c | 122 ++++++++++++-------------------- include/linux/hwmon.h | 3 +- 3 files changed, 51 insertions(+), 80 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 6812d1fd7c28..c16e6da0690c 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -1082,6 +1082,7 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); * @dev: the parent device * @name: hwmon name attribute * @drvdata: driver data to attach to created device + * @extra_groups: pointer to list of additional non-standard attribute groups * * The use of this function is restricted. It is provided for legacy reasons * and must only be called from the thermal subsystem. @@ -1093,12 +1094,13 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); */ struct device * hwmon_device_register_for_thermal(struct device *dev, const char *name, - void *drvdata) + void *drvdata, + const struct attribute_group **extra_groups) { if (!name || !dev) return ERR_PTR(-EINVAL); - return __hwmon_device_register(dev, name, drvdata, NULL, NULL); + return __hwmon_device_register(dev, name, drvdata, NULL, extra_groups); } EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, "HWMON_THERMAL"); diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index 223ae1571655..386dfb9f559e 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -25,25 +25,13 @@ */ #define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11) -struct thermal_hwmon_attr { - struct device_attribute attr; -}; - -/* one temperature input for each thermal zone */ -struct thermal_hwmon_temp { - struct thermal_zone_device *tz; - struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ - struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ - bool temp_crit_present; -}; - /* hwmon sys I/F */ /* thermal zone devices with the same type share one hwmon device */ struct thermal_hwmon_device { char name[THERMAL_HWMON_NAME_LENGTH]; struct device *device; struct list_head node; - struct thermal_hwmon_temp tz_temp; + struct thermal_zone_device *tz; }; static LIST_HEAD(thermal_hwmon_list); @@ -51,19 +39,14 @@ static LIST_HEAD(thermal_hwmon_list); static DEFINE_MUTEX(thermal_hwmon_list_lock); static ssize_t -temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) +temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); + struct thermal_zone_device *tz = hwmon->tz; int temperature; int ret; - struct thermal_hwmon_attr *hwmon_attr - = container_of(attr, struct thermal_hwmon_attr, attr); - struct thermal_hwmon_temp *temp - = container_of(hwmon_attr, struct thermal_hwmon_temp, - temp_input); - struct thermal_zone_device *tz = temp->tz; ret = thermal_zone_get_temp(tz, &temperature); - if (ret) return ret; @@ -71,14 +54,10 @@ temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) } static ssize_t -temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) +temp1_crit_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct thermal_hwmon_attr *hwmon_attr - = container_of(attr, struct thermal_hwmon_attr, attr); - struct thermal_hwmon_temp *temp - = container_of(hwmon_attr, struct thermal_hwmon_temp, - temp_crit); - struct thermal_zone_device *tz = temp->tz; + struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); + struct thermal_zone_device *tz = hwmon->tz; int temperature; int ret; @@ -91,22 +70,49 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) return sysfs_emit(buf, "%d\n", temperature); } -static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RO(temp1_crit); + +static struct attribute *thermal_hwmon_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_temp1_crit.attr, + NULL, +}; + +static umode_t thermal_hwmon_attr_is_visible(struct kobject *kobj, + struct attribute *a, int n) { - int temp; - return tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &temp); + if (a == &dev_attr_temp1_input.attr) + return a->mode; + + if (a == &dev_attr_temp1_crit.attr) { + struct thermal_hwmon_device *hwmon = dev_get_drvdata(kobj_to_dev(kobj)); + struct thermal_zone_device *tz = hwmon->tz; + int dummy; + + if (tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &dummy)) + return a->mode; + } + + return 0; } +static const struct attribute_group thermal_hwmon_group = { + .attrs = thermal_hwmon_attrs, + .is_visible = thermal_hwmon_attr_is_visible, +}; + +__ATTRIBUTE_GROUPS(thermal_hwmon); + int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; - struct thermal_hwmon_temp *temp; - int result; hwmon = kzalloc_obj(*hwmon); if (!hwmon) return -ENOMEM; + hwmon->tz = tz; /* * Append the thermal zone ID preceded by an underline character to the * type to disambiguate the sensors command output. @@ -114,35 +120,13 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id); strreplace(hwmon->name, '-', '_'); hwmon->device = hwmon_device_register_for_thermal(&tz->device, - hwmon->name, hwmon); + hwmon->name, hwmon, + thermal_hwmon_groups); if (IS_ERR(hwmon->device)) { - result = PTR_ERR(hwmon->device); - goto free_mem; - } + int result = PTR_ERR(hwmon->device); - temp = &hwmon->tz_temp; - - temp->tz = tz; - - temp->temp_input.attr.attr.name = "temp1_input"; - temp->temp_input.attr.attr.mode = 0444; - temp->temp_input.attr.show = temp_input_show; - sysfs_attr_init(&temp->temp_input.attr.attr); - result = device_create_file(hwmon->device, &temp->temp_input.attr); - if (result) - goto unregister_name; - - if (thermal_zone_crit_temp_valid(tz)) { - temp->temp_crit.attr.attr.name = "temp1_crit"; - temp->temp_crit.attr.attr.mode = 0444; - temp->temp_crit.attr.show = temp_crit_show; - sysfs_attr_init(&temp->temp_crit.attr.attr); - result = device_create_file(hwmon->device, - &temp->temp_crit.attr); - if (result) - goto unregister_input; - - temp->temp_crit_present = true; + kfree(hwmon); + return result; } /* The list is needed for hwmon lookup during removal. */ @@ -151,15 +135,6 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) mutex_unlock(&thermal_hwmon_list_lock); return 0; - - unregister_input: - device_remove_file(hwmon->device, &temp->temp_input.attr); - unregister_name: - hwmon_device_unregister(hwmon->device); - free_mem: - kfree(hwmon); - - return result; } EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); @@ -169,7 +144,7 @@ thermal_hwmon_lookup(const struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; list_for_each_entry(hwmon, &thermal_hwmon_list, node) { - if (hwmon->tz_temp.tz == tz) + if (hwmon->tz == tz) return hwmon; } return NULL; @@ -178,7 +153,6 @@ thermal_hwmon_lookup(const struct thermal_zone_device *tz) void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; - struct thermal_hwmon_temp *temp; scoped_guard(mutex, &thermal_hwmon_list_lock) { hwmon = thermal_hwmon_lookup(tz); @@ -188,12 +162,6 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) list_del(&hwmon->node); } - temp = &hwmon->tz_temp; - - device_remove_file(hwmon->device, &temp->temp_input.attr); - if (temp->temp_crit_present) - device_remove_file(hwmon->device, &temp->temp_crit.attr); - hwmon_device_unregister(hwmon->device); kfree(hwmon); } diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 301a83afbd66..a578a10baff2 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -477,7 +477,8 @@ hwmon_device_register_with_info(struct device *dev, const struct attribute_group **extra_groups); struct device * hwmon_device_register_for_thermal(struct device *dev, const char *name, - void *drvdata); + void *drvdata, + const struct attribute_group **extra_groups); struct device * devm_hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, From 13f4e660a1268dbc9c3fcba7fe214868c7c45062 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 5 May 2026 16:44:47 +0200 Subject: [PATCH 10/54] thermal/core: Split __thermal_cooling_device_register() into two functions In preparation for the upcoming changes separating OF and non-OF code, split __thermal_cooling_device_register() into allocation and addition phases. This allows moving the device node assignment out of the core initialization path. This change is not a trivial split. The lifetime of the cooling device is managed by the device core through put_device(), which triggers thermal_release() to free all associated resources. With the introduction of thermal_cooling_device_alloc(), the allocation path must mirror what thermal_release() undoes. In contrast, thermal_cooling_device_add() must not perform any rollback and relies on put_device() for cleanup on error paths. This avoids both double free and resource leaks. As part of this rework, add the missing device_initialize() call when allocating the cooling device. Suggested-by: Rafael J. Wysocki Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba [ rjw: Replace device_register() with device_add() ] [ rjw: Rebase on top of previously applied material ] Link: https://patch.msgid.link/20260505144447.2853933-1-daniel.lezcano@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 169 +++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 70 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index ca3b6bf2f292..4e2a17fdb6a7 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -963,6 +963,101 @@ static void thermal_cdev_release(struct device *dev) kfree(cdev); } +static struct thermal_cooling_device * +thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + if (!ops || !ops->get_max_state || !ops->get_cur_state || + !ops->set_cur_state) + return ERR_PTR(-EINVAL); + + if (!thermal_class) + return ERR_PTR(-ENODEV); + + cdev = kzalloc_obj(*cdev); + if (!cdev) + return ERR_PTR(-ENOMEM); + + cdev->ops = ops; + + ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL); + if (ret < 0) + goto out_kfree_cdev; + cdev->id = ret; + + cdev->type = kstrdup_const(type ? type : "", GFP_KERNEL); + if (!cdev->type) { + ret = -ENOMEM; + goto out_ida_remove; + } + + return cdev; + +out_ida_remove: + ida_free(&thermal_cdev_ida, cdev->id); +out_kfree_cdev: + kfree(cdev); + return ERR_PTR(ret); +} + +static int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata) +{ + unsigned long current_state; + int ret; + + mutex_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->thermal_instances); + cdev->updated = false; + cdev->device.class = thermal_class; + cdev->device.release = thermal_cdev_release; + device_initialize(&cdev->device); + cdev->devdata = devdata; + + thermal_cooling_device_setup_sysfs(cdev); + + ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id); + if (ret) + goto out_put_device; + + ret = cdev->ops->get_max_state(cdev, &cdev->max_state); + if (ret) + goto out_put_device; + + /* + * The cooling device's current state is only needed for debug + * initialization below, so a failure to get it does not cause + * the entire cooling device initialization to fail. However, + * the debug will not work for the device if its initial state + * cannot be determined and drivers are responsible for ensuring + * that this will not happen. + */ + ret = cdev->ops->get_cur_state(cdev, ¤t_state); + if (ret) + current_state = ULONG_MAX; + + ret = device_add(&cdev->device); + if (ret) + goto out_put_device; + + if (current_state <= cdev->max_state) + thermal_debug_cdev_add(cdev, current_state); + + thermal_cooling_device_init_complete(cdev); + + return 0; + +out_put_device: + /* + * The device core will release the memory via + * thermal_release() after put_device() is called in the error + * path + */ + put_device(&cdev->device); + return ret; +} + /** * __thermal_cooling_device_register() - register a new thermal cooling device * @np: a pointer to a device tree node. @@ -985,85 +1080,19 @@ __thermal_cooling_device_register(struct device_node *np, const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; - unsigned long current_state; int ret; - if (!ops || !ops->get_max_state || !ops->get_cur_state || - !ops->set_cur_state) - return ERR_PTR(-EINVAL); + cdev = thermal_cooling_device_alloc(type, ops); + if (IS_ERR(cdev)) + return cdev; - if (!thermal_class) - return ERR_PTR(-ENODEV); - - cdev = kzalloc_obj(*cdev); - if (!cdev) - return ERR_PTR(-ENOMEM); - - ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL); - if (ret < 0) - goto out_kfree_cdev; - cdev->id = ret; - - cdev->type = kstrdup_const(type ? type : "", GFP_KERNEL); - if (!cdev->type) { - ret = -ENOMEM; - goto out_ida_remove; - } - - mutex_init(&cdev->lock); - INIT_LIST_HEAD(&cdev->thermal_instances); cdev->np = np; - cdev->ops = ops; - cdev->updated = false; - cdev->device.class = thermal_class; - cdev->device.release = thermal_cdev_release; - cdev->devdata = devdata; - ret = cdev->ops->get_max_state(cdev, &cdev->max_state); + ret = thermal_cooling_device_add(cdev, devdata); if (ret) - goto out_cdev_type; - - /* - * The cooling device's current state is only needed for debug - * initialization below, so a failure to get it does not cause - * the entire cooling device initialization to fail. However, - * the debug will not work for the device if its initial state - * cannot be determined and drivers are responsible for ensuring - * that this will not happen. - */ - ret = cdev->ops->get_cur_state(cdev, ¤t_state); - if (ret) - current_state = ULONG_MAX; - - thermal_cooling_device_setup_sysfs(cdev); - - ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id); - if (ret) - goto out_cooling_dev; - - ret = device_register(&cdev->device); - if (ret) { - /* thermal_release() handles rest of the cleanup */ - put_device(&cdev->device); return ERR_PTR(ret); - } - - if (current_state <= cdev->max_state) - thermal_debug_cdev_add(cdev, current_state); - - thermal_cooling_device_init_complete(cdev); return cdev; - -out_cooling_dev: - thermal_cooling_device_destroy_sysfs(cdev); -out_cdev_type: - kfree_const(cdev->type); -out_ida_remove: - ida_free(&thermal_cdev_ida, cdev->id); -out_kfree_cdev: - kfree(cdev); - return ERR_PTR(ret); } /** From f5d4d76ec1390d490c14848d787ef27bf7b9c0a8 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 20 May 2026 16:58:34 +0000 Subject: [PATCH 11/54] thermal/core: Populate max_state before setting up cooling dev sysfs Since commit 13f4e660a126 ("thermal/core: Split __thermal_cooling_device_register() into two functions") thermal_cooling_device_setup_sysfs() is called before the ->get_max_state() callback in thermal_cooling_device_add(). However, cooling_device_stats_setup() allocates space based on cdev->max_state, which is not initialized at that point. With CONFIG_THERMAL_STATISTICS=y, an out of bounds access happens inside thermal_cooling_device_stats_update(), followed by a kernel crash: Unable to handle kernel paging request at virtual address ffff800081329e60 Call trace: queued_spin_lock_slowpath+0x1cc/0x320 (P) thermal_cooling_device_stats_update+0x28/0xa4 __thermal_cdev_update+0x74/0x88 thermal_cdev_update+0x44/0x58 step_wise_manage+0x1b8/0x300 __thermal_zone_device_update+0x270/0x414 thermal_zone_device_check+0x28/0x40 process_one_work+0x150/0x290 worker_thread+0x18c/0x300 kthread+0x114/0x120 ret_from_fork+0x10/0x20 To fix this, restore the original ordering of ->get_max_state() and thermal_cooling_device_setup_sysfs(). Note that with this reordering, the dev_set_name() and ->get_max_state() error paths now reach thermal_cdev_release() without setup_sysfs() having run. This is safe because cdev->stats is NULL in that case and destroy_sysfs() is a no-op. Fixes: 13f4e660a126 ("thermal/core: Split __thermal_cooling_device_register() into two functions") Signed-off-by: Ovidiu Panait Reviewed-by: Daniel Lezcano Link: https://patch.msgid.link/20260520165835.90974-1-ovidiu.panait.rb@renesas.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 4e2a17fdb6a7..9b9fa51067bd 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1015,8 +1015,6 @@ static int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void device_initialize(&cdev->device); cdev->devdata = devdata; - thermal_cooling_device_setup_sysfs(cdev); - ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id); if (ret) goto out_put_device; @@ -1037,6 +1035,8 @@ static int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void if (ret) current_state = ULONG_MAX; + thermal_cooling_device_setup_sysfs(cdev); + ret = device_add(&cdev->device); if (ret) goto out_put_device; From e21c3e4c88a20a6329c5768b4c6fa810653d311a Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 15 May 2026 10:49:20 +0200 Subject: [PATCH 12/54] thermal: core: Add WQ_UNBOUND to alloc_workqueue() users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The refactoring is going to alter the default behavior of alloc_workqueue() to be unbound by default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. For more details see the Link tag below. This workqueue has no benefits being per-CPU, so make it unbound adding WQ_UNBOUND, removing also the WQ_POWER_EFFICIENT flag. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260515084920.70544-1-marco.crivellari@suse.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 9b9fa51067bd..7d7ce855ae88 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1855,7 +1855,7 @@ static int __init thermal_init(void) if (result) goto error; - thermal_wq = alloc_workqueue("thermal_events", WQ_POWER_EFFICIENT, 0); + thermal_wq = alloc_workqueue("thermal_events", WQ_UNBOUND, 0); if (!thermal_wq) { result = -ENOMEM; goto unregister_netlink; From 21c315342b81526874acfa311f11b3f72bed4e14 Mon Sep 17 00:00:00 2001 From: Mayur Kumar Date: Mon, 11 May 2026 23:22:46 +0530 Subject: [PATCH 13/54] thermal: sysfs: remove space before tab in macro Adjust white space in thermal_trip_of_attr(). Signed-off-by: Mayur Kumar [ rjw: Added changelog, added tabs before backslash ] Link: https://patch.msgid.link/20260511175246.217788-1-kmayur809@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index 5eecae13f07d..9f2f25a6da37 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -82,7 +82,7 @@ mode_store(struct device *dev, struct device_attribute *attr, } #define thermal_trip_of_attr(_ptr_, _attr_) \ - ({ \ + ({ \ struct thermal_trip_desc *td; \ \ td = container_of(_ptr_, struct thermal_trip_desc, \ From ba8a4e072a3a6b9affdb3d231975f3a42252a6fa Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Mon, 4 May 2026 16:12:36 +0800 Subject: [PATCH 14/54] dt-bindings: thermal: qcom-tsens: Document Nord Temperature Sensor Document Temperature Sensor (TSENS) on Qualcomm Nord SoC. Signed-off-by: Deepti Jaggi Signed-off-by: Shawn Guo Signed-off-by: Daniel Lezcano Reviewed-by: Pankaj Patil Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260504081236.825755-1-shengchao.guo@oss.qualcomm.com --- Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml index 7d34ba00e684..e65ebc6f1698 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -58,6 +58,7 @@ properties: - qcom,glymur-tsens - qcom,kaanapali-tsens - qcom,milos-tsens + - qcom,nord-tsens - qcom,msm8953-tsens - qcom,msm8996-tsens - qcom,msm8998-tsens From 4ab6e05dc2f2b4ea3d8ffc0e0edfb8a5bcd2957f Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 24 Apr 2026 18:00:18 +0200 Subject: [PATCH 15/54] thermal/drivers/tegra/soctherm: Use devm_add_action_or_reset() for clock disable Replace the manual error handling paths disabling the clocks with devm_add_action_or_reset(). This ensures the clocks are properly disabled on probe failure and driver removal, while simplifying the code by removing the explicit error paths. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260424160019.41710-1-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/tegra/soctherm.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index 5d26b52beaba..40c3715e84c5 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -1499,6 +1499,13 @@ static int soctherm_clk_enable(struct platform_device *pdev, bool enable) return 0; } +static void soctherm_clk_disable(void *data) +{ + struct platform_device *pdev = data; + + soctherm_clk_enable(pdev, false); +} + static int throt_get_cdev_max_state(struct thermal_cooling_device *cdev, unsigned long *max_state) { @@ -2175,6 +2182,10 @@ static int tegra_soctherm_probe(struct platform_device *pdev) if (err) return err; + err = devm_add_action_or_reset(&pdev->dev, soctherm_clk_disable, pdev); + if (err) + return err; + soctherm_thermtrips_parse(pdev); soctherm_init_hw_throt_cdev(pdev); @@ -2184,10 +2195,8 @@ static int tegra_soctherm_probe(struct platform_device *pdev) for (i = 0; i < soc->num_ttgs; ++i) { struct tegra_thermctl_zone *zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); - if (!zone) { - err = -ENOMEM; - goto disable_clocks; - } + if (!zone) + return -ENOMEM; zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset; zone->dev = &pdev->dev; @@ -2201,7 +2210,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev) err = PTR_ERR(z); dev_err(&pdev->dev, "failed to register sensor: %d\n", err); - goto disable_clocks; + return err; } zone->tz = z; @@ -2210,7 +2219,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev) /* Configure hw trip points */ err = tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); if (err) - goto disable_clocks; + return err; } err = soctherm_interrupts_init(pdev, tegra); @@ -2218,11 +2227,6 @@ static int tegra_soctherm_probe(struct platform_device *pdev) soctherm_debug_init(pdev); return 0; - -disable_clocks: - soctherm_clk_enable(pdev, false); - - return err; } static void tegra_soctherm_remove(struct platform_device *pdev) @@ -2230,8 +2234,6 @@ static void tegra_soctherm_remove(struct platform_device *pdev) struct tegra_soctherm *tegra = platform_get_drvdata(pdev); debugfs_remove_recursive(tegra->debugfs_dir); - - soctherm_clk_enable(pdev, false); } static int __maybe_unused soctherm_suspend(struct device *dev) From ee126267bc04bfb03816ae9d71ca24c5bf99e739 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 24 Apr 2026 18:00:19 +0200 Subject: [PATCH 16/54] thermal/drivers/tegra/soctherma: Switch to devm cooling device registration Use devm_thermal_of_cooling_device_register() to simplify resource management and avoid manual cleanup in error paths. As a side effect this change has the benefit of solving an existing issue. Before, the function tegra_soctherm_remove() only called debugfs_remove_recursive() and never called thermal_cooling_device_unregister() for any of the cooling devices registered here. After the driver removal, the thermal framework's cdev list would still hold references to thermal_cooling_device objects whose devdata pointer (ts) pointed to memory already freed by the platform device's devm cleanup. With this change, the cooling device is unregistered when the driver is removed, thus fixing the issue above. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260424160019.41710-2-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/tegra/soctherm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index 40c3715e84c5..6a56638c98f1 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -1707,9 +1707,9 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) stc->init = true; } else { - tcd = thermal_of_cooling_device_register(np_stcc, - (char *)name, ts, - &throt_cooling_ops); + tcd = devm_thermal_of_cooling_device_register(dev, np_stcc, + (char *)name, ts, + &throt_cooling_ops); if (IS_ERR_OR_NULL(tcd)) { dev_err(dev, "throttle-cfg: %s: failed to register cooling device\n", From 31da1d4451c88b63779bc3f855ae15f718c0e539 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Wed, 29 Apr 2026 18:14:14 +0200 Subject: [PATCH 17/54] thermal/core: Use devm_add_action_or_reset() when registering a cooling device Use devm_add_action_or_reset() which does the replaced code. It results in a simpler and more concise code. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260429161430.3802970-2-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/thermal_core.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 7d7ce855ae88..db01361569d7 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1140,10 +1140,11 @@ thermal_of_cooling_device_register(struct device_node *np, } EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); -static void thermal_cooling_device_release(struct device *dev, void *res) +static void thermal_cooling_device_release(void *data) { - thermal_cooling_device_unregister( - *(struct thermal_cooling_device **)res); + struct thermal_cooling_device *cdev = data; + + thermal_cooling_device_unregister(cdev); } /** @@ -1169,23 +1170,18 @@ devm_thermal_of_cooling_device_register(struct device *dev, const char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { - struct thermal_cooling_device **ptr, *tcd; + struct thermal_cooling_device *cdev; + int ret; - ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr), - GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); + cdev = __thermal_cooling_device_register(np, type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; - tcd = __thermal_cooling_device_register(np, type, devdata, ops); - if (IS_ERR(tcd)) { - devres_free(ptr); - return tcd; - } + ret = devm_add_action_or_reset(dev, thermal_cooling_device_release, cdev); + if (ret) + return ERR_PTR(ret); - *ptr = tcd; - devres_add(dev, ptr); - - return tcd; + return cdev; } EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); From b1c8ccdbd4e92db05548bd3ad2a866bacec07490 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Fri, 24 Apr 2026 17:45:09 +0200 Subject: [PATCH 18/54] dt-bindings: thermal: amlogic: Add support for T7 Add the amlogic,t7-thermal compatible for the Amlogic T7 thermal sensor. Unlike existing variants which use a phandle to the ao-secure syscon, the T7 relies on a secure monitor interface described by a phandle and a sensor index argument. The T7 integrates multiple thermal sensors, all accessed through the same SMC call. The sensor index argument is required to identify which sensor's calibration data the secure monitor should return, as a single SM_THERMAL_CALIB_READ command serves all of them. Introduce the amlogic,secure-monitor property as a phandle-array and make amlogic,ao-secure or amlogic,secure-monitor conditionally required depending on the compatible. Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Acked-by: Conor Dooley Link: https://patch.msgid.link/20260424-add-thermal-t7-vim4-v5-1-9040ca36afe2@aliel.fr --- .../bindings/thermal/amlogic,thermal.yaml | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml index 70b273271754..e28612510d67 100644 --- a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml +++ b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml @@ -21,7 +21,9 @@ properties: - amlogic,g12a-cpu-thermal - amlogic,g12a-ddr-thermal - const: amlogic,g12a-thermal - - const: amlogic,a1-cpu-thermal + - enum: + - amlogic,a1-cpu-thermal + - amlogic,t7-thermal reg: maxItems: 1 @@ -42,12 +44,34 @@ properties: '#thermal-sensor-cells': const: 0 + amlogic,secure-monitor: + description: phandle to the secure monitor + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to the secure monitor + - description: sensor index to get specific calibration data + required: - compatible - reg - interrupts - clocks - - amlogic,ao-secure + +allOf: + - if: + properties: + compatible: + contains: + enum: + - amlogic,a1-cpu-thermal + - amlogic,g12a-thermal + then: + required: + - amlogic,ao-secure + else: + required: + - amlogic,secure-monitor unevaluatedProperties: false @@ -62,4 +86,13 @@ examples: #thermal-sensor-cells = <0>; amlogic,ao-secure = <&sec_AO>; }; + - | + temperature-sensor@20000 { + compatible = "amlogic,t7-thermal"; + reg = <0x0 0x20000 0x0 0x50>; + interrupts = ; + clocks = <&clkc_periphs CLKID_TS>; + #thermal-sensor-cells = <0>; + amlogic,secure-monitor = <&sm 1>; + }; ... From d0bf38194bc908468e06c0ae7fba7281046c12d8 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Fri, 24 Apr 2026 17:45:10 +0200 Subject: [PATCH 19/54] firmware: meson: sm: Thermal calibration read via secure monitor Add SM_THERMAL_CALIB_READ to the secure monitor command enum and introduce meson_sm_get_thermal_calib() to allow drivers to retrieve thermal sensor calibration data through the firmware interface. Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260424-add-thermal-t7-vim4-v5-2-9040ca36afe2@aliel.fr --- include/linux/firmware/meson/meson_sm.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h index 8eaf8922ab02..3ebc2bd9a976 100644 --- a/include/linux/firmware/meson/meson_sm.h +++ b/include/linux/firmware/meson/meson_sm.h @@ -12,6 +12,7 @@ enum { SM_EFUSE_WRITE, SM_EFUSE_USER_MAX, SM_GET_CHIP_ID, + SM_THERMAL_CALIB_READ, SM_A1_PWRC_SET, SM_A1_PWRC_GET, }; @@ -27,5 +28,7 @@ int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, unsigned int bsize, unsigned int cmd_index, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node); +int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, + u32 tsensor_id); #endif /* _MESON_SM_FW_H_ */ From 1c5cb7391ef5f869ef333c04d6422c0e0859a870 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Fri, 24 Apr 2026 17:45:11 +0200 Subject: [PATCH 20/54] firmware: meson: sm: Add thermal calibration SMC call Add SM_THERMAL_CALIB_READ at SMC ID 0x82000047 in the command table and implement meson_sm_get_thermal_calib(), which forwards the tsensor_id argument to the secure monitor and returns the calibration data. Also realign the CMD() column to improve readability. Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano [ dlezcano: Fixed kernel-doc format warning ] Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260424-add-thermal-t7-vim4-v5-3-9040ca36afe2@aliel.fr --- drivers/firmware/meson/meson_sm.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c index 3ab67aaa9e5d..ab9751a59b55 100644 --- a/drivers/firmware/meson/meson_sm.c +++ b/drivers/firmware/meson/meson_sm.c @@ -41,12 +41,13 @@ static const struct meson_sm_chip gxbb_chip = { .cmd_shmem_in_base = 0x82000020, .cmd_shmem_out_base = 0x82000021, .cmd = { - CMD(SM_EFUSE_READ, 0x82000030), - CMD(SM_EFUSE_WRITE, 0x82000031), + CMD(SM_EFUSE_READ, 0x82000030), + CMD(SM_EFUSE_WRITE, 0x82000031), CMD(SM_EFUSE_USER_MAX, 0x82000033), - CMD(SM_GET_CHIP_ID, 0x82000044), - CMD(SM_A1_PWRC_SET, 0x82000093), - CMD(SM_A1_PWRC_GET, 0x82000095), + CMD(SM_GET_CHIP_ID, 0x82000044), + CMD(SM_THERMAL_CALIB_READ, 0x82000047), + CMD(SM_A1_PWRC_SET, 0x82000093), + CMD(SM_A1_PWRC_GET, 0x82000095), { /* sentinel */ }, }, }; @@ -245,6 +246,23 @@ struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node) } EXPORT_SYMBOL_GPL(meson_sm_get); +/** + * meson_sm_get_thermal_calib - Read thermal sensor calibration data. + * @fw: Pointer to secure-monitor firmware. + * @trim_info: Pointer to store the returned calibration data. + * @tsensor_id: Sensor index to identify which sensor's calibration data + * to retrieve + * + * Return: 0 on success, negative error code on failure. + */ +int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, + u32 tsensor_id) +{ + return meson_sm_call(fw, SM_THERMAL_CALIB_READ, trim_info, tsensor_id, + 0, 0, 0, 0); +} +EXPORT_SYMBOL_GPL(meson_sm_get_thermal_calib); + #define SM_CHIP_ID_LENGTH 119 #define SM_CHIP_ID_OFFSET 4 #define SM_CHIP_ID_SIZE 12 From 1fac72873549bbdd6292014d9676bcf1c39cf285 Mon Sep 17 00:00:00 2001 From: Aravind Anilraj Date: Sun, 29 Mar 2026 03:06:41 -0400 Subject: [PATCH 21/54] thermal: intel: int340x: Fix potential shift overflow in ptc_mmio_write() The value parameter is u32 but is shifted into a u64 register value without casting first. If the shift amount pushes bits beyond 32, they are lost. Cast value to u64 before shifting to ensure all bits are preserved. Signed-off-by: Aravind Anilraj Link: https://patch.msgid.link/20260329070642.10721-2-aravindanilraj0702@gmail.com Signed-off-by: Rafael J. Wysocki --- .../intel/int340x_thermal/platform_temperature_control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c index 0ccc72c93499..18ac5014d8dc 100644 --- a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c +++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c @@ -138,7 +138,7 @@ static void ptc_mmio_write(struct pci_dev *pdev, u32 offset, int index, u32 valu reg_val = readq((void __iomem *) (proc_priv->mmio_base + offset)); reg_val &= ~mask; - reg_val |= (value << ptc_mmio_regs[index].shift); + reg_val |= ((u64)value << ptc_mmio_regs[index].shift); writeq(reg_val, (void __iomem *) (proc_priv->mmio_base + offset)); } From 8aa807a89dccdff4bcfc0cfc459b1ba714badbb6 Mon Sep 17 00:00:00 2001 From: Aravind Anilraj Date: Sun, 29 Mar 2026 03:06:42 -0400 Subject: [PATCH 22/54] thermal: intel: int340x: Check return value of ptc_create_groups() proc_thermal_ptc_add() ignores the return value of ptc_create_groups() causing the driver to silenty continue even if sysfs group creation fails. The thermal control interface would be unavailable with no indication of failure. Check the return value and on failure clean up any sysfs groups that were successfully created before the error, then propagate the error to the caller which already handles it correctly via goto err_rem_rapl. Signed-off-by: Aravind Anilraj Link: https://patch.msgid.link/20260329070642.10721-3-aravindanilraj0702@gmail.com Signed-off-by: Rafael J. Wysocki --- .../int340x_thermal/platform_temperature_control.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c index 18ac5014d8dc..d92a6f84a778 100644 --- a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c +++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c @@ -278,12 +278,18 @@ static void ptc_delete_debugfs(void) int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) { if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) { - int i; + int i, ret; for (i = 0; i < PTC_MAX_INSTANCES; i++) { ptc_instance[i].offset = ptc_offsets[i]; ptc_instance[i].pdev = pdev; - ptc_create_groups(pdev, i, &ptc_instance[i]); + ret = ptc_create_groups(pdev, i, &ptc_instance[i]); + if (ret) { + while (i--) + sysfs_remove_group(&pdev->dev.kobj, + &ptc_instance[i].ptc_attr_group); + return ret; + } } ptc_create_debugfs(); From 7244cbbdb8b55879a3bf41c0f1a7339d22b3032a Mon Sep 17 00:00:00 2001 From: Yury Norov Date: Thu, 28 May 2026 14:36:22 -0400 Subject: [PATCH 23/54] thermal: intel: Use sysfs_emit() for powerclamp cpumask cpumask_get() is used as a sysfs getter for the cpumask module parameter. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov Link: https://patch.msgid.link/20260528183625.870813-16-ynorov@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index ccf380da12f2..bd7fd98dc310 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -200,7 +200,7 @@ static int cpumask_get(char *buf, const struct kernel_param *kp) if (!cpumask_available(idle_injection_cpu_mask)) return -ENODEV; - return cpumap_print_to_pagebuf(false, buf, idle_injection_cpu_mask); + return sysfs_emit(buf, "%*pb\n", cpumask_pr_args(idle_injection_cpu_mask)); } static const struct kernel_param_ops cpumask_ops = { From 5f1e4f65d3002a814c690434f1dd37facc1df6ca Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Tue, 2 Jun 2026 16:19:20 +0200 Subject: [PATCH 24/54] thermal/drivers/amlogic: Add missing dependency on MESON_SM The amlogic thermal driver calls meson_sm_get() and meson_sm_get_thermal_calib() which are exported by the meson_sm driver. Without CONFIG_MESON_SM enabled, the build fails with undefined references to these symbols. Add a proper Kconfig dependency on MESON_SM instead of relying on stub functions, which makes the dependency explicit and prevents invalid configurations. Closes: https://lore.kernel.org/oe-kbuild-all/202605291530.en7aGn7w-lkp@intel.com/ Reported-by: Mark Brown Reported-by: kernel test robot Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/oe-kbuild-all/202605291530.en7aGn7w-lkp@intel.com/ Link: https://patch.msgid.link/20260602-fix-missing-meson_sm-symbol-v3-1-6f7f69cd7d6c@aliel.fr --- drivers/thermal/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index b10080d61860..40a376cf9936 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -436,6 +436,7 @@ config AMLOGIC_THERMAL tristate "Amlogic Thermal Support" default ARCH_MESON depends on OF && ARCH_MESON + depends on MESON_SM help If you say yes here you get support for Amlogic Thermal for G12 SoC Family. From 18d65de8157c93666b2d42f1d46330ee4cb030b7 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Fri, 24 Apr 2026 17:45:12 +0200 Subject: [PATCH 25/54] thermal/drivers/amlogic: Add support for secure monitor calibration readout Some SoCs (e.g. T7) expose thermal calibration data through the secure monitor rather than a directly accessible eFuse register. Add a use_sm flag to amlogic_thermal_data to select this path, and retrieve the firmware handle and tsensor_id from the "amlogic,secure-monitor" DT phandle with one fixed argument. Also introduce the amlogic,t7-thermal compatible using this new path. While refactoring, fix a pre-existing bug where amlogic_thermal_initialize() was called after devm_thermal_of_zone_register(), causing the thermal framework to read an uninitialized trim_info on zone registration. Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260424-add-thermal-t7-vim4-v5-4-9040ca36afe2@aliel.fr --- drivers/thermal/amlogic_thermal.c | 112 ++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 30 deletions(-) diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c index 5448d772db12..a0b530624b60 100644 --- a/drivers/thermal/amlogic_thermal.c +++ b/drivers/thermal/amlogic_thermal.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "thermal_hwmon.h" @@ -84,12 +85,14 @@ struct amlogic_thermal_soc_calib_data { * @u_efuse_off: register offset to read fused calibration value * @calibration_parameters: calibration parameters structure pointer * @regmap_config: regmap config for the device + * @use_sm: read data from secure monitor instead of efuse * This structure is required for configuration of amlogic thermal driver. */ struct amlogic_thermal_data { int u_efuse_off; const struct amlogic_thermal_soc_calib_data *calibration_parameters; const struct regmap_config *regmap_config; + bool use_sm; }; struct amlogic_thermal { @@ -100,6 +103,8 @@ struct amlogic_thermal { struct clk *clk; struct thermal_zone_device *tzd; u32 trim_info; + struct meson_sm_firmware *sm_fw; + u32 tsensor_id; }; /* @@ -133,26 +138,6 @@ static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata, return temp; } -static int amlogic_thermal_initialize(struct amlogic_thermal *pdata) -{ - int ret = 0; - int ver; - - regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off, - &pdata->trim_info); - - ver = TSENSOR_TRIM_VERSION(pdata->trim_info); - - if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) { - ret = -EINVAL; - dev_err(&pdata->pdev->dev, - "tsensor thermal calibration not supported: 0x%x!\n", - ver); - } - - return ret; -} - static int amlogic_thermal_enable(struct amlogic_thermal *data) { int ret; @@ -190,6 +175,67 @@ static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } +static int amlogic_thermal_probe_sm(struct platform_device *pdev, + struct amlogic_thermal *pdata) +{ + struct device *dev = &pdev->dev; + struct of_phandle_args ph_args; + int ret; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "amlogic,secure-monitor", + 1, 0, &ph_args); + if (ret) + return ret; + + if (!ph_args.np) { + dev_err(dev, "Failed to parse secure monitor phandle\n"); + return -ENODEV; + } + + pdata->sm_fw = meson_sm_get(ph_args.np); + of_node_put(ph_args.np); + if (!pdata->sm_fw) { + dev_err(dev, "Failed to get secure monitor firmware\n"); + return -EPROBE_DEFER; + } + + pdata->tsensor_id = ph_args.args[0]; + + return meson_sm_get_thermal_calib(pdata->sm_fw, + &pdata->trim_info, + pdata->tsensor_id); +} + +static int amlogic_thermal_probe_syscon(struct platform_device *pdev, + struct amlogic_thermal *pdata) +{ + struct device *dev = &pdev->dev; + int ver; + + pdata->sec_ao_map = + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "amlogic,ao-secure"); + if (IS_ERR(pdata->sec_ao_map)) { + dev_err(dev, "syscon regmap lookup failed.\n"); + return PTR_ERR(pdata->sec_ao_map); + } + + regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off, + &pdata->trim_info); + + ver = TSENSOR_TRIM_VERSION(pdata->trim_info); + + if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) { + dev_err(&pdata->pdev->dev, + "tsensor thermal calibration not supported: 0x%x!\n", + ver); + return -EINVAL; + } + + return 0; +} + static const struct thermal_zone_device_ops amlogic_thermal_ops = { .get_temp = amlogic_thermal_get_temp, }; @@ -226,6 +272,12 @@ static const struct amlogic_thermal_data amlogic_thermal_a1_cpu_param = { .regmap_config = &amlogic_thermal_regmap_config_g12a, }; +static const struct amlogic_thermal_data amlogic_thermal_t7_param = { + .use_sm = true, + .calibration_parameters = &amlogic_thermal_g12a, + .regmap_config = &amlogic_thermal_regmap_config_g12a, +}; + static const struct of_device_id of_amlogic_thermal_match[] = { { .compatible = "amlogic,g12a-ddr-thermal", @@ -239,6 +291,10 @@ static const struct of_device_id of_amlogic_thermal_match[] = { .compatible = "amlogic,a1-cpu-thermal", .data = &amlogic_thermal_a1_cpu_param, }, + { + .compatible = "amlogic,t7-thermal", + .data = &amlogic_thermal_t7_param, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match); @@ -271,12 +327,12 @@ static int amlogic_thermal_probe(struct platform_device *pdev) if (IS_ERR(pdata->clk)) return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n"); - pdata->sec_ao_map = syscon_regmap_lookup_by_phandle - (pdev->dev.of_node, "amlogic,ao-secure"); - if (IS_ERR(pdata->sec_ao_map)) { - dev_err(dev, "syscon regmap lookup failed.\n"); - return PTR_ERR(pdata->sec_ao_map); - } + if (pdata->data->use_sm) + ret = amlogic_thermal_probe_sm(pdev, pdata); + else + ret = amlogic_thermal_probe_syscon(pdev, pdata); + if (ret) + return ret; pdata->tzd = devm_thermal_of_zone_register(&pdev->dev, 0, @@ -290,10 +346,6 @@ static int amlogic_thermal_probe(struct platform_device *pdev) devm_thermal_add_hwmon_sysfs(&pdev->dev, pdata->tzd); - ret = amlogic_thermal_initialize(pdata); - if (ret) - return ret; - ret = amlogic_thermal_enable(pdata); return ret; From 4bb840492cedf89d5bed72ede6e0f8e848141977 Mon Sep 17 00:00:00 2001 From: Jinseok Kim Date: Sun, 17 May 2026 00:23:16 +0900 Subject: [PATCH 26/54] thermal/drivers/qcom: Fix typo in comment Fix a typo in the struct tsens_irq_data comment. Replace "uppper" with "upper". Signed-off-by: Jinseok Kim Signed-off-by: Daniel Lezcano Acked-by: Amit Kucheria Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260516152324.1863-1-always.starving0@gmail.com --- drivers/thermal/qcom/tsens.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index a2422ebee816..5e19c7588e03 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -27,7 +27,7 @@ * @up_viol: upper threshold violated * @up_thresh: upper threshold temperature value * @up_irq_mask: mask register for upper threshold irqs - * @up_irq_clear: clear register for uppper threshold irqs + * @up_irq_clear: clear register for upper threshold irqs * @low_viol: lower threshold violated * @low_thresh: lower threshold temperature value * @low_irq_mask: mask register for lower threshold irqs From fc4ad576cb836b0538d214b789adf7e89287fb78 Mon Sep 17 00:00:00 2001 From: Gaurav Kohli Date: Wed, 13 May 2026 13:00:58 +0530 Subject: [PATCH 27/54] dt-bindings: thermal: qcom-tsens: Document the Shikra Temperature Sensor Document the Temperature Sensor (TSENS) on the Shikra SoC. Signed-off-by: Gaurav Kohli Signed-off-by: Daniel Lezcano Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260513-tsens_binding-v1-1-1780c6a6caf2@oss.qualcomm.com --- Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml index e65ebc6f1698..fd6a67ac28f6 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -75,6 +75,7 @@ properties: - qcom,sdm630-tsens - qcom,sdm670-tsens - qcom,sdm845-tsens + - qcom,shikra-tsens - qcom,sm6115-tsens - qcom,sm6350-tsens - qcom,sm6375-tsens From dc10bb3cd2e9b44dbb7602e4f5bb96cdd9d62e25 Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Tue, 12 May 2026 16:43:36 +0530 Subject: [PATCH 28/54] dt-bindings: thermal: qcom-tsens: Document the Hawi Temperature Sensor Document the Temperature Sensor (TSENS) on the Qualcomm Hawi SoC. Signed-off-by: Dipa Ramesh Mantre Signed-off-by: Daniel Lezcano Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260512-dtbinding-hawi-v1-1-96149d06cccf@oss.qualcomm.com --- Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml index fd6a67ac28f6..f0efaa8349ee 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -56,6 +56,7 @@ properties: - enum: - qcom,eliza-tsens - qcom,glymur-tsens + - qcom,hawi-tsens - qcom,kaanapali-tsens - qcom,milos-tsens - qcom,nord-tsens From e28ef2f3ccea276436bd0f30c93f99e764ba492b Mon Sep 17 00:00:00 2001 From: Priyansh Jain Date: Thu, 14 May 2026 17:06:43 +0530 Subject: [PATCH 29/54] thermal/drivers/qcom/tsens: Atomic temperature read with hardware-guided retries The existing TSENS temperature read logic polls the valid bit and then reads the temperature register. When temperature reads are triggered at very short intervals, this can race with hardware updates and allow the temperature field to be read while it is still being updated. In this case, the valid bit may already be asserted even though the temperature value is transitioning, resulting in an incorrect reading. Hardware programming guidelines require the temperature value and the valid bit to be sampled atomically in the same read transaction. A reading is considered valid only if the valid bit is observed set in that same sample. The guidelines further specify that software should attempt the temperature read up to three times to account for transient update windows. If none of the attempts yields a valid sample, a stable fallback value must be returned: if the first and second samples match, the second value is returned;otherwise, if the second and third samples match, the third value is returned;if neither pair matches, -EAGAIN is returned. Update the TSENS sensor read logic to implement atomic sampling along with the recommended retry-and-compare fallback behavior. This removes the race window and ensures deterministic temperature values in accordance with hardware requirements. Signed-off-by: Priyansh Jain Signed-off-by: Daniel Lezcano Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260514113643.1954111-1-priyansh.jain@oss.qualcomm.com --- drivers/thermal/qcom/tsens.c | 111 ++++++++++++++++++++++++----------- drivers/thermal/qcom/tsens.h | 1 + 2 files changed, 78 insertions(+), 34 deletions(-) diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 5e19c7588e03..cf7fc0d57a54 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -316,9 +316,66 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) } /** - * tsens_hw_to_mC - Return sign-extended temperature in mCelsius. + * tsens_read_temp - Retrieve temperature readings from the hardware. * @s: Pointer to sensor struct * @field: Index into regmap_field array pointing to temperature data + * @temp: temperature in deciCelsius to be read from hardware + * + * This function handles temperature returned in ADC code or deciCelsius + * depending on IP version. + * + * Return: 0 on success, a negative errno will be returned in error cases + */ +static int tsens_read_temp(const struct tsens_sensor *s, int field, int *temp) +{ + struct tsens_priv *priv = s->priv; + int temp_val[MAX_READ_RETRY] = {0}; + u32 status; + int ret; + u32 last_temp_mask = GENMASK(priv->fields[LAST_TEMP_0].msb, + priv->fields[LAST_TEMP_0].lsb); + u32 valid_bit = priv->rf[VALID_0] ? BIT(priv->fields[VALID_0].lsb) : 0; + + for (int i = 0; i < MAX_READ_RETRY; i++) { + ret = regmap_read(priv->tm_map, priv->fields[field].reg, &status); + if (ret) + return ret; + + /* VER_0 doesn't have a VALID bit */ + if (!valid_bit) { + *temp = status & last_temp_mask; + return 0; + } + + temp_val[i] = status & last_temp_mask; + + if (status & valid_bit) { + *temp = temp_val[i]; + return 0; + } + } + + /* + * As per the HW guidelines, if none of the attempts observe a + * valid sample, a stable fallback value must be returned. If the + * first and second samples match, the second value is returned; + * otherwise, if the second and third samples match, the third + * value is returned. + */ + if (temp_val[0] == temp_val[1]) + *temp = temp_val[1]; + else if (temp_val[1] == temp_val[2]) + *temp = temp_val[2]; + else + return -EAGAIN; + + return 0; +} + +/** + * tsens_hw_to_mC - Return sign-extended temperature in mCelsius. + * @s: Pointer to sensor struct + * @temp: temperature in milliCelsius to be read from hardware * * This function handles temperature returned in ADC code or deciCelsius * depending on IP version. @@ -326,20 +383,14 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) * Return: Temperature in milliCelsius on success, a negative errno will * be returned in error cases */ -static int tsens_hw_to_mC(const struct tsens_sensor *s, int field) +static int tsens_hw_to_mC(const struct tsens_sensor *s, int temp) { struct tsens_priv *priv = s->priv; u32 resolution; - u32 temp = 0; - int ret; resolution = priv->fields[LAST_TEMP_0].msb - priv->fields[LAST_TEMP_0].lsb; - ret = regmap_field_read(priv->rf[field], &temp); - if (ret) - return ret; - /* Convert temperature from ADC code to milliCelsius */ if (priv->feat->adc) return code_to_degc(temp, s) * 1000; @@ -514,8 +565,10 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, &d->crit_irq_mask); if (ret) return ret; - - d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id); + ret = regmap_field_read(priv->rf[CRIT_THRESH_0 + hw_id], &d->crit_thresh); + if (ret) + return ret; + d->crit_thresh = tsens_hw_to_mC(s, d->crit_thresh); } else { /* No mask register on older TSENS */ d->up_irq_mask = 0; @@ -525,8 +578,16 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, d->crit_thresh = 0; } - d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id); - d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id); + ret = regmap_field_read(priv->rf[UP_THRESH_0 + hw_id], &d->up_thresh); + if (ret) + return ret; + + d->up_thresh = tsens_hw_to_mC(s, d->up_thresh); + ret = regmap_field_read(priv->rf[LOW_THRESH_0 + hw_id], &d->low_thresh); + if (ret) + return ret; + + d->low_thresh = tsens_hw_to_mC(s, d->low_thresh); dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n", hw_id, __func__, @@ -750,33 +811,15 @@ static void tsens_disable_irq(struct tsens_priv *priv) int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) { - struct tsens_priv *priv = s->priv; int hw_id = s->hw_id; u32 temp_idx = LAST_TEMP_0 + hw_id; - u32 valid_idx = VALID_0 + hw_id; - u32 valid; int ret; - /* VER_0 doesn't have VALID bit */ - if (tsens_version(priv) == VER_0) - goto get_temp; + ret = tsens_read_temp(s, temp_idx, temp); + if (!ret) + *temp = tsens_hw_to_mC(s, *temp); - /* Valid bit is 0 for 6 AHB clock cycles. - * At 19.2MHz, 1 AHB clock is ~60ns. - * We should enter this loop very, very rarely. - * Wait 1 us since it's the min of poll_timeout macro. - * Old value was 400 ns. - */ - ret = regmap_field_read_poll_timeout(priv->rf[valid_idx], valid, - valid, 1, 20 * USEC_PER_MSEC); - if (ret) - return ret; - -get_temp: - /* Valid bit is set, OK to read the temperature */ - *temp = tsens_hw_to_mC(s, temp_idx); - - return 0; + return ret; } int get_temp_common(const struct tsens_sensor *s, int *temp) diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 2a7afa4c899b..ab57ad88c3f7 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -21,6 +21,7 @@ #define THRESHOLD_MIN_ADC_CODE 0x0 #define MAX_SENSORS 16 +#define MAX_READ_RETRY 3 #include #include From 375a4a81968cc8bb56685c99bf1153bc70eb3b29 Mon Sep 17 00:00:00 2001 From: Mayur Kumar Date: Mon, 11 May 2026 21:48:54 +0530 Subject: [PATCH 30/54] thermal/of: Fix trailing whitespace and repeated word Correct a trailing whitespace error on line 101 and remove a duplicated "which" in the kernel-doc comment for thermal_of_zone_register. Signed-off-by: Mayur Kumar Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260511161854.193573-1-kmayur809@gmail.com --- drivers/thermal/thermal_of.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 99085c806a1f..75fb7663c507 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -98,7 +98,7 @@ static struct thermal_trip *thermal_of_trips_init(struct device_node *np, int *n int ret, count; *ntrips = 0; - + struct device_node *trips __free(device_node) = of_get_child_by_name(np, "trips"); if (!trips) return NULL; @@ -494,7 +494,7 @@ EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register); /** * devm_thermal_of_zone_unregister - Resource managed version of * thermal_of_zone_unregister(). - * @dev: Device for which which resource was allocated. + * @dev: Device for which resource was allocated. * @tz: a pointer to struct thermal_zone where the sensor is registered. * * This function removes the sensor callbacks and private data from the From 4f5130427e678f7cb8e78c1c18c9485e126469c0 Mon Sep 17 00:00:00 2001 From: Mayur Kumar Date: Mon, 11 May 2026 23:12:55 +0530 Subject: [PATCH 31/54] thermal/drivers/imx: Do not split quoted string across lines The checkpatch tool warns against splitting quoted strings across multiple lines. Join the dev_info message into a single line to improve the ability to grep for the message in the source. Signed-off-by: Mayur Kumar Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260511174255.215207-1-kmayur809@gmail.com --- drivers/thermal/imx_thermal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index 38c993d1bcb3..5aaacbc53478 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -693,8 +693,8 @@ static int imx_thermal_probe(struct platform_device *pdev) goto clk_disable; } - dev_info(dev, "%s CPU temperature grade - max:%dC" - " critical:%dC passive:%dC\n", data->temp_grade, + dev_info(dev, "%s CPU temperature grade - max:%dC critical:%dC passive:%dC\n", + data->temp_grade, data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000, trips[IMX_TRIP_PASSIVE].temperature / 1000); From 0309075e080306ad2e3b3148a3495b10dd252ef2 Mon Sep 17 00:00:00 2001 From: Shuwei Wu Date: Mon, 27 Apr 2026 15:15:15 +0800 Subject: [PATCH 32/54] dt-bindings: thermal: Add SpacemiT K1 thermal sensor Document the SpacemiT K1 Thermal Sensor, which supports monitoring temperatures for five zones: soc, package, gpu, cluster0, and cluster1. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Shuwei Wu Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260427-k1-thermal-v5-1-df39187480ed@mailbox.org --- .../bindings/thermal/spacemit,k1-tsensor.yaml | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml diff --git a/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml b/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml new file mode 100644 index 000000000000..6dad76a7dd36 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/spacemit,k1-tsensor.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 Thermal Sensor + +description: + The SpacemiT K1 Thermal Sensor monitors the temperature of the SoC + using multiple internal sensors (e.g., soc, package, gpu, clusters). + +maintainers: + - Shuwei Wu + +$ref: thermal-sensor.yaml# + +properties: + compatible: + const: spacemit,k1-tsensor + + reg: + maxItems: 1 + + clocks: + items: + - description: Core clock for thermal sensor + - description: Bus clock for thermal sensor + + clock-names: + items: + - const: core + - const: bus + + interrupts: + maxItems: 1 + + resets: + items: + - description: Reset for the thermal sensor + + "#thermal-sensor-cells": + const: 1 + description: + The first cell indicates the sensor ID. + 0 = soc + 1 = package + 2 = gpu + 3 = cluster0 + 4 = cluster1 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - resets + - "#thermal-sensor-cells" + +additionalProperties: false + +examples: + - | + #include + + thermal@d4018000 { + compatible = "spacemit,k1-tsensor"; + reg = <0xd4018000 0x100>; + clocks = <&syscon_apbc CLK_TSEN>, + <&syscon_apbc CLK_TSEN_BUS>; + clock-names = "core", "bus"; + interrupts = <61>; + resets = <&syscon_apbc RESET_TSEN>; + #thermal-sensor-cells = <1>; + }; From 296a977f2bac45759a427dc57a21edb628f5c86c Mon Sep 17 00:00:00 2001 From: Shuwei Wu Date: Mon, 27 Apr 2026 15:15:16 +0800 Subject: [PATCH 33/54] thermal/drivers/spacemit/k1: Add thermal sensor support The thermal sensor on K1 supports monitoring five temperature zones. The driver registers these sensors with the thermal framework and supports standard operations: - Reading temperature (millidegree Celsius) - Setting high/low thresholds for interrupts Signed-off-by: Shuwei Wu Signed-off-by: Daniel Lezcano Reviewed-by: Anand Moon Reviewed-by: Troy Mitchell Reviewed-by: Yao Zi Tested-by: Anand Moon Tested-by: Vincent Legoll # OrangePi-RV2 Tested-by: Gong Shuai Link: https://patch.msgid.link/20260427-k1-thermal-v5-2-df39187480ed@mailbox.org --- drivers/thermal/Kconfig | 2 + drivers/thermal/Makefile | 1 + drivers/thermal/spacemit/Kconfig | 19 ++ drivers/thermal/spacemit/Makefile | 3 + drivers/thermal/spacemit/k1_tsensor.c | 280 ++++++++++++++++++++++++++ 5 files changed, 305 insertions(+) create mode 100644 drivers/thermal/spacemit/Kconfig create mode 100644 drivers/thermal/spacemit/Makefile create mode 100644 drivers/thermal/spacemit/k1_tsensor.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 40a376cf9936..810eeccedfba 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -473,6 +473,8 @@ endmenu source "drivers/thermal/renesas/Kconfig" +source "drivers/thermal/spacemit/Kconfig" + source "drivers/thermal/tegra/Kconfig" config GENERIC_ADC_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index bb21e7ea7fc6..3b249195c088 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -65,6 +65,7 @@ obj-y += mediatek/ obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o +obj-y += spacemit/ obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig new file mode 100644 index 000000000000..de7b5ece5af2 --- /dev/null +++ b/drivers/thermal/spacemit/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "SpacemiT thermal drivers" +depends on ARCH_SPACEMIT || COMPILE_TEST + +config SPACEMIT_K1_TSENSOR + tristate "SpacemiT K1 thermal sensor driver" + depends on THERMAL_OF + help + This driver provides support for the thermal sensor + integrated in the SpacemiT K1 SoC. + + The thermal sensor monitors temperatures for five thermal zones: + soc, package, gpu, cluster0, and cluster1. It supports reporting + temperature values and handling high/low threshold interrupts. + + Say Y here if you want to enable thermal monitoring on SpacemiT K1. + If compiled as a module, it will be called k1_tsensor. + +endmenu diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile new file mode 100644 index 000000000000..82b30741e4ec --- /dev/null +++ b/drivers/thermal/spacemit/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SPACEMIT_K1_TSENSOR) += k1_tsensor.o diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c new file mode 100644 index 000000000000..79222d233129 --- /dev/null +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Thermal sensor driver for SpacemiT K1 SoC + * + * Copyright (C) 2026 Shuwei Wu + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define K1_TSENSOR_PCTRL_REG 0x00 +#define K1_TSENSOR_PCTRL_ENABLE BIT(0) +#define K1_TSENSOR_PCTRL_TEMP_MODE BIT(3) +#define K1_TSENSOR_PCTRL_RAW_SEL BIT(7) + +#define K1_TSENSOR_PCTRL_CTUNE GENMASK(11, 8) +#define K1_TSENSOR_PCTRL_SW_CTRL GENMASK(21, 18) +#define K1_TSENSOR_PCTRL_HW_AUTO_MODE BIT(23) + +#define K1_TSENSOR_EN_REG 0x08 +#define K1_TSENSOR_EN_ALL GENMASK(MAX_SENSOR_NUMBER - 1, 0) + +#define K1_TSENSOR_TIME_REG 0x0C +#define K1_TSENSOR_TIME_WAIT_REF_CNT GENMASK(3, 0) +#define K1_TSENSOR_TIME_ADC_CNT_RST GENMASK(7, 4) +#define K1_TSENSOR_TIME_FILTER_PERIOD GENMASK(21, 20) +#define K1_TSENSOR_TIME_MASK GENMASK(23, 0) + +#define K1_TSENSOR_INT_CLR_REG 0x10 +#define K1_TSENSOR_INT_EN_REG 0x14 +#define K1_TSENSOR_INT_STA_REG 0x18 + +#define K1_TSENSOR_INT_EN_MASK BIT(0) +#define K1_TSENSOR_INT_MASK(x) (GENMASK(2, 1) << ((x) * 2)) + +#define K1_TSENSOR_DATA_BASE_REG 0x20 +#define K1_TSENSOR_DATA_REG(x) (K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4) +#define K1_TSENSOR_DATA_LOW_MASK GENMASK(15, 0) +#define K1_TSENSOR_DATA_HIGH_MASK GENMASK(31, 16) + +#define K1_TSENSOR_THRSH_BASE_REG 0x40 +#define K1_TSENSOR_THRSH_REG(x) (K1_TSENSOR_THRSH_BASE_REG + ((x) * 4)) +#define K1_TSENSOR_THRSH_LOW_MASK GENMASK(15, 0) +#define K1_TSENSOR_THRSH_HIGH_MASK GENMASK(31, 16) + +#define MAX_SENSOR_NUMBER 5 + +/* Hardware offset value required for temperature calculation */ +#define TEMPERATURE_OFFSET 278 + +struct k1_tsensor_channel { + struct k1_tsensor *ts; + struct thermal_zone_device *tzd; + int id; +}; + +struct k1_tsensor { + void __iomem *base; + struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER]; +}; + +static void k1_tsensor_init(struct k1_tsensor *ts) +{ + u32 val; + + /* Disable all the interrupts */ + writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Configure ADC sampling time and filter period */ + val = readl(ts->base + K1_TSENSOR_TIME_REG); + val &= ~K1_TSENSOR_TIME_MASK; + val |= K1_TSENSOR_TIME_FILTER_PERIOD | + K1_TSENSOR_TIME_ADC_CNT_RST | + K1_TSENSOR_TIME_WAIT_REF_CNT; + writel(val, ts->base + K1_TSENSOR_TIME_REG); + + /* + * Enable all sensors' auto mode, enable dither control, + * consecutive mode, and power up sensor. + */ + val = readl(ts->base + K1_TSENSOR_PCTRL_REG); + val &= ~K1_TSENSOR_PCTRL_SW_CTRL; + val &= ~K1_TSENSOR_PCTRL_CTUNE; + val |= K1_TSENSOR_PCTRL_RAW_SEL | + K1_TSENSOR_PCTRL_TEMP_MODE | + K1_TSENSOR_PCTRL_HW_AUTO_MODE | + K1_TSENSOR_PCTRL_ENABLE; + writel(val, ts->base + K1_TSENSOR_PCTRL_REG); + + /* Enable each sensor */ + val = readl(ts->base + K1_TSENSOR_EN_REG); + val |= K1_TSENSOR_EN_ALL; + writel(val, ts->base + K1_TSENSOR_EN_REG); +} + +static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch) +{ + struct k1_tsensor *ts = ch->ts; + u32 val; + + val = readl(ts->base + K1_TSENSOR_INT_CLR_REG); + val |= K1_TSENSOR_INT_MASK(ch->id); + writel(val, ts->base + K1_TSENSOR_INT_CLR_REG); + + val = readl(ts->base + K1_TSENSOR_INT_EN_REG); + val &= ~K1_TSENSOR_INT_MASK(ch->id); + writel(val, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Enable thermal interrupt */ + val = readl(ts->base + K1_TSENSOR_INT_EN_REG); + val |= K1_TSENSOR_INT_EN_MASK; + writel(val, ts->base + K1_TSENSOR_INT_EN_REG); +} + +/* + * The conversion formula used is: + * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000 + */ +static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz); + struct k1_tsensor *ts = ch->ts; + u32 val; + + val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id)); + if (ch->id % 2) + *temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val); + else + *temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val); + + *temp -= TEMPERATURE_OFFSET; + *temp *= 1000; + + return 0; +} + +/* + * For each sensor, the hardware threshold register is 32 bits: + * - Lower 16 bits [15:0] configure the low threshold temperature. + * - Upper 16 bits [31:16] configure the high threshold temperature. + */ +static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high) +{ + struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz); + struct k1_tsensor *ts = ch->ts; + u32 val; + + if (low >= high) + return -EINVAL; + + low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, + FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK)); + high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, + FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK)); + + val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id)); + + val &= ~(K1_TSENSOR_THRSH_LOW_MASK | K1_TSENSOR_THRSH_HIGH_MASK); + val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low); + val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high); + + writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id)); + + return 0; +} + +static const struct thermal_zone_device_ops k1_tsensor_ops = { + .get_temp = k1_tsensor_get_temp, + .set_trips = k1_tsensor_set_trips, +}; + +static irqreturn_t k1_tsensor_irq_thread(int irq, void *data) +{ + struct k1_tsensor *ts = (struct k1_tsensor *)data; + int mask, status, i; + + status = readl(ts->base + K1_TSENSOR_INT_STA_REG); + + for (i = 0; i < MAX_SENSOR_NUMBER; i++) { + if (status & K1_TSENSOR_INT_MASK(i)) { + mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG); + mask |= K1_TSENSOR_INT_MASK(i); + writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG); + thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED); + } + } + + return IRQ_HANDLED; +} + +static int k1_tsensor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct k1_tsensor *ts; + struct reset_control *reset; + struct clk *clk; + int i, irq, ret; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ts->base)) + return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n"); + + reset = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n"); + + clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n"); + + clk = devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n"); + + k1_tsensor_init(ts); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + k1_tsensor_irq_thread, + IRQF_ONESHOT, "k1_tsensor", ts); + if (ret < 0) + return ret; + + for (i = 0; i < MAX_SENSOR_NUMBER; ++i) { + ts->ch[i].id = i; + ts->ch[i].ts = ts; + ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops); + if (IS_ERR(ts->ch[i].tzd)) + return PTR_ERR(ts->ch[i].tzd); + + /* Attach sysfs hwmon attributes for userspace monitoring */ + ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd); + if (ret) + dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); + + k1_tsensor_enable_irq(ts->ch + i); + } + + platform_set_drvdata(pdev, ts); + + return 0; +} + +static const struct of_device_id k1_tsensor_dt_ids[] = { + { .compatible = "spacemit,k1-tsensor" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids); + +static struct platform_driver k1_tsensor_driver = { + .driver = { + .name = "k1_tsensor", + .of_match_table = k1_tsensor_dt_ids, + }, + .probe = k1_tsensor_probe, +}; +module_platform_driver(k1_tsensor_driver); + +MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver"); +MODULE_AUTHOR("Shuwei Wu "); +MODULE_LICENSE("GPL"); From 2086b040c3d22e0c70e731047b013e31ef83ae10 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Thu, 30 Apr 2026 10:53:30 +0800 Subject: [PATCH 34/54] dt-bindings: thermal: qoriq: Add compatible string for imx93 Add i.MX93 compatible string 'fsl,imx93-tmu' because Thermal monitor unit(TMU) on i.MX93 has differences with QorIQ platform and not fully compatible with existing Platform, such as fsl,qoriq-tmu. Signed-off-by: Jacky Bai Signed-off-by: Daniel Lezcano Tested-by: Alexander Stein Reviewed-by: Frank Li Acked-by: Conor Dooley Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260430-imx93_tmu-v6-1-485459d7b54f@nxp.com --- Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml b/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml index aa756dae512a..f3b136f5e1cb 100644 --- a/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml +++ b/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml @@ -25,6 +25,7 @@ properties: enum: - fsl,qoriq-tmu - fsl,imx8mq-tmu + - fsl,imx93-tmu reg: maxItems: 1 From e058b025906ee13b1cb3755073abe6ac3b297de1 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Thu, 30 Apr 2026 10:53:31 +0800 Subject: [PATCH 35/54] thermal/drivers/qoriq: Add i.MX93 tmu support For Thermal monitor unit(TMU) used on i.MX93, the HW revision info read from the ID register is the same the one used on some of the QorIQ platform, but the config has some slight differance. Add i.MX93 compatible string and corresponding code for it. Signed-off-by: Alice Guo Signed-off-by: Jacky Bai Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Link: https://patch.msgid.link/20260430-imx93_tmu-v6-2-485459d7b54f@nxp.com --- drivers/thermal/qoriq_thermal.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c index 01b58be0dcc6..e4b61d531e44 100644 --- a/drivers/thermal/qoriq_thermal.c +++ b/drivers/thermal/qoriq_thermal.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 // // Copyright 2016 Freescale Semiconductor, Inc. +// Copyright 2025 NXP #include #include @@ -24,6 +25,7 @@ #define TMTMIR_DEFAULT 0x0000000f #define TIER_DISABLE 0x0 #define TEUMR0_V2 0x51009c00 +#define TEUMR0_V21 0x55000c00 #define TMSARA_V2 0xe #define TMU_VER1 0x1 #define TMU_VER2 0x2 @@ -73,12 +75,17 @@ struct qoriq_sensor { int id; }; +struct tmu_drvdata { + u32 teumr0; +}; + struct qoriq_tmu_data { int ver; u32 ttrcr[NUM_TTRCR_MAX]; struct regmap *regmap; struct clk *clk; struct qoriq_sensor sensor[SITES_MAX]; + const struct tmu_drvdata *drvdata; }; static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s) @@ -234,7 +241,8 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) regmap_write(data->regmap, REGS_TMTMIR, TMTMIR_DEFAULT); } else { regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT); - regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2); + regmap_write(data->regmap, REGS_V2_TEUMR(0), + data->drvdata->teumr0); } /* Disable monitoring */ @@ -319,6 +327,10 @@ static int qoriq_tmu_probe(struct platform_device *pdev) data->ver = (ver >> 8) & 0xff; + data->drvdata = of_device_get_match_data(&pdev->dev); + if (!data->drvdata) + return dev_err_probe(dev, -EINVAL, "Failed to get match data\n"); + qoriq_tmu_init_device(data); /* TMU initialization */ ret = qoriq_tmu_calibration(dev, data); /* TMU calibration */ @@ -376,9 +388,22 @@ static int qoriq_tmu_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, qoriq_tmu_suspend, qoriq_tmu_resume); +static const struct tmu_drvdata qoriq_tmu_data = { + .teumr0 = TEUMR0_V2, +}; + +static const struct tmu_drvdata imx8mq_tmu_data = { + .teumr0 = TEUMR0_V2, +}; + +static const struct tmu_drvdata imx93_data = { + .teumr0 = TEUMR0_V21, +}; + static const struct of_device_id qoriq_tmu_match[] = { - { .compatible = "fsl,qoriq-tmu", }, - { .compatible = "fsl,imx8mq-tmu", }, + { .compatible = "fsl,qoriq-tmu", .data = &qoriq_tmu_data }, + { .compatible = "fsl,imx8mq-tmu", .data = &imx8mq_tmu_data }, + { .compatible = "fsl,imx93-tmu", .data = &imx93_data }, {}, }; MODULE_DEVICE_TABLE(of, qoriq_tmu_match); From 50e72d6dc427228b62d2ebce6fb0e3107527180a Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Thu, 30 Apr 2026 10:53:32 +0800 Subject: [PATCH 36/54] thermal/driver/qoriq: Workaround unexpected temperature readings from tmu Invalid temperature measurements may be observed across the temperature range specified in the device data sheet. The invalid temperature can be read from any remote site and from any capture or report registers. The invalid change in temperature can be positive or negative and the resulting temperature can be outside the calibrated range, in which case the TSR[ORL] or TSR[ORH] bit will be set. Workaround: Use the raising/falling edge threshold to filter out the invalid temp. Check the TIDR register to make sure no jump happens When reading the temp. i.MX93 ERR052243: (https://www.nxp.com/webapp/Download?colCode=IMX93_2P87F&appType=license) Signed-off-by: Jacky Bai Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260430-imx93_tmu-v6-3-485459d7b54f@nxp.com --- drivers/thermal/qoriq_thermal.c | 40 ++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c index e4b61d531e44..35439ec5f8bc 100644 --- a/drivers/thermal/qoriq_thermal.c +++ b/drivers/thermal/qoriq_thermal.c @@ -3,6 +3,7 @@ // Copyright 2016 Freescale Semiconductor, Inc. // Copyright 2025 NXP +#include #include #include #include @@ -30,6 +31,9 @@ #define TMU_VER1 0x1 #define TMU_VER2 0x2 +/* errata ID info define */ +#define TMU_ERR052243 BIT(0) + #define REGS_TMR 0x000 /* Mode Register */ #define TMR_DISABLE 0x0 #define TMR_ME 0x80000000 @@ -45,6 +49,15 @@ #define REGS_TIER 0x020 /* Interrupt Enable Register */ #define TIER_DISABLE 0x0 +#define REGS_TIDR 0x24 +#define TEMP_RATE_IRQ_MASK GENMASK(25, 24) +#define TMRTRCTR 0x70 +#define TMRTRCTR_EN BIT(31) +#define TMRTRCTR_TEMP_MASK GENMASK(7, 0) +#define TMFTRCTR 0x74 +#define TMFTRCTR_EN BIT(31) +#define TMFTRCTR_TEMP_MASK GENMASK(7, 0) +#define TEMP_RATE_THR_LVL 0x7 #define REGS_TTCFGR 0x080 /* Temperature Configuration Register */ #define REGS_TSCFGR 0x084 /* Sensor Configuration Register */ @@ -77,6 +90,7 @@ struct qoriq_sensor { struct tmu_drvdata { u32 teumr0; + u32 tmu_errata; }; struct qoriq_tmu_data { @@ -88,6 +102,12 @@ struct qoriq_tmu_data { const struct tmu_drvdata *drvdata; }; +static inline bool qoriq_tmu_has_errata(const struct tmu_drvdata *drvdata, + u32 flag) +{ + return drvdata->tmu_errata & flag; +} + static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s) { return container_of(s, struct qoriq_tmu_data, sensor[s->id]); @@ -97,7 +117,7 @@ static int tmu_get_temp(struct thermal_zone_device *tz, int *temp) { struct qoriq_sensor *qsensor = thermal_zone_device_priv(tz); struct qoriq_tmu_data *qdata = qoriq_sensor_to_data(qsensor); - u32 val; + u32 val, tidr; /* * REGS_TRITSR(id) has the following layout: * @@ -130,6 +150,15 @@ static int tmu_get_temp(struct thermal_zone_device *tz, int *temp) 10 * USEC_PER_MSEC)) return -ENODATA; + /*ERR052243: If a raising or falling edge happens, try later */ + if (qoriq_tmu_has_errata(qdata->drvdata, TMU_ERR052243)) { + regmap_read(qdata->regmap, REGS_TIDR, &tidr); + if (tidr & TEMP_RATE_IRQ_MASK) { + regmap_write(qdata->regmap, REGS_TIDR, TEMP_RATE_IRQ_MASK); + return -EAGAIN; + } + } + if (qdata->ver == TMU_VER1) { *temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE; } else { @@ -245,6 +274,14 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) data->drvdata->teumr0); } + /* ERR052243: Set the raising & falling edge monitor */ + if (qoriq_tmu_has_errata(data->drvdata, TMU_ERR052243)) { + regmap_write(data->regmap, TMRTRCTR, TMRTRCTR_EN | + FIELD_PREP(TMRTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL)); + regmap_write(data->regmap, TMFTRCTR, TMFTRCTR_EN | + FIELD_PREP(TMFTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL)); + + } /* Disable monitoring */ regmap_write(data->regmap, REGS_TMR, TMR_DISABLE); } @@ -398,6 +435,7 @@ static const struct tmu_drvdata imx8mq_tmu_data = { static const struct tmu_drvdata imx93_data = { .teumr0 = TEUMR0_V21, + .tmu_errata = TMU_ERR052243, }; static const struct of_device_id qoriq_tmu_match[] = { From de4483d4447981292c68d45ea643158bb8ca92b9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 26 May 2026 15:53:13 +0200 Subject: [PATCH 37/54] thermal/drivers/samsung: Enable TMU by default The SoC Thermal Management Unit (TMU) is essential for proper operation of the SoCs. Kernel should not ask users choice of drivers when that choice is obvious and known to the developers that answer should be 'yes' or 'module'. Impact of making it default: 1. arm64 defconfig: No changes, already present in defconfig. 2. arm32: No changes, the driver is already selected by MACH_EXYNOS. 3. COMPILE_TEST builds: enable by default for arm32 or arm64 builds, whenever ARCH_EXYNOS is selected. This has impact on build time and feels logical, because if one selects ARCH_EXYNOS then probably by default wants to build test it entirely. Kernels with COMPILE_TEST are not supposed to be used for booting. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260526135312.8697-2-krzysztof.kozlowski@oss.qualcomm.com --- drivers/thermal/samsung/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index f4eff5a41a84..e1e8035d24fb 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -3,6 +3,7 @@ config EXYNOS_THERMAL tristate "Exynos thermal management unit driver" depends on THERMAL_OF depends on HAS_IOMEM + default ARCH_EXYNOS help If you say yes here you get support for the TMU (Thermal Management Unit) driver for Samsung Exynos series of SoCs. This driver initialises From 298a2d461f1ffd75e4ac06774f42e1f018c3a840 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:02 +0200 Subject: [PATCH 38/54] thermal/core: Introduce non-OF thermal_cooling_device_register() Split the cooling device registration API into OF and non-OF variants. Introduce thermal_cooling_device_register() for non-device-tree users and rework thermal_of_cooling_device_register() to use the new alloc/add split. This removes the need for the internal __thermal_cooling_device_register() helper and makes the separation between OF and non-OF users explicit. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-13-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/thermal_core.c | 88 +++++++++++++++------------------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index db01361569d7..0b3db889d60d 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1059,7 +1059,39 @@ static int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void } /** - * __thermal_cooling_device_register() - register a new thermal cooling device + * thermal_cooling_device_register() - register a new thermal cooling device + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +thermal_cooling_device_register(const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_cooling_device_alloc(type, ops); + if (IS_ERR(cdev)) + return cdev; + + ret = thermal_cooling_device_add(cdev, devdata); + if (ret) + return ERR_PTR(ret); + + return cdev; +} +EXPORT_SYMBOL_GPL(thermal_cooling_device_register); + +/** + * thermal_of_cooling_device_register() - register an OF thermal cooling device * @np: a pointer to a device tree node. * @type: the thermal cooling device type. * @devdata: device private data. @@ -1074,10 +1106,10 @@ static int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void * Return: a pointer to the created struct thermal_cooling_device or an * ERR_PTR. Caller must check return value with IS_ERR*() helpers. */ -static struct thermal_cooling_device * -__thermal_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +struct thermal_cooling_device * +thermal_of_cooling_device_register(struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; int ret; @@ -1094,50 +1126,6 @@ __thermal_cooling_device_register(struct device_node *np, return cdev; } - -/** - * thermal_cooling_device_register() - register a new thermal cooling device - * @type: the thermal cooling device type. - * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. - * - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. - * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. - */ -struct thermal_cooling_device * -thermal_cooling_device_register(const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) -{ - return __thermal_cooling_device_register(NULL, type, devdata, ops); -} -EXPORT_SYMBOL_GPL(thermal_cooling_device_register); - -/** - * thermal_of_cooling_device_register() - register an OF thermal cooling device - * @np: a pointer to a device tree node. - * @type: the thermal cooling device type. - * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. - * - * This function will register a cooling device with device tree node reference. - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. - * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. - */ -struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) -{ - return __thermal_cooling_device_register(np, type, devdata, ops); -} EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); static void thermal_cooling_device_release(void *data) @@ -1173,7 +1161,7 @@ devm_thermal_of_cooling_device_register(struct device *dev, struct thermal_cooling_device *cdev; int ret; - cdev = __thermal_cooling_device_register(np, type, devdata, ops); + cdev = thermal_of_cooling_device_register(np, type, devdata, ops); if (IS_ERR(cdev)) return cdev; From 876bb45f36939307c1e376243d862ad1b1a5f0cd Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:03 +0200 Subject: [PATCH 39/54] thermal/core: Add devm_thermal_cooling_device_register() Introduce a device-managed variant of the non-OF cooling device registration API. This complements devm_thermal_of_cooling_device_register() and allows non-device-tree users to register cooling devices with automatic cleanup tied to the device lifecycle. The helper relies on devm_add_action_or_reset() to release the cooling device via thermal_cooling_device_release() on driver detach or probe failure. This keeps the API consistent across OF and non-OF users and avoids manual cleanup in error paths. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-14-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/thermal_core.c | 35 ++++++++++++++++++++++++++++++++++ include/linux/thermal.h | 7 +++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 0b3db889d60d..bb4fc3ff2ad5 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1173,6 +1173,41 @@ devm_thermal_of_cooling_device_register(struct device *dev, } EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); +/** + * devm_thermal_cooling_device_register() - register a thermal cooling device + * + * @dev: a valid struct device pointer of a sensor device. + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device. This interface + * function adds a new thermal cooling device (fan/processor/...) to + * /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind + * itself to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_cooling_device_register(type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + ret = devm_add_action_or_reset(dev, thermal_cooling_device_release, cdev); + if (ret) + return ERR_PTR(ret); + + return cdev; +} +EXPORT_SYMBOL_GPL(devm_thermal_cooling_device_register); + static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev) { struct thermal_cooling_device *pos = NULL; diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 0ddc77aeeca2..fc3f4a098370 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -253,8 +253,11 @@ void thermal_zone_device_update(struct thermal_zone_device *, struct thermal_cooling_device *thermal_cooling_device_register(const char *, void *, const struct thermal_cooling_device_ops *); struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, const char *, void *, - const struct thermal_cooling_device_ops *); +devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); +struct thermal_cooling_device * +thermal_of_cooling_device_register(struct device_node *np, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); struct thermal_cooling_device * devm_thermal_of_cooling_device_register(struct device *dev, struct device_node *np, From 61e7550fe8b26c2b132eff2ced57c6b2dd93ca7f Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:04 +0200 Subject: [PATCH 40/54] hwmon: Use non-OF thermal cooling device registration API Some HWMON drivers register cooling devices using the OF helper devm_thermal_of_cooling_device_register() with a NULL device node. With the introduction of a dedicated non-OF registration API, switch these users to devm_thermal_cooling_device_register() to make the intent explicit and avoid relying on OF-specific helpers. This is a pure refactoring with no functional change. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Guenter Roeck Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-15-daniel.lezcano@oss.qualcomm.com --- drivers/hwmon/cros_ec_hwmon.c | 4 ++-- drivers/hwmon/dell-smm-hwmon.c | 4 ++-- drivers/hwmon/mlxreg-fan.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 6cf5ab0f4b73..77dd9f28962d 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -532,8 +532,8 @@ static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev, cpriv->hwmon_priv = priv; cpriv->index = i; - cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv, - &cros_ec_thermal_cooling_ops); + cdev = devm_thermal_cooling_device_register(dev, type, cpriv, + &cros_ec_thermal_cooling_ops); if (IS_ERR(cdev)) { dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i, cdev); diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 038edffc1ac7..47b373ea6db4 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1161,8 +1161,8 @@ static int dell_smm_init_cdev(struct device *dev, u8 fan_num) if (cdata) { cdata->fan_num = fan_num; cdata->data = data; - cdev = devm_thermal_of_cooling_device_register(dev, NULL, name, cdata, - &dell_smm_cooling_ops); + cdev = devm_thermal_cooling_device_register(dev, name, cdata, + &dell_smm_cooling_ops); if (IS_ERR(cdev)) { devm_kfree(dev, cdata); ret = PTR_ERR(cdev); diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c index 137a90dd2075..860de6cfd8a4 100644 --- a/drivers/hwmon/mlxreg-fan.c +++ b/drivers/hwmon/mlxreg-fan.c @@ -583,8 +583,8 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan) pwm->fan = fan; /* Set minimal PWM speed. */ pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY); - pwm->cdev = devm_thermal_of_cooling_device_register(dev, NULL, mlxreg_fan_name[i], - pwm, &mlxreg_fan_cooling_ops); + pwm->cdev = devm_thermal_cooling_device_register(dev, mlxreg_fan_name[i], + pwm, &mlxreg_fan_cooling_ops); if (IS_ERR(pwm->cdev)) { dev_err(dev, "Failed to register cooling device\n"); return PTR_ERR(pwm->cdev); From 27559121b2e3ffbdfdbb77b528ba1015e2617daa Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:06 +0200 Subject: [PATCH 41/54] thermal/of: Move cooling device OF helpers out of thermal core The functions: - thermal_of_cooling_device_register() - devm_thermal_of_cooling_device_register() are specific to device tree usage but are currently implemented in thermal_core.c. Move them to thermal_of.c to better reflect the separation between generic thermal core code and OF-specific logic. This change is enabled by the recent split of the cooling device registration into allocation and addition phases, allowing OF-specific handling (such as device node assignment) to be isolated from the core. No functional change intended. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-17-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/thermal_core.c | 80 +------------------------------- drivers/thermal/thermal_core.h | 5 ++ drivers/thermal/thermal_of.c | 83 ++++++++++++++++++++++++++++++++++ include/linux/thermal.h | 49 ++++++++++++-------- 4 files changed, 119 insertions(+), 98 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index bb4fc3ff2ad5..28a20d4b475c 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -963,7 +963,7 @@ static void thermal_cdev_release(struct device *dev) kfree(cdev); } -static struct thermal_cooling_device * +struct thermal_cooling_device * thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; @@ -1002,7 +1002,7 @@ thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_devi return ERR_PTR(ret); } -static int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata) +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata) { unsigned long current_state; int ret; @@ -1090,44 +1090,6 @@ thermal_cooling_device_register(const char *type, void *devdata, } EXPORT_SYMBOL_GPL(thermal_cooling_device_register); -/** - * thermal_of_cooling_device_register() - register an OF thermal cooling device - * @np: a pointer to a device tree node. - * @type: the thermal cooling device type. - * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. - * - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. - * It also gives the opportunity to link the cooling device to a device tree - * node, so that it can be bound to a thermal zone created out of device tree. - * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. - */ -struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) -{ - struct thermal_cooling_device *cdev; - int ret; - - cdev = thermal_cooling_device_alloc(type, ops); - if (IS_ERR(cdev)) - return cdev; - - cdev->np = np; - - ret = thermal_cooling_device_add(cdev, devdata); - if (ret) - return ERR_PTR(ret); - - return cdev; -} -EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); - static void thermal_cooling_device_release(void *data) { struct thermal_cooling_device *cdev = data; @@ -1135,44 +1097,6 @@ static void thermal_cooling_device_release(void *data) thermal_cooling_device_unregister(cdev); } -/** - * devm_thermal_of_cooling_device_register() - register an OF thermal cooling - * device - * @dev: a valid struct device pointer of a sensor device. - * @np: a pointer to a device tree node. - * @type: the thermal cooling device type. - * @devdata: device private data. - * @ops: standard thermal cooling devices callbacks. - * - * This function will register a cooling device with device tree node reference. - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. - * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. - */ -struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) -{ - struct thermal_cooling_device *cdev; - int ret; - - cdev = thermal_of_cooling_device_register(np, type, devdata, ops); - if (IS_ERR(cdev)) - return cdev; - - ret = devm_add_action_or_reset(dev, thermal_cooling_device_release, cdev); - if (ret) - return ERR_PTR(ret); - - return cdev; -} -EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); - /** * devm_thermal_cooling_device_register() - register a thermal cooling device * diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 0acb7d9587ca..e98b0aa5aacc 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -267,6 +267,11 @@ void thermal_zone_device_critical_shutdown(struct thermal_zone_device *tz); void thermal_governor_update_tz(struct thermal_zone_device *tz, enum thermal_notify_event reason); +struct thermal_cooling_device * +thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops); + +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata); + /* Helpers */ #define for_each_trip_desc(__tz, __td) \ for (__td = __tz->trips; __td - __tz->trips < __tz->num_trips; __td++) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 75fb7663c507..8c49d449d43f 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -510,3 +510,86 @@ void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_dev devm_thermal_of_zone_match, tz)); } EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister); + +/** + * thermal_of_cooling_device_register() - register an OF thermal cooling device + * @np: a pointer to a device tree node. + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * It also gives the opportunity to link the cooling device to a device tree + * node, so that it can be bound to a thermal zone created out of device tree. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +thermal_of_cooling_device_register(struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_cooling_device_alloc(type, ops); + if (IS_ERR(cdev)) + return cdev; + + cdev->np = np; + + ret = thermal_cooling_device_add(cdev, devdata); + if (ret) + return ERR_PTR(ret); + + return cdev; +} +EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); + +static void thermal_of_cooling_device_release(void *data) +{ + struct thermal_cooling_device *cdev = data; + + thermal_cooling_device_unregister(cdev); +} + +/** + * devm_thermal_of_cooling_device_register() - register an OF thermal cooling + * device + * @dev: a valid struct device pointer of a sensor device. + * @np: a pointer to a device tree node. + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device with device tree node reference. + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_of_cooling_device_register(np, type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev); + if (ret) + return ERR_PTR(ret); + + return cdev; +} +EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index fc3f4a098370..3e663267a19d 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -198,6 +198,15 @@ struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, in void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz); +struct thermal_cooling_device * +thermal_of_cooling_device_register(struct device_node *np, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); + +struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); #else static inline @@ -211,6 +220,23 @@ static inline void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz) { } + +static inline struct thermal_cooling_device * +thermal_of_cooling_device_register(struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return ERR_PTR(-ENODEV); +} + +static inline struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return ERR_PTR(-ENODEV); +} #endif int for_each_thermal_trip(struct thermal_zone_device *tz, @@ -252,17 +278,11 @@ void thermal_zone_device_update(struct thermal_zone_device *, struct thermal_cooling_device *thermal_cooling_device_register(const char *, void *, const struct thermal_cooling_device_ops *); + struct thermal_cooling_device * devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, const struct thermal_cooling_device_ops *ops); -struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops); -struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops); + void thermal_cooling_device_update(struct thermal_cooling_device *); void thermal_cooling_device_unregister(struct thermal_cooling_device *); struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name); @@ -308,18 +328,7 @@ thermal_cooling_device_register(const char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { return ERR_PTR(-ENODEV); } static inline struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) -{ return ERR_PTR(-ENODEV); } -static inline struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) -{ - return ERR_PTR(-ENODEV); -} + static inline void thermal_cooling_device_unregister( struct thermal_cooling_device *cdev) { } From 5fa8b4225bec1fde0862a2d19964429662841384 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:05 +0200 Subject: [PATCH 42/54] thermal/core: Make cooling device OF node conditional on CONFIG_THERMAL_OF The device node pointer stored in struct thermal_cooling_device is only used by the OF-specific thermal code to associate cooling devices with thermal zones defined in device tree. Now that OF and non-OF registration paths are separated and non-OF users no longer rely on devm_thermal_of_cooling_device_register() with a NULL device node, the np field is no longer required for non-OF configurations. Make this field conditional on CONFIG_THERMAL_OF to reduce memory footprint and better reflect its usage. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Acked-by: Rafael J. Wysocki (Intel) Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260526140802.1059293-16-daniel.lezcano@oss.qualcomm.com --- include/linux/thermal.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 3e663267a19d..6d1862ac187f 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -125,7 +125,6 @@ struct thermal_cooling_device { const char *type; unsigned long max_state; struct device device; - struct device_node *np; void *devdata; void *stats; const struct thermal_cooling_device_ops *ops; @@ -133,6 +132,9 @@ struct thermal_cooling_device { struct mutex lock; /* protect thermal_instances list */ struct list_head thermal_instances; struct list_head node; +#ifdef CONFIG_THERMAL_OF + struct device_node *np; +#endif #ifdef CONFIG_THERMAL_DEBUGFS struct thermal_debugfs *debugfs; #endif From 8e1529e79385608002e126730d32bd91d7427795 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:07 +0200 Subject: [PATCH 43/54] thermal/of: Rename the devm_thermal_of_cooling_device_register() function To clarify that the function operates on child nodes, rename: devm_thermal_of_cooling_device_register() | v devm_thermal_of_child_cooling_device_register() Used the command: find . -type f -name '*.[ch]' -exec \ sed -i 's/devm_thermal_of_cooling_device_register/\ devm_thermal_of_child_cooling_device_register/g' {} \; Did not used clang-format-diff because it does not indent correctly and checkpatch complained. Manually reindented to make checkpatch happy This prepares for upcoming support of cooling devices identified by an ID rather than device tree child nodes. No functional change. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-18-daniel.lezcano@oss.qualcomm.com --- drivers/hwmon/amc6821.c | 2 +- drivers/hwmon/aspeed-pwm-tacho.c | 5 +++-- drivers/hwmon/emc2305.c | 6 +++--- drivers/hwmon/gpio-fan.c | 6 ++++-- drivers/hwmon/max6650.c | 6 +++--- drivers/hwmon/npcm750-pwm-fan.c | 6 ++++-- drivers/hwmon/pwm-fan.c | 5 +++-- drivers/hwmon/qnap-mcu-hwmon.c | 6 +++--- drivers/hwmon/tc654.c | 5 +++-- drivers/memory/tegra/tegra210-emc-core.c | 4 ++-- drivers/soc/qcom/qcom_aoss.c | 2 +- drivers/thermal/khadas_mcu_fan.c | 7 ++++--- drivers/thermal/tegra/soctherm.c | 6 +++--- drivers/thermal/thermal_of.c | 15 +++++++++------ include/linux/thermal.h | 16 ++++++++-------- 15 files changed, 54 insertions(+), 43 deletions(-) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index d5f864b360b0..8e5926b06070 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -1076,7 +1076,7 @@ static int amc6821_probe(struct i2c_client *client) "Failed to initialize hwmon\n"); if (IS_ENABLED(CONFIG_THERMAL) && fan_np && data->fan_cooling_levels) - return PTR_ERR_OR_ZERO(devm_thermal_of_cooling_device_register(dev, + return PTR_ERR_OR_ZERO(devm_thermal_of_child_cooling_device_register(dev, fan_np, client->name, data, &amc6821_cooling_ops)); return 0; diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index aa159bf158a3..1c5945d4ba37 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -841,8 +841,9 @@ static int aspeed_create_pwm_cooling(struct device *dev, } snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%pOFn%d", child, pwm_port); - cdev->tcdev = devm_thermal_of_cooling_device_register(dev, child, - cdev->name, cdev, &aspeed_pwm_cool_ops); + cdev->tcdev = devm_thermal_of_child_cooling_device_register(dev, child, + cdev->name, cdev, + &aspeed_pwm_cool_ops); if (IS_ERR(cdev->tcdev)) return PTR_ERR(cdev->tcdev); diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 64b213e1451e..2505e9fac499 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -309,9 +309,9 @@ static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_nod pwm = data->pwm_min[cdev_idx]; data->cdev_data[cdev_idx].cdev = - devm_thermal_of_cooling_device_register(dev, fan_node, - emc2305_fan_name[idx], data, - &emc2305_cooling_ops); + devm_thermal_of_child_cooling_device_register(dev, fan_node, + emc2305_fan_name[idx], data, + &emc2305_cooling_ops); if (IS_ERR(data->cdev_data[cdev_idx].cdev)) { dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]); diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index a8892ced1e54..084828e1e281 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -592,8 +592,10 @@ static int gpio_fan_probe(struct platform_device *pdev) } /* Optional cooling device register for Device tree platforms */ - fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np, - "gpio-fan", fan_data, &gpio_fan_cool_ops); + fan_data->cdev = devm_thermal_of_child_cooling_device_register(dev, np, + "gpio-fan", + fan_data, + &gpio_fan_cool_ops); dev_info(dev, "GPIO fan initialized\n"); diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index 56b8157885bb..3466edd7d501 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -794,9 +794,9 @@ static int max6650_probe(struct i2c_client *client) return err; if (IS_ENABLED(CONFIG_THERMAL)) { - cooling_dev = devm_thermal_of_cooling_device_register(dev, - dev->of_node, client->name, - data, &max6650_cooling_ops); + cooling_dev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node, + client->name, data, + &max6650_cooling_ops); if (IS_ERR(cooling_dev)) { dev_warn(dev, "thermal cooling device register failed: %ld\n", PTR_ERR(cooling_dev)); diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c index c8f5e695fb6d..aea0b8659f5f 100644 --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -857,8 +857,10 @@ static int npcm7xx_create_pwm_cooling(struct device *dev, snprintf(cdev->name, THERMAL_NAME_LENGTH, "%pOFn%d", child, pwm_port); - cdev->tcdev = devm_thermal_of_cooling_device_register(dev, child, - cdev->name, cdev, &npcm7xx_pwm_cool_ops); + cdev->tcdev = devm_thermal_of_child_cooling_device_register(dev, child, + cdev->name, + cdev, + &npcm7xx_pwm_cool_ops); if (IS_ERR(cdev->tcdev)) return PTR_ERR(cdev->tcdev); diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 37269db2de84..e6a567d58579 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -685,8 +685,9 @@ static int pwm_fan_probe(struct platform_device *pdev) ctx->pwm_fan_state = ctx->pwm_fan_max_state; if (IS_ENABLED(CONFIG_THERMAL)) { - cdev = devm_thermal_of_cooling_device_register(dev, - dev->of_node, "pwm-fan", ctx, &pwm_fan_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node, + "pwm-fan", ctx, + &pwm_fan_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); dev_err(dev, diff --git a/drivers/hwmon/qnap-mcu-hwmon.c b/drivers/hwmon/qnap-mcu-hwmon.c index e86e64c4d391..c1c1e9d6f340 100644 --- a/drivers/hwmon/qnap-mcu-hwmon.c +++ b/drivers/hwmon/qnap-mcu-hwmon.c @@ -337,9 +337,9 @@ static int qnap_mcu_hwmon_probe(struct platform_device *pdev) * levels and only succeed with either no or correct cooling levels. */ if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) { - cdev = devm_thermal_of_cooling_device_register(dev, - to_of_node(hwm->fan_node), "qnap-mcu-hwmon", - hwm, &qnap_mcu_hwmon_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev, to_of_node(hwm->fan_node), + "qnap-mcu-hwmon", hwm, + &qnap_mcu_hwmon_cooling_ops); if (IS_ERR(cdev)) return dev_err_probe(dev, PTR_ERR(cdev), "Failed to register qnap-mcu-hwmon as cooling device\n"); diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c index 39fe5836f237..ba18b442b81e 100644 --- a/drivers/hwmon/tc654.c +++ b/drivers/hwmon/tc654.c @@ -541,8 +541,9 @@ static int tc654_probe(struct i2c_client *client) if (IS_ENABLED(CONFIG_THERMAL)) { struct thermal_cooling_device *cdev; - cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, client->name, - hwmon_dev, &tc654_fan_cool_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node, + client->name, hwmon_dev, + &tc654_fan_cool_ops); return PTR_ERR_OR_ZERO(cdev); } diff --git a/drivers/memory/tegra/tegra210-emc-core.c b/drivers/memory/tegra/tegra210-emc-core.c index e96ca4157d48..065ae8bc2830 100644 --- a/drivers/memory/tegra/tegra210-emc-core.c +++ b/drivers/memory/tegra/tegra210-emc-core.c @@ -1966,8 +1966,8 @@ static int tegra210_emc_probe(struct platform_device *pdev) tegra210_emc_debugfs_init(emc); - cd = devm_thermal_of_cooling_device_register(emc->dev, np, "emc", emc, - &tegra210_emc_cd_ops); + cd = devm_thermal_of_child_cooling_device_register(emc->dev, np, "emc", emc, + &tegra210_emc_cd_ops); if (IS_ERR(cd)) { err = PTR_ERR(cd); dev_err(emc->dev, "failed to register cooling device: %d\n", diff --git a/drivers/soc/qcom/qcom_aoss.c b/drivers/soc/qcom/qcom_aoss.c index c255662b8fc3..259c41f0c34e 100644 --- a/drivers/soc/qcom/qcom_aoss.c +++ b/drivers/soc/qcom/qcom_aoss.c @@ -381,7 +381,7 @@ static int qmp_cooling_device_add(struct qmp *qmp, qmp_cdev->qmp = qmp; qmp_cdev->state = !qmp_cdev_max_state; qmp_cdev->name = cdev_name; - qmp_cdev->cdev = devm_thermal_of_cooling_device_register + qmp_cdev->cdev = devm_thermal_of_child_cooling_device_register (qmp->dev, node, cdev_name, qmp_cdev, &qmp_cooling_device_ops); diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c index d35e5313bea4..21b3d0a71bd0 100644 --- a/drivers/thermal/khadas_mcu_fan.c +++ b/drivers/thermal/khadas_mcu_fan.c @@ -90,9 +90,10 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev) ctx->mcu = mcu; platform_set_drvdata(pdev, ctx); - cdev = devm_thermal_of_cooling_device_register(dev->parent, - dev->parent->of_node, "khadas-mcu-fan", ctx, - &khadas_mcu_fan_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev->parent, + dev->parent->of_node, + "khadas-mcu-fan", ctx, + &khadas_mcu_fan_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n", diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index 6a56638c98f1..d8e988a0d43e 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -1707,9 +1707,9 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) stc->init = true; } else { - tcd = devm_thermal_of_cooling_device_register(dev, np_stcc, - (char *)name, ts, - &throt_cooling_ops); + tcd = devm_thermal_of_child_cooling_device_register(dev, np_stcc, + (char *)name, ts, + &throt_cooling_ops); if (IS_ERR_OR_NULL(tcd)) { dev_err(dev, "throttle-cfg: %s: failed to register cooling device\n", diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 8c49d449d43f..b59d2588ff7a 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -557,7 +557,7 @@ static void thermal_of_cooling_device_release(void *data) } /** - * devm_thermal_of_cooling_device_register() - register an OF thermal cooling + * devm_thermal_of_child_cooling_device_register() - register an OF thermal cooling * device * @dev: a valid struct device pointer of a sensor device. * @np: a pointer to a device tree node. @@ -570,14 +570,17 @@ static void thermal_of_cooling_device_release(void *data) * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself * to all the thermal zone devices registered at the same time. * + * This function should be used when a cooling controller has child + * nodes which are referenced in the thermal zone cooling map. + * * Return: a pointer to the created struct thermal_cooling_device or an * ERR_PTR. Caller must check return value with IS_ERR*() helpers. */ struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; int ret; @@ -592,4 +595,4 @@ devm_thermal_of_cooling_device_register(struct device *dev, return cdev; } -EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); +EXPORT_SYMBOL_GPL(devm_thermal_of_child_cooling_device_register); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 6d1862ac187f..e6328234a42b 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -205,10 +205,10 @@ thermal_of_cooling_device_register(struct device_node *np, const char *type, voi const struct thermal_cooling_device_ops *ops); struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops); +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); #else static inline @@ -232,10 +232,10 @@ thermal_of_cooling_device_register(struct device_node *np, } static inline struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { return ERR_PTR(-ENODEV); } From 37324803f049bee0d2b97941e3fbdf35db9a66b3 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:08 +0200 Subject: [PATCH 44/54] thermal/of: Add cooling device ID support Introduce an identifier (cdev_id) for cooling devices registered from device tree. This prepares support for a new DT binding where cooling devices are identified by a tuple (device node, ID), instead of relying on child nodes. Existing users are updated to pass a default ID of 0, preserving the current behavior. Future changes will extend the cooling map parsing to match cooling devices based on both the device node and the ID. No functional change intended. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-19-daniel.lezcano@oss.qualcomm.com --- drivers/gpu/drm/etnaviv/etnaviv_gpu.c | 5 +++-- drivers/thermal/cpufreq_cooling.c | 2 +- drivers/thermal/cpuidle_cooling.c | 2 +- drivers/thermal/devfreq_cooling.c | 2 +- drivers/thermal/thermal_of.c | 14 ++++++++------ include/linux/thermal.h | 6 ++++-- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c index a891d4f1f843..552631c3554a 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c @@ -1791,8 +1791,9 @@ static int etnaviv_gpu_bind(struct device *dev, struct device *master, int ret; if (IS_ENABLED(CONFIG_DRM_ETNAVIV_THERMAL)) { - gpu->cooling = thermal_of_cooling_device_register(dev->of_node, - (char *)dev_name(dev), gpu, &cooling_ops); + gpu->cooling = thermal_of_cooling_device_register(dev->of_node, 0, + dev_name(dev), + gpu, &cooling_ops); if (IS_ERR(gpu->cooling)) return PTR_ERR(gpu->cooling); } diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 32bf5ab44f4a..768859a7aed0 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -592,7 +592,7 @@ __cpufreq_cooling_register(struct device_node *np, if (!name) goto remove_qos_req; - cdev = thermal_of_cooling_device_register(np, name, cpufreq_cdev, + cdev = thermal_of_cooling_device_register(np, 0, name, cpufreq_cdev, cooling_ops); kfree(name); diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c index 425f596614e8..bbd2e91cf5ab 100644 --- a/drivers/thermal/cpuidle_cooling.c +++ b/drivers/thermal/cpuidle_cooling.c @@ -207,7 +207,7 @@ static int __cpuidle_cooling_register(struct device_node *np, goto out_unregister; } - cdev = thermal_of_cooling_device_register(np, name, idle_cdev, + cdev = thermal_of_cooling_device_register(np, 0, name, idle_cdev, &cpuidle_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 1c7dffc8d45f..0330a8112832 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -454,7 +454,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, if (!name) goto remove_qos_req; - cdev = thermal_of_cooling_device_register(np, name, dfc, ops); + cdev = thermal_of_cooling_device_register(np, 0, name, dfc, ops); kfree(name); if (IS_ERR(cdev)) { diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index b59d2588ff7a..0110b195f7a3 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -514,6 +514,7 @@ EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister); /** * thermal_of_cooling_device_register() - register an OF thermal cooling device * @np: a pointer to a device tree node. + * @cdev_id: a cooling device id in the cooling controller * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. @@ -528,9 +529,9 @@ EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister); * ERR_PTR. Caller must check return value with IS_ERR*() helpers. */ struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; int ret; @@ -540,6 +541,7 @@ thermal_of_cooling_device_register(struct device_node *np, return cdev; cdev->np = np; + cdev->cdev_id = cdev_id; ret = thermal_cooling_device_add(cdev, devdata); if (ret) @@ -585,9 +587,9 @@ devm_thermal_of_child_cooling_device_register(struct device *dev, struct thermal_cooling_device *cdev; int ret; - cdev = thermal_of_cooling_device_register(np, type, devdata, ops); - if (IS_ERR(cdev)) - return cdev; + cdev = thermal_of_cooling_device_register(np, 0, type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev); if (ret) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index e6328234a42b..fb7649439dfa 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -134,6 +134,7 @@ struct thermal_cooling_device { struct list_head node; #ifdef CONFIG_THERMAL_OF struct device_node *np; + u32 cdev_id; #endif #ifdef CONFIG_THERMAL_DEBUGFS struct thermal_debugfs *debugfs; @@ -201,7 +202,8 @@ struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, in void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz); struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, const char *type, void *devdata, +thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, + const char *type, void *data, const struct thermal_cooling_device_ops *ops); struct thermal_cooling_device * @@ -224,7 +226,7 @@ static inline void devm_thermal_of_zone_unregister(struct device *dev, } static inline struct thermal_cooling_device * -thermal_of_cooling_device_register(struct device_node *np, +thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, const char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { From 3570cb58e3171c8a65d2cedc0371ed412e0caff6 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:09 +0200 Subject: [PATCH 45/54] thermal/of: Pass cdev_id and introduce devm registration helper Extend the OF cooling device registration to support an explicit cooling device identifier (cdev_id), preparing for upcoming DT bindings where cooling devices are identified by a tuple (device node, id) instead of relying on child nodes. Introduce a new helper: devm_thermal_of_cooling_device_register() which registers a cooling device using the device's of_node and an explicit cdev_id. This complements the existing devm_thermal_of_child_cooling_device_register() helper, which remains dedicated to the legacy child-node based bindings. Internally, factorize the devm registration logic into a common helper to avoid code duplication. Existing users are unaffected, as the child-based helper continues to pass a default cdev_id of 0, preserving current behavior. This change is a preparatory step for supporting indexed cooling devices in thermal OF bindings. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-20-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/thermal_of.c | 58 ++++++++++++++++++++++++++++-------- include/linux/thermal.h | 13 ++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0110b195f7a3..3584024b76f5 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -558,6 +558,51 @@ static void thermal_of_cooling_device_release(void *data) thermal_cooling_device_unregister(cdev); } +static struct thermal_cooling_device * +__devm_thermal_of_cooling_device_register(struct device *dev, struct device_node *np, + u32 cdev_id, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + int ret; + + cdev = thermal_of_cooling_device_register(np, cdev_id, type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev); + if (ret) + return ERR_PTR(ret); + + return cdev; +} + +/** + * devm_thermal_of_cooling_device_register() - register an OF thermal cooling device + * @dev: a valid struct device pointer of a sensor device. + * @cdev_id: a cooling device index in the cooling controller + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * This function will register a cooling device with device tree node reference. + * This interface function adds a new thermal cooling device (fan/processor/...) + * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself + * to all the thermal zone devices registered at the same time. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return __devm_thermal_of_cooling_device_register(dev, dev->of_node, cdev_id, + type, devdata, ops); +} +EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); + /** * devm_thermal_of_child_cooling_device_register() - register an OF thermal cooling * device @@ -584,17 +629,6 @@ devm_thermal_of_child_cooling_device_register(struct device *dev, const char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { - struct thermal_cooling_device *cdev; - int ret; - - cdev = thermal_of_cooling_device_register(np, 0, type, devdata, ops); - if (IS_ERR(cdev)) - return cdev; - - ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev); - if (ret) - return ERR_PTR(ret); - - return cdev; + return __devm_thermal_of_cooling_device_register(dev, np, 0, type, devdata, ops); } EXPORT_SYMBOL_GPL(devm_thermal_of_child_cooling_device_register); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index fb7649439dfa..81be6e6061b3 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -206,6 +206,11 @@ thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, const char *type, void *data, const struct thermal_cooling_device_ops *ops); +struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); + struct thermal_cooling_device * devm_thermal_of_child_cooling_device_register(struct device *dev, struct device_node *np, @@ -233,6 +238,14 @@ thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, return ERR_PTR(-ENODEV); } +static inline struct thermal_cooling_device * +devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return ERR_PTR(-ENODEV); +} + static inline struct thermal_cooling_device * devm_thermal_of_child_cooling_device_register(struct device *dev, struct device_node *np, From 2baee1cc03c65a35f0d053e5c5e646a3bdf6ed85 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 26 May 2026 16:08:10 +0200 Subject: [PATCH 46/54] thermal/of: Support cooling device ID in cooling-spec Extend the cooling device specifier parsing to support an optional cooling device identifier (cdev_id). Two formats are now supported: - Legacy format: <&cdev lower upper> - Indexed format: <&cdev cdev_id lower upper> When the indexed format is used, both the device node and the cdev_id must match in order to bind a cooling device to a thermal zone. The legacy format continues to match on the device node only, preserving backward compatibility. Update the parsing logic accordingly to handle both formats and extract the mitigation limits from the appropriate arguments. This is a preparatory step for upcoming DT bindings describing cooling devices using (device node, id) tuples instead of child nodes. No functional change for existing device trees. Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-21-daniel.lezcano@oss.qualcomm.com --- drivers/thermal/thermal_of.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 3584024b76f5..100fd8a0c8ce 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -259,16 +259,34 @@ static bool thermal_of_get_cooling_spec(struct device_node *map_np, int index, of_node_put(cooling_spec.np); - if (cooling_spec.args_count < 2) { - pr_err("wrong reference to cooling device, missing limits\n"); + /* + * There are two formats: + * - Legacy format : <&cdev lower upper> + * - New format : <&cdev cdev_id lower upper> + * + * With the new format, along with the device node pointer, + * the cdev_id must match with the cooling device cdev_id in + * order to bind + */ + if (cooling_spec.args_count < 2 || cooling_spec.args_count > 3) { + pr_err("Invalid number of cooling device parameters\n"); return false; } if (cooling_spec.np != cdev->np) return false; - c->lower = cooling_spec.args[0]; - c->upper = cooling_spec.args[1]; + if (cooling_spec.args_count == 3 && + cooling_spec.args[0] != cdev->cdev_id) + return false; + + if (cooling_spec.args_count != 3) { + c->lower = cooling_spec.args[0]; + c->upper = cooling_spec.args[1]; + } else { + c->lower = cooling_spec.args[1]; + c->upper = cooling_spec.args[2]; + } c->weight = weight; return true; From 6d0c207c0a95bc4832fe0189973f64734ef5bfc0 Mon Sep 17 00:00:00 2001 From: Gaurav Kohli Date: Tue, 26 May 2026 16:08:11 +0200 Subject: [PATCH 47/54] dt-bindings: thermal: cooling-devices: Update support for 3 cells cooling device Extend the thermal cooling device binding to support a 3 cells specifier along with the 2 cells format. Update #cooling-cells property to enum to support both 2 and 3 arguments. Fix pwm-fan.yaml to restrict the number of cells to 'const: 2' Signed-off-by: Gaurav Kohli Signed-off-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Lukasz Luba Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260526140802.1059293-22-daniel.lezcano@oss.qualcomm.com --- Documentation/devicetree/bindings/hwmon/pwm-fan.yaml | 3 ++- .../bindings/thermal/thermal-cooling-devices.yaml | 8 ++++++-- .../devicetree/bindings/thermal/thermal-zones.yaml | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml b/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml index a84cc3a4cfdc..6a24851fd80d 100644 --- a/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml +++ b/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml @@ -63,7 +63,8 @@ properties: description: The PWM that is used to control the fan. maxItems: 1 - "#cooling-cells": true + "#cooling-cells": + const: 2 required: - compatible diff --git a/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml b/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml index b9022f1613d8..28f5818f1e60 100644 --- a/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml +++ b/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml @@ -44,10 +44,14 @@ select: true properties: "#cooling-cells": description: - Must be 2, in order to specify minimum and maximum cooling state used in + Must be 2 or 3. If 2, specifies minimum and maximum cooling state used in the cooling-maps reference. The first cell is the minimum cooling state and the second cell is the maximum cooling state requested. - const: 2 + If 3, the first cell specifies the thermal mitigation device specifier + index for devices that support multiple thermal mitigation mechanisms. + The two other cells are respectively the minimum cooling state and the + maximum cooling state. + enum: [2, 3] additionalProperties: true diff --git a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml index 07d9f576ffe7..999ad40a20d5 100644 --- a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml +++ b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml @@ -211,7 +211,8 @@ patternProperties: device. Using the THERMAL_NO_LIMIT (-1UL) constant in the cooling-device phandle limit specifier lets the framework use the minimum and maximum cooling state for that cooling - device automatically. + device automatically. If three arguments are specified, + the first argument is the cooling device specifier. contribution: $ref: /schemas/types.yaml#/definitions/uint32 From 995b736ad46a80af84e58596c5e23ce94035bddf Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 1 Jun 2026 11:01:53 +0200 Subject: [PATCH 48/54] thermal/core: Fix missing stub for devm_thermal_cooling_device_register Even it is very unlikely the thermal framework is disabled, the newly added devm_thermal_cooling_device_register() function has not the stub when the thermal framework is optout in the kernel. Add it. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605301554.S9n45bfQ-lkp@intel.com/ Signed-off-by: Daniel Lezcano Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260601090152.1243983-2-daniel.lezcano@kernel.org --- include/linux/thermal.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 81be6e6061b3..083b4f533933 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -344,7 +344,11 @@ static inline struct thermal_cooling_device * thermal_cooling_device_register(const char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { return ERR_PTR(-ENODEV); } + static inline struct thermal_cooling_device * +devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ return ERR_PTR(-ENODEV); } static inline void thermal_cooling_device_unregister( struct thermal_cooling_device *cdev) From c665de5eeb85d1a2b87c1bb4bf4d3dbd8c1c4c37 Mon Sep 17 00:00:00 2001 From: Priyansh Jain Date: Mon, 1 Jun 2026 12:07:56 +0530 Subject: [PATCH 49/54] thermal/drivers/qcom/tsens: Switch wake IRQ handling to PM callbacks This change improves power management by using the standardized PM framework for wake IRQ handling. Move wake IRQ control to the PM suspend/resume path: - store uplow/critical IRQ numbers in struct tsens_priv - enable wake IRQs in tsens_suspend_common() when wakeup is allowed - disable wake IRQs in tsens_resume_common() - mark the device wakeup-capable during probe This aligns TSENS wake behavior with suspend flow and avoids keeping wake IRQs permanently enabled during runtime. Signed-off-by: Priyansh Jain Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260601-tsens_interrupt_wake_control-v2-1-ce9570946abd@oss.qualcomm.com --- drivers/thermal/qcom/tsens-v2.c | 1 - drivers/thermal/qcom/tsens.c | 64 ++++++++++++++++++++++++++------- drivers/thermal/qcom/tsens.h | 18 +++++++++- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c index 8d9698ea3ec4..e06f8e5802e8 100644 --- a/drivers/thermal/qcom/tsens-v2.c +++ b/drivers/thermal/qcom/tsens-v2.c @@ -263,7 +263,6 @@ static int __init init_tsens_v2_no_rpm(struct tsens_priv *priv) static const struct tsens_ops ops_generic_v2 = { .init = init_common, .get_temp = get_temp_tsens_valid, - .resume = tsens_resume_common, }; struct tsens_plat_data data_tsens_v2 = { diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index cf7fc0d57a54..78d12c247fb9 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1129,22 +1129,30 @@ static int tsens_get_temp(struct thermal_zone_device *tz, int *temp) static int __maybe_unused tsens_suspend(struct device *dev) { + int ret = 0; struct tsens_priv *priv = dev_get_drvdata(dev); - if (priv->ops && priv->ops->suspend) - return priv->ops->suspend(priv); + if (priv->ops && priv->ops->suspend) { + ret = priv->ops->suspend(priv); + if (ret) + return ret; + } - return 0; + return tsens_suspend_common(priv); } static int __maybe_unused tsens_resume(struct device *dev) { + int ret = 0; struct tsens_priv *priv = dev_get_drvdata(dev); - if (priv->ops && priv->ops->resume) - return priv->ops->resume(priv); + if (priv->ops && priv->ops->resume) { + ret = priv->ops->resume(priv); + if (ret) + return ret; + } - return 0; + return tsens_resume_common(priv); } static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); @@ -1215,7 +1223,7 @@ static const struct thermal_zone_device_ops tsens_of_ops = { }; static int tsens_register_irq(struct tsens_priv *priv, char *irqname, - irq_handler_t thread_fn) + irq_handler_t thread_fn, int *irq_num) { struct platform_device *pdev; int ret, irq; @@ -1248,7 +1256,7 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, dev_err(&pdev->dev, "%s: failed to get irq\n", __func__); else - enable_irq_wake(irq); + *irq_num = irq; } put_device(&pdev->dev); @@ -1275,11 +1283,38 @@ static int tsens_reinit(struct tsens_priv *priv) return 0; } +int tsens_suspend_common(struct tsens_priv *priv) +{ + if (!device_may_wakeup(priv->dev)) + return 0; + + if (priv->feat->combo_int) + enable_irq_wake(priv->combined_irq); + else { + enable_irq_wake(priv->uplow_irq); + if (priv->feat->crit_int) + enable_irq_wake(priv->crit_irq); + } + + return 0; +} + int tsens_resume_common(struct tsens_priv *priv) { if (pm_suspend_target_state == PM_SUSPEND_MEM) tsens_reinit(priv); + if (!device_may_wakeup(priv->dev)) + return 0; + + if (priv->feat->combo_int) + disable_irq_wake(priv->combined_irq); + else { + disable_irq_wake(priv->uplow_irq); + if (priv->feat->crit_int) + disable_irq_wake(priv->crit_irq); + } + return 0; } @@ -1319,15 +1354,18 @@ static int tsens_register(struct tsens_priv *priv) if (priv->feat->combo_int) { ret = tsens_register_irq(priv, "combined", - tsens_combined_irq_thread); + tsens_combined_irq_thread, &priv->combined_irq); } else { - ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); + ret = tsens_register_irq(priv, "uplow", tsens_irq_thread, + &priv->uplow_irq); if (ret < 0) return ret; - if (priv->feat->crit_int) + if (priv->feat->crit_int) { ret = tsens_register_irq(priv, "critical", - tsens_critical_irq_thread); + tsens_critical_irq_thread, + &priv->crit_irq); + } } return ret; @@ -1386,6 +1424,8 @@ static int tsens_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); + device_init_wakeup(dev, true); + if (!priv->ops || !priv->ops->init || !priv->ops->get_temp) return -EINVAL; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index ab57ad88c3f7..206ee2d5d301 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -568,6 +568,9 @@ struct tsens_context { * @ops: pointer to list of callbacks supported by this device * @debug_root: pointer to debugfs dentry for all tsens * @debug: pointer to debugfs dentry for tsens controller + * @uplow_irq: IRQ number for uplow (upper/lower) threshold interrupts + * @crit_irq: IRQ number for critical threshold interrupts + * @combined_irq: IRQ number for combined threshold interrupts * @sensor: list of sensors attached to this device */ struct tsens_priv { @@ -589,6 +592,10 @@ struct tsens_priv { struct dentry *debug_root; struct dentry *debug; + int uplow_irq; + int crit_irq; + int combined_irq; + struct tsens_sensor sensor[] __counted_by(num_sensors); }; @@ -640,8 +647,17 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp); int get_temp_common(const struct tsens_sensor *s, int *temp); #ifdef CONFIG_SUSPEND int tsens_resume_common(struct tsens_priv *priv); +int tsens_suspend_common(struct tsens_priv *priv); #else -#define tsens_resume_common NULL +static inline int tsens_resume_common(struct tsens_priv *priv) +{ + return 0; +} + +static inline int tsens_suspend_common(struct tsens_priv *priv) +{ + return 0; +} #endif /* TSENS target */ From 968098b4ca5219b0d2e0a981aed1dacfbd5adc69 Mon Sep 17 00:00:00 2001 From: Priyansh Jain Date: Mon, 1 Jun 2026 12:07:57 +0530 Subject: [PATCH 50/54] thermal/drivers/qcom/tsens: Disable wakeup interrupt setup on automotive targets Add a no_irq_wake flag to struct tsens_plat_data to allow platforms to control whether TSENS interrupts should be configured as wakeup sources. Create a new data_automotive structure and add compatible strings for automotive TSENS variants (SA8775P, SA8255P) with wakeup interrupts disabled. Automotive platforms can enter a low-power parking suspend state where the application processors and thermal mitigation paths are not active. In this state, waking the system due to TSENS threshold interrupts does not enable useful thermal action, but it does repeatedly break suspend residency and increase battery drain. Allow these automotive variants to keep TSENS monitoring enabled during normal runtime while opting out of TSENS wakeup interrupts during suspend, so the system can remain in low power until ignition/resume. Signed-off-by: Priyansh Jain Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260601-tsens_interrupt_wake_control-v2-2-ce9570946abd@oss.qualcomm.com --- drivers/thermal/qcom/tsens-v2.c | 8 ++++++++ drivers/thermal/qcom/tsens.c | 8 +++++++- drivers/thermal/qcom/tsens.h | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c index e06f8e5802e8..2ee117aa91ba 100644 --- a/drivers/thermal/qcom/tsens-v2.c +++ b/drivers/thermal/qcom/tsens-v2.c @@ -306,3 +306,11 @@ struct tsens_plat_data data_8996 = { .feat = &tsens_v2_feat, .fields = tsens_v2_regfields, }; + +/* Do not enable wakeup capable interrupts for automotive platforms */ +struct tsens_plat_data data_automotive_v2 = { + .ops = &ops_generic_v2, + .feat = &tsens_v2_feat, + .fields = tsens_v2_regfields, + .no_irq_wake = true, +}; diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 78d12c247fb9..6e3714ecab1d 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1212,6 +1212,12 @@ static const struct of_device_id tsens_table[] = { }, { .compatible = "qcom,tsens-v2", .data = &data_tsens_v2, + }, { + .compatible = "qcom,sa8775p-tsens", + .data = &data_automotive_v2, + }, { + .compatible = "qcom,sa8255p-tsens", + .data = &data_automotive_v2, }, {} }; @@ -1424,7 +1430,7 @@ static int tsens_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - device_init_wakeup(dev, true); + device_init_wakeup(dev, !data->no_irq_wake); if (!priv->ops || !priv->ops->init || !priv->ops->get_temp) return -EINVAL; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 206ee2d5d301..e8376accdff3 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -532,6 +532,7 @@ struct tsens_features { * @hw_ids: Subset of sensors ids supported by platform, if not the first n * @feat: features of the IP * @fields: bitfield locations + * @no_irq_wake: if set, TSENS interrupts will not be configured as wakeup sources */ struct tsens_plat_data { const u32 num_sensors; @@ -539,6 +540,7 @@ struct tsens_plat_data { unsigned int *hw_ids; struct tsens_features *feat; const struct reg_field *fields; + bool no_irq_wake; }; /** @@ -676,4 +678,7 @@ extern const struct tsens_plat_data data_ipq5018; extern struct tsens_plat_data data_8996, data_ipq8074, data_tsens_v2; extern const struct tsens_plat_data data_ipq5332, data_ipq5424; +/* TSENS automotive targets */ +extern struct tsens_plat_data data_automotive_v2; + #endif /* __QCOM_TSENS_H__ */ From f2f2ebd675c5518115286969d6daa3ff45306d83 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Fri, 5 Jun 2026 10:30:54 -0700 Subject: [PATCH 51/54] thermal: intel: intel_tcc_cooling: Add Arrow Lake CPU models Add Arrow Lake CPU models to the support list. Signed-off-by: Srinivas Pandruvada [ rjw: Changelog tweak ] Link: https://patch.msgid.link/20260605173054.2050476-1-srinivas.pandruvada@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_tcc_cooling.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c index 6c2ba0ba3178..52ea4f7fc9f9 100644 --- a/drivers/thermal/intel/intel_tcc_cooling.c +++ b/drivers/thermal/intel/intel_tcc_cooling.c @@ -65,6 +65,9 @@ static const struct x86_cpu_id tcc_ids[] __initconst = { X86_MATCH_VFM(INTEL_RAPTORLAKE, NULL), X86_MATCH_VFM(INTEL_RAPTORLAKE_P, NULL), X86_MATCH_VFM(INTEL_RAPTORLAKE_S, NULL), + X86_MATCH_VFM(INTEL_ARROWLAKE, NULL), + X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL), + X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), X86_MATCH_VFM(INTEL_NOVALAKE, NULL), From ef3e98b0aa4b348f44065d7130251273c83bd204 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 5 Jun 2026 18:52:06 +0000 Subject: [PATCH 52/54] thermal: testing: reject missing command arguments The thermal testing debugfs command parser splits commands at ':' and passes the right-hand side to the command implementation. Commands such as deltz, tzaddtrip, tzreg, and tzunreg require a zone id, but writing one of those command names without ':' leaves the argument pointer NULL. The command implementations parse the id with sscanf(arg, "%d", ...), so the missing-argument form dereferences a NULL pointer from the debugfs write path. Reject missing arguments in tt_command_exec() before calling handlers that require an id. Fixes: f6a034f2df42 ("thermal: Introduce a debugfs-based testing facility") Assisted-by: Codex:gpt-5.5-cyber-preview Signed-off-by: Samuel Moelius Link: https://patch.msgid.link/20260605185212.2491144-1-sam.moelius@trailofbits.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/testing/command.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/thermal/testing/command.c b/drivers/thermal/testing/command.c index 1159ecea57e7..fbf7ab9729b5 100644 --- a/drivers/thermal/testing/command.c +++ b/drivers/thermal/testing/command.c @@ -116,18 +116,30 @@ static int tt_command_exec(int index, const char *arg) break; case TT_CMD_DELTZ: + if (!arg || !*arg) + return -EINVAL; + ret = tt_del_tz(arg); break; case TT_CMD_TZADDTRIP: + if (!arg || !*arg) + return -EINVAL; + ret = tt_zone_add_trip(arg); break; case TT_CMD_TZREG: + if (!arg || !*arg) + return -EINVAL; + ret = tt_zone_reg(arg); break; case TT_CMD_TZUNREG: + if (!arg || !*arg) + return -EINVAL; + ret = tt_zone_unreg(arg); break; From e4f87cfcbae03498f0fd1689653cf84126196e14 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Sun, 7 Jun 2026 00:04:19 +0300 Subject: [PATCH 53/54] thermal: testing: Replace sscanf() with kstrtoint() Generally, kstrtoint() is preferred to sscanf() in kernel code, so replace the latter with the former in tt_del_tz() and tt_get_tt_zone(). Signed-off-by: Ovidiu Panait [ rjw: Changelog rewrite ] Link: https://patch.msgid.link/20260606210420.2311145-2-ovidiu.panait.oss@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/testing/zone.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/testing/zone.c b/drivers/thermal/testing/zone.c index 3c339242f52d..f7f9ca2f1f2c 100644 --- a/drivers/thermal/testing/zone.c +++ b/drivers/thermal/testing/zone.c @@ -239,9 +239,9 @@ int tt_del_tz(const char *arg) int ret; int id; - ret = sscanf(arg, "%d", &id); - if (ret != 1) - return -EINVAL; + ret = kstrtoint(arg, 10, &id); + if (ret < 0) + return ret; struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work); if (!tt_work) @@ -279,9 +279,9 @@ static struct tt_thermal_zone *tt_get_tt_zone(const char *arg) struct tt_thermal_zone *tt_zone; int ret, id; - ret = sscanf(arg, "%d", &id); - if (ret != 1) - return ERR_PTR(-EINVAL); + ret = kstrtoint(arg, 10, &id); + if (ret < 0) + return ERR_PTR(ret); guard(mutex)(&tt_thermal_zones_lock); From 64762d48ec84d36fc2618920a731368387253efc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Sun, 7 Jun 2026 00:04:20 +0300 Subject: [PATCH 54/54] thermal: sysfs: Replace sscanf() with kstrtoul() Replace sscanf() with kstrtoul() in cur_state_store(), as kstrto is preferred over single-variable sscanf(). Signed-off-by: Ovidiu Panait [ rjw: Changelog edits ] Link: https://patch.msgid.link/20260606210420.2311145-3-ovidiu.panait.oss@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_sysfs.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index 9f2f25a6da37..b44abfc997ed 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -536,11 +536,9 @@ cur_state_store(struct device *dev, struct device_attribute *attr, unsigned long state; int result; - if (sscanf(buf, "%ld\n", &state) != 1) - return -EINVAL; - - if ((long)state < 0) - return -EINVAL; + result = kstrtoul(buf, 10, &state); + if (result < 0) + return result; /* Requested state should be less than max_state + 1 */ if (state > cdev->max_state)