Add a new generic driver for thermal cooling devices that control
remote processors (modem, DSP, etc.) through various communication
channels.
This driver provides an abstraction layer between the thermal
subsystem and vendor-specific remote processor communication
mechanisms.
Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
MAINTAINERS | 8 ++
drivers/thermal/Kconfig | 11 ++
drivers/thermal/Makefile | 2 +
drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
include/linux/remoteproc_cooling.h | 52 +++++++++
5 files changed, 227 insertions(+)
create mode 100644 drivers/thermal/remoteproc_cooling.c
create mode 100644 include/linux/remoteproc_cooling.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 679e5f11e672..c1ba87315cdf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
F: drivers/thermal/cpuidle_cooling.c
F: include/linux/cpu_cooling.h
+THERMAL/REMOTEPROC_COOLING
+M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
+L: linux-pm@vger.kernel.org
+S: Supported
+F: drivers/thermal/remoteproc_cooling.c
+F: include/linux/remoteproc_cooling.h
+
+
THERMAL/POWER_ALLOCATOR
M: Lukasz Luba <lukasz.luba@arm.com>
L: linux-pm@vger.kernel.org
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index b10080d61860..31e92be34387 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -229,6 +229,17 @@ config PCIE_THERMAL
If you want this support, you should say Y here.
+
+config REMOTEPROC_THERMAL
+ bool "Remote processor cooling support"
+ help
+ This implements a generic cooling mechanism for remote processors
+ (modem, DSP, etc.) that allows vendor-specific implementations to
+ register thermal cooling devices and provide callbacks for thermal
+ mitigation.
+
+ If you want this support, you should say Y here.
+
config THERMAL_EMULATION
bool "Thermal emulation mode support"
help
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index bb21e7ea7fc6..ae747dde54fe 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
+thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
+
obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
# platform thermal drivers
obj-y += broadcom/
diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
new file mode 100644
index 000000000000..a1f948cbde0f
--- /dev/null
+++ b/drivers/thermal/remoteproc_cooling.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Remote Processor Cooling Device
+ *
+ * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define REMOTEPROC_PREFIX "rproc_"
+
+struct remoteproc_cooling_ops {
+ int (*get_max_level)(void *devdata, unsigned long *level);
+ int (*get_cur_level)(void *devdata, unsigned long *level);
+ int (*set_cur_level)(void *devdata, unsigned long level);
+};
+
+/**
+ * struct remoteproc_cdev - Remote processor cooling device
+ * @cdev: Thermal cooling device handle
+ * @ops: Vendor-specific operation callbacks
+ * @devdata: Private data for vendor implementation
+ * @np: Device tree node associated with this cooling device
+ * @lock: Mutex to protect cooling device operations
+ */
+struct remoteproc_cdev {
+ struct thermal_cooling_device *cdev;
+ const struct remoteproc_cooling_ops *ops;
+ void *devdata;
+ struct device_node *np;
+ struct mutex lock;
+};
+
+
+/* Thermal cooling device callbacks */
+
+static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct remoteproc_cdev *rproc_cdev = cdev->devdata;
+ int ret;
+
+ if (!rproc_cdev || !rproc_cdev->ops)
+ return -EINVAL;
+
+ mutex_lock(&rproc_cdev->lock);
+ ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
+ mutex_unlock(&rproc_cdev->lock);
+
+ return ret;
+}
+
+static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct remoteproc_cdev *rproc_cdev = cdev->devdata;
+ int ret;
+
+ if (!rproc_cdev || !rproc_cdev->ops)
+ return -EINVAL;
+
+ mutex_lock(&rproc_cdev->lock);
+ ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
+ mutex_unlock(&rproc_cdev->lock);
+
+ return ret;
+}
+
+static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct remoteproc_cdev *rproc_cdev = cdev->devdata;
+ int ret;
+
+ if (!rproc_cdev || !rproc_cdev->ops)
+ return -EINVAL;
+
+ mutex_lock(&rproc_cdev->lock);
+ ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
+ mutex_unlock(&rproc_cdev->lock);
+
+ return ret;
+}
+
+static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
+ .get_max_state = remoteproc_get_max_state,
+ .get_cur_state = remoteproc_get_cur_state,
+ .set_cur_state = remoteproc_set_cur_state,
+};
+
+struct remoteproc_cdev *
+remoteproc_cooling_register(struct device_node *np,
+ const char *name, const struct remoteproc_cooling_ops *ops,
+ void *devdata)
+{
+ struct remoteproc_cdev *rproc_cdev;
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ if (!name || !ops) {
+ return ERR_PTR(-EINVAL);
+ }
+
+ rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
+ if (!rproc_cdev)
+ return ERR_PTR(-ENOMEM);
+
+ rproc_cdev->ops = ops;
+ rproc_cdev->devdata = devdata;
+ rproc_cdev->np = np;
+ mutex_init(&rproc_cdev->lock);
+
+ char *rproc_name __free(kfree) =
+ kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
+ /* Register with thermal framework */
+ if (np) {
+ cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
+ &remoteproc_cooling_ops);
+ }
+
+ if (IS_ERR(cdev)) {
+ ret = PTR_ERR(cdev);
+ goto free_rproc_cdev;
+ }
+
+ rproc_cdev->cdev = cdev;
+
+ return rproc_cdev;
+
+free_rproc_cdev:
+ kfree(rproc_cdev);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
+
+void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
+{
+ if (!rproc_cdev)
+ return;
+
+ thermal_cooling_device_unregister(rproc_cdev->cdev);
+ mutex_destroy(&rproc_cdev->lock);
+ kfree(rproc_cdev);
+}
+EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Remote Processor Cooling Device");
diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
new file mode 100644
index 000000000000..ef94019d220d
--- /dev/null
+++ b/include/linux/remoteproc_cooling.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Remote Processor Cooling Device
+ *
+ * Copyright (c) 2025, Qualcomm Innovation Center
+ */
+
+#ifndef __REMOTEPROC_COOLING_H__
+#define __REMOTEPROC_COOLING_H__
+
+#include <linux/thermal.h>
+
+struct device;
+struct device_node;
+
+struct remoteproc_cooling_ops {
+ int (*get_max_level)(void *devdata, unsigned long *level);
+ int (*get_cur_level)(void *devdata, unsigned long *level);
+ int (*set_cur_level)(void *devdata, unsigned long level);
+};
+
+struct remoteproc_cdev;
+
+#ifdef CONFIG_REMOTEPROC_THERMAL
+
+struct remoteproc_cdev *
+remoteproc_cooling_register(struct device_node *np,
+ const char *name,
+ const struct remoteproc_cooling_ops *ops,
+ void *devdata);
+
+void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
+
+#else /* !CONFIG_REMOTEPROC_THERMAL */
+
+static inline struct remoteproc_cdev *
+remoteproc_cooling_register(struct device_node *np,
+ const char *name,
+ const struct remoteproc_cooling_ops *ops,
+ void *devdata)
+{
+ return ERR_PTR(-EINVAL);
+}
+
+static inline void
+remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
+{
+}
+
+#endif /* CONFIG_REMOTEPROC_THERMAL */
+
+#endif /* __REMOTEPROC_COOLING_H__ */
--
2.34.1
Hi Gaurav,
On 12/23/25 12:32, Gaurav Kohli wrote:
> Add a new generic driver for thermal cooling devices that control
> remote processors (modem, DSP, etc.) through various communication
> channels.
>
> This driver provides an abstraction layer between the thermal
> subsystem and vendor-specific remote processor communication
> mechanisms.
Is this the patch about proposing the new cooling
device type at last LPC2025 conference (what we've discussed with Amit)?
There was some feedback asking you to add a bit more description
into this patch header, please do that (with some background as well).
>
> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> ---
> MAINTAINERS | 8 ++
> drivers/thermal/Kconfig | 11 ++
> drivers/thermal/Makefile | 2 +
> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
> include/linux/remoteproc_cooling.h | 52 +++++++++
> 5 files changed, 227 insertions(+)
> create mode 100644 drivers/thermal/remoteproc_cooling.c
> create mode 100644 include/linux/remoteproc_cooling.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 679e5f11e672..c1ba87315cdf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
> F: drivers/thermal/cpuidle_cooling.c
> F: include/linux/cpu_cooling.h
>
> +THERMAL/REMOTEPROC_COOLING
> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> +L: linux-pm@vger.kernel.org
> +S: Supported
> +F: drivers/thermal/remoteproc_cooling.c
> +F: include/linux/remoteproc_cooling.h
> +
> +
> THERMAL/POWER_ALLOCATOR
> M: Lukasz Luba <lukasz.luba@arm.com>
> L: linux-pm@vger.kernel.org
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index b10080d61860..31e92be34387 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>
> If you want this support, you should say Y here.
>
> +
> +config REMOTEPROC_THERMAL
> + bool "Remote processor cooling support"
> + help
> + This implements a generic cooling mechanism for remote processors
> + (modem, DSP, etc.) that allows vendor-specific implementations to
> + register thermal cooling devices and provide callbacks for thermal
> + mitigation.
> +
> + If you want this support, you should say Y here.
> +
> config THERMAL_EMULATION
> bool "Thermal emulation mode support"
> help
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index bb21e7ea7fc6..ae747dde54fe 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>
> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>
> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
> +
> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
> # platform thermal drivers
> obj-y += broadcom/
> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
> new file mode 100644
> index 000000000000..a1f948cbde0f
> --- /dev/null
> +++ b/drivers/thermal/remoteproc_cooling.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/thermal.h>
> +
> +#define REMOTEPROC_PREFIX "rproc_"
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
1. There is no comment for struct and the functions like you did below.
2. Why you need those 3 callbacks?
It looks like they are simple wrappers on stuff in
'struct thermal_cooling_device_ops'.
Please try to get rid of them and re-use the existing fwk callbacks.
> +
> +/**
> + * struct remoteproc_cdev - Remote processor cooling device
> + * @cdev: Thermal cooling device handle
> + * @ops: Vendor-specific operation callbacks
> + * @devdata: Private data for vendor implementation
> + * @np: Device tree node associated with this cooling device
> + * @lock: Mutex to protect cooling device operations
> + */
> +struct remoteproc_cdev {
Please use the full naming:
remoteproc_cooling_device
> + struct thermal_cooling_device *cdev;
You don't need to keep it here. AFAICS it's only
used in the 'unregister' function. Please check my
comment here and then remove this pointer.
(It creates uneseccery linkage between those devices).
> + const struct remoteproc_cooling_ops *ops;
So here it can be simply:
struct thermal_cooling_device_ops cooling_ops;
> + void *devdata;
> + struct device_node *np;
This 'np' is also not used, remove it please.
> + struct mutex lock;
> +};
> +
> +
> +/* Thermal cooling device callbacks */
> +
> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
This mustn't be changed in runtime accidenly. We don't guard in
cpufreq-/devfreq- cooling these callbacks that way. Please drop them.
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
as above
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
as above
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
> + .get_max_state = remoteproc_get_max_state,
> + .get_cur_state = remoteproc_get_cur_state,
> + .set_cur_state = remoteproc_set_cur_state,
> +};
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name, const struct remoteproc_cooling_ops *ops,
> + void *devdata)
Since this is based on device_node, please align to the naming
convention from cpufreq-/devfreq-cooling and use prefix 'of_'
of_remoteproc_cooling_register()
> +{
> + struct remoteproc_cdev *rproc_cdev;
> + struct thermal_cooling_device *cdev;
> + int ret;
> +
> + if (!name || !ops) {
IMO you should check the '!np' here, not the lines below.
We can simply bail out very early.
> + return ERR_PTR(-EINVAL);
> + }
> +
> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
> + if (!rproc_cdev)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc_cdev->ops = ops;
> + rproc_cdev->devdata = devdata;
> + rproc_cdev->np = np;
> + mutex_init(&rproc_cdev->lock);
> +
> + char *rproc_name __free(kfree) =
> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
> + /* Register with thermal framework */
> + if (np) {
Too late to test 'np'.
> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
> + &remoteproc_cooling_ops);
> + }
> +
> + if (IS_ERR(cdev)) {
This check should be just below the line when you get the 'cdev'
> + ret = PTR_ERR(cdev);
> + goto free_rproc_cdev;
> + }
> +
> + rproc_cdev->cdev = cdev;
> +
> + return rproc_cdev;
> +
> +free_rproc_cdev:
> + kfree(rproc_cdev);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
Change the API to be alined with cpufreq-cooling and devfreq-cooling
types of devices, so:
void remoteproc_cooling_unregister(struct thermal_cooling_device *cdev)
You still should be able to get the rptoc_cdev like:
rproc_cdev = cdev->devdata;
and free it.
> +{
> + if (!rproc_cdev)
> + return;
> +
> + thermal_cooling_device_unregister(rproc_cdev->cdev);
> + mutex_destroy(&rproc_cdev->lock);
> + kfree(rproc_cdev);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
> new file mode 100644
> index 000000000000..ef94019d220d
> --- /dev/null
> +++ b/include/linux/remoteproc_cooling.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center
> + */
> +
> +#ifndef __REMOTEPROC_COOLING_H__
> +#define __REMOTEPROC_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +struct device;
> +struct device_node;
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
That duplicate w/ .c file content.
We don't need this in the header, please follow the cpufreq-/devfreq-
design.
> +
> +struct remoteproc_cdev;
> +
> +#ifdef CONFIG_REMOTEPROC_THERMAL
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
> +
> +#else /* !CONFIG_REMOTEPROC_THERMAL */
> +
> +static inline struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + return ERR_PTR(-EINVAL);
> +}
Function naming convention here as well
Regards,
Lukasz
On 2/2/2026 4:29 PM, Lukasz Luba wrote:
> Hi Gaurav,
>
> On 12/23/25 12:32, Gaurav Kohli wrote:
>> Add a new generic driver for thermal cooling devices that control
>> remote processors (modem, DSP, etc.) through various communication
>> channels.
>>
>> This driver provides an abstraction layer between the thermal
>> subsystem and vendor-specific remote processor communication
>> mechanisms.
>
> Is this the patch about proposing the new cooling
> device type at last LPC2025 conference (what we've discussed with Amit)?
>
thanks Lukasz for review, yes this is the same.
sorry for late reply, was on leave last week.
> There was some feedback asking you to add a bit more description
> into this patch header, please do that (with some background as well).
>
Sure, will update.
>>
>> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
>> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> ---
>> MAINTAINERS | 8 ++
>> drivers/thermal/Kconfig | 11 ++
>> drivers/thermal/Makefile | 2 +
>> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
>> include/linux/remoteproc_cooling.h | 52 +++++++++
>> 5 files changed, 227 insertions(+)
>> create mode 100644 drivers/thermal/remoteproc_cooling.c
>> create mode 100644 include/linux/remoteproc_cooling.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 679e5f11e672..c1ba87315cdf 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
>> F: drivers/thermal/cpuidle_cooling.c
>> F: include/linux/cpu_cooling.h
>> +THERMAL/REMOTEPROC_COOLING
>> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> +L: linux-pm@vger.kernel.org
>> +S: Supported
>> +F: drivers/thermal/remoteproc_cooling.c
>> +F: include/linux/remoteproc_cooling.h
>> +
>> +
>> THERMAL/POWER_ALLOCATOR
>> M: Lukasz Luba <lukasz.luba@arm.com>
>> L: linux-pm@vger.kernel.org
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index b10080d61860..31e92be34387 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>> If you want this support, you should say Y here.
>> +
>> +config REMOTEPROC_THERMAL
>> + bool "Remote processor cooling support"
>> + help
>> + This implements a generic cooling mechanism for remote processors
>> + (modem, DSP, etc.) that allows vendor-specific implementations to
>> + register thermal cooling devices and provide callbacks for thermal
>> + mitigation.
>> +
>> + If you want this support, you should say Y here.
>> +
>> config THERMAL_EMULATION
>> bool "Thermal emulation mode support"
>> help
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index bb21e7ea7fc6..ae747dde54fe 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) +=
>> devfreq_cooling.o
>> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
>> +
>> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
>> # platform thermal drivers
>> obj-y += broadcom/
>> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/
>> remoteproc_cooling.c
>> new file mode 100644
>> index 000000000000..a1f948cbde0f
>> --- /dev/null
>> +++ b/drivers/thermal/remoteproc_cooling.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#include <linux/err.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of.h>
>> +#include <linux/slab.h>
>> +#include <linux/thermal.h>
>> +
>> +#define REMOTEPROC_PREFIX "rproc_"
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>
> 1. There is no comment for struct and the functions like you did below.
> 2. Why you need those 3 callbacks?
> It looks like they are simple wrappers on stuff in
> 'struct thermal_cooling_device_ops'.
> Please try to get rid of them and re-use the existing fwk callbacks.
>
thanks for this suggestion, i will use thermal_cooling_device_ops directly.
>> +
>> +/**
>> + * struct remoteproc_cdev - Remote processor cooling device
>> + * @cdev: Thermal cooling device handle
>> + * @ops: Vendor-specific operation callbacks
>> + * @devdata: Private data for vendor implementation
>> + * @np: Device tree node associated with this cooling device
>> + * @lock: Mutex to protect cooling device operations
>> + */
>> +struct remoteproc_cdev {
>
> Please use the full naming:
> remoteproc_cooling_device
>
>> + struct thermal_cooling_device *cdev;
>
> You don't need to keep it here. AFAICS it's only
> used in the 'unregister' function. Please check my
> comment here and then remove this pointer.
> (It creates uneseccery linkage between those devices).
>
>> + const struct remoteproc_cooling_ops *ops;
>
> So here it can be simply:
> struct thermal_cooling_device_ops cooling_ops;
>
yes, i will use this as part of remoteproc_cooling_device struct.
>> + void *devdata;
>> + struct device_node *np;
>
> This 'np' is also not used, remove it please.
>
>> + struct mutex lock;
>> +};
>> +
>> +
>> +/* Thermal cooling device callbacks */
>> +
>> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>
> This mustn't be changed in runtime accidenly. We don't guard in
> cpufreq-/devfreq- cooling these callbacks that way. Please drop them.
Sure, let me rewrite this and update in next version.
>
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>
> as above
>
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
>> + unsigned long state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>
> as above
>
will fix in all callbacks.
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops
>> = {
>> + .get_max_state = remoteproc_get_max_state,
>> + .get_cur_state = remoteproc_get_cur_state,
>> + .set_cur_state = remoteproc_set_cur_state,
>> +};
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name, const struct
>> remoteproc_cooling_ops *ops,
>> + void *devdata)
>
> Since this is based on device_node, please align to the naming
> convention from cpufreq-/devfreq-cooling and use prefix 'of_'
>
> of_remoteproc_cooling_register()
>
>> +{
>> + struct remoteproc_cdev *rproc_cdev;
>> + struct thermal_cooling_device *cdev;
>> + int ret;
>> +
>> + if (!name || !ops) {
>
> IMO you should check the '!np' here, not the lines below.
> We can simply bail out very early.
>
thanks will put explicit check for np, but please let me know for non
np, do we have to add support for non np also.
so they can directly register with thermal_cooling_device_register.
>> + return ERR_PTR(-EINVAL);
>> + }
>> +
>> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
>> + if (!rproc_cdev)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + rproc_cdev->ops = ops;
>> + rproc_cdev->devdata = devdata;
>> + rproc_cdev->np = np;
>> + mutex_init(&rproc_cdev->lock);
>> +
>> + char *rproc_name __free(kfree) =
>> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
>> + /* Register with thermal framework */
>> + if (np) {
>
> Too late to test 'np'.
yes, will put a check in the start of function.
>
>> + cdev = thermal_of_cooling_device_register(np, rproc_name,
>> rproc_cdev,
>> + &remoteproc_cooling_ops);
>> + }
>> +
>> + if (IS_ERR(cdev)) {
>
> This check should be just below the line when you get the 'cdev'
>
>> + ret = PTR_ERR(cdev);
>> + goto free_rproc_cdev;
>> + }
>> +
>> + rproc_cdev->cdev = cdev;
>> +
>> + return rproc_cdev;
>> +
>> +free_rproc_cdev:
>> + kfree(rproc_cdev);
>> + return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>
> Change the API to be alined with cpufreq-cooling and devfreq-cooling
> types of devices, so:
>
> void remoteproc_cooling_unregister(struct thermal_cooling_device *cdev)
>
> You still should be able to get the rptoc_cdev like:
>
> rproc_cdev = cdev->devdata;
>
> and free it.
>
thanks, will change something like below
+ rproc_cdev = cdev->devdata;
+ thermal_cooling_device_unregister(cdev);
>> +{
>> + if (!rproc_cdev)
>> + return;
>> +
>> + thermal_cooling_device_unregister(rproc_cdev->cdev);
>> + mutex_destroy(&rproc_cdev->lock);
>> + kfree(rproc_cdev);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
>> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/
>> remoteproc_cooling.h
>> new file mode 100644
>> index 000000000000..ef94019d220d
>> --- /dev/null
>> +++ b/include/linux/remoteproc_cooling.h
>> @@ -0,0 +1,52 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center
>> + */
>> +
>> +#ifndef __REMOTEPROC_COOLING_H__
>> +#define __REMOTEPROC_COOLING_H__
>> +
>> +#include <linux/thermal.h>
>> +
>> +struct device;
>> +struct device_node;
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>
> That duplicate w/ .c file content.
> We don't need this in the header, please follow the cpufreq-/devfreq-
> design.
>
Yes, with new approach of using thermal_cooling_device_ops directly can
save this.
>> +
>> +struct remoteproc_cdev;
>> +
>> +#ifdef CONFIG_REMOTEPROC_THERMAL
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
>> +
>> +#else /* !CONFIG_REMOTEPROC_THERMAL */
>> +
>> +static inline struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + return ERR_PTR(-EINVAL);
>> +}
>
> Function naming convention here as well
>
thanks a lot, let me rewrite as per suggestion and update in newer version.
>
> Regards,
> Lukasz
On 2/9/26 05:28, Gaurav Kohli wrote:
>
>
> On 2/2/2026 4:29 PM, Lukasz Luba wrote:
>> Hi Gaurav,
>>
>> On 12/23/25 12:32, Gaurav Kohli wrote:
>>> Add a new generic driver for thermal cooling devices that control
>>> remote processors (modem, DSP, etc.) through various communication
>>> channels.
>>>
>>> This driver provides an abstraction layer between the thermal
>>> subsystem and vendor-specific remote processor communication
>>> mechanisms.
>>
>> Is this the patch about proposing the new cooling
>> device type at last LPC2025 conference (what we've discussed with Amit)?
>>
>
> thanks Lukasz for review, yes this is the same.
> sorry for late reply, was on leave last week.
>
>> There was some feedback asking you to add a bit more description
>> into this patch header, please do that (with some background as well).
>>
>
> Sure, will update.
>
>>>
>>> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
>>> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>>> ---
[snip]
>>> +struct remoteproc_cooling_ops {
>>> + int (*get_max_level)(void *devdata, unsigned long *level);
>>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>>> + int (*set_cur_level)(void *devdata, unsigned long level);
>>> +};
>>
>> 1. There is no comment for struct and the functions like you did below.
>> 2. Why you need those 3 callbacks?
>> It looks like they are simple wrappers on stuff in
>> 'struct thermal_cooling_device_ops'.
>> Please try to get rid of them and re-use the existing fwk callbacks.
>>
>
> thanks for this suggestion, i will use thermal_cooling_device_ops directly.
>
>>> +
>>> +/**
>>> + * struct remoteproc_cdev - Remote processor cooling device
>>> + * @cdev: Thermal cooling device handle
>>> + * @ops: Vendor-specific operation callbacks
>>> + * @devdata: Private data for vendor implementation
>>> + * @np: Device tree node associated with this cooling device
>>> + * @lock: Mutex to protect cooling device operations
>>> + */
>>> +struct remoteproc_cdev {
>>
>> Please use the full naming:
>> remoteproc_cooling_device
>>
>>> + struct thermal_cooling_device *cdev;
>>
>> You don't need to keep it here. AFAICS it's only
>> used in the 'unregister' function. Please check my
>> comment here and then remove this pointer.
>> (It creates uneseccery linkage between those devices).
>>
>>> + const struct remoteproc_cooling_ops *ops;
>>
>> So here it can be simply:
>> struct thermal_cooling_device_ops cooling_ops;
>>
>
> yes, i will use this as part of remoteproc_cooling_device struct.
>
>>> + void *devdata;
>>> + struct device_node *np;
>>
>> This 'np' is also not used, remove it please.
>>
>>> + struct mutex lock;
>>> +};
>>> +
>>> +
>>> +/* Thermal cooling device callbacks */
>>> +
>>> +static int remoteproc_get_max_state(struct thermal_cooling_device
>>> *cdev,
>>> + unsigned long *state)
>>> +{
>>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>>> + int ret;
>>> +
>>> + if (!rproc_cdev || !rproc_cdev->ops)
>>> + return -EINVAL;
>>
>> This mustn't be changed in runtime accidenly. We don't guard in
>> cpufreq-/devfreq- cooling these callbacks that way. Please drop them.
>
> Sure, let me rewrite this and update in next version.
>
>>
[snip]
>>> +
>>> + if (!name || !ops) {
>>
>> IMO you should check the '!np' here, not the lines below.
>> We can simply bail out very early.
>>
>
> thanks will put explicit check for np, but please let me know for non
> np, do we have to add support for non np also.
If your code doesn't use the non-np then let's not implement it.
When there will be a new client, we can refactor slightly the existing
code and make two interfaces for the registration (similat to cpufreq
cooling).
> so they can directly register with thermal_cooling_device_register.
>
>>> + return ERR_PTR(-EINVAL);
>>> + }
>>> +
[snip]
>>> +
>>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>>
>> Change the API to be alined with cpufreq-cooling and devfreq-cooling
>> types of devices, so:
>>
>> void remoteproc_cooling_unregister(struct thermal_cooling_device *cdev)
>>
>> You still should be able to get the rptoc_cdev like:
>>
>> rproc_cdev = cdev->devdata;
>>
>> and free it.
>>
>
> thanks, will change something like below
> + rproc_cdev = cdev->devdata;
> + thermal_cooling_device_unregister(cdev);
Should work, let see in the new code.
[snip]
>>> +struct remoteproc_cooling_ops {
>>> + int (*get_max_level)(void *devdata, unsigned long *level);
>>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>>> + int (*set_cur_level)(void *devdata, unsigned long level);
>>> +};
>>
>> That duplicate w/ .c file content.
>> We don't need this in the header, please follow the cpufreq-/devfreq-
>> design.
>>
>
> Yes, with new approach of using thermal_cooling_device_ops directly can
> save this.
Great, looking for the the v2
>
>>> +
>>> +struct remoteproc_cdev;
>>> +
>>> +#ifdef CONFIG_REMOTEPROC_THERMAL
>>> +
>>> +struct remoteproc_cdev *
>>> +remoteproc_cooling_register(struct device_node *np,
>>> + const char *name,
>>> + const struct remoteproc_cooling_ops *ops,
>>> + void *devdata);
>>> +
>>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
>>> +
>>> +#else /* !CONFIG_REMOTEPROC_THERMAL */
>>> +
>>> +static inline struct remoteproc_cdev *
>>> +remoteproc_cooling_register(struct device_node *np,
>>> + const char *name,
>>> + const struct remoteproc_cooling_ops *ops,
>>> + void *devdata)
>>> +{
>>> + return ERR_PTR(-EINVAL);
>>> +}
>>
>> Function naming convention here as well
>>
>
> thanks a lot, let me rewrite as per suggestion and update in newer version.
>
you're welcome
On 12/23/2025 8:32 PM, Gaurav Kohli wrote:
> Add a new generic driver for thermal cooling devices that control
> remote processors (modem, DSP, etc.) through various communication
> channels.
>
> This driver provides an abstraction layer between the thermal
> subsystem and vendor-specific remote processor communication
> mechanisms.
>
> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> ---
> MAINTAINERS | 8 ++
> drivers/thermal/Kconfig | 11 ++
> drivers/thermal/Makefile | 2 +
> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
> include/linux/remoteproc_cooling.h | 52 +++++++++
> 5 files changed, 227 insertions(+)
> create mode 100644 drivers/thermal/remoteproc_cooling.c
> create mode 100644 include/linux/remoteproc_cooling.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 679e5f11e672..c1ba87315cdf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
> F: drivers/thermal/cpuidle_cooling.c
> F: include/linux/cpu_cooling.h
>
> +THERMAL/REMOTEPROC_COOLING
> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> +L: linux-pm@vger.kernel.org
> +S: Supported
> +F: drivers/thermal/remoteproc_cooling.c
> +F: include/linux/remoteproc_cooling.h
> +
> +
> THERMAL/POWER_ALLOCATOR
> M: Lukasz Luba <lukasz.luba@arm.com>
> L: linux-pm@vger.kernel.org
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index b10080d61860..31e92be34387 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>
> If you want this support, you should say Y here.
>
> +
> +config REMOTEPROC_THERMAL
> + bool "Remote processor cooling support"
Hi Gaurav,
May I know any depends here?
> + help
> + This implements a generic cooling mechanism for remote processors
> + (modem, DSP, etc.) that allows vendor-specific implementations to
> + register thermal cooling devices and provide callbacks for thermal
> + mitigation.
> +
> + If you want this support, you should say Y here.
> +
> config THERMAL_EMULATION
> bool "Thermal emulation mode support"
> help
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index bb21e7ea7fc6..ae747dde54fe 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>
> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>
> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
> +
> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
> # platform thermal drivers
> obj-y += broadcom/
> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
> new file mode 100644
> index 000000000000..a1f948cbde0f
> --- /dev/null
> +++ b/drivers/thermal/remoteproc_cooling.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/thermal.h>
> +
> +#define REMOTEPROC_PREFIX "rproc_"
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
It's better to have a document to explain member?
And may I know why double define here(another is in .h file)? should it
include .h file remoteproc_cooling.h ?
> +
> +/**
> + * struct remoteproc_cdev - Remote processor cooling device
> + * @cdev: Thermal cooling device handle
> + * @ops: Vendor-specific operation callbacks
> + * @devdata: Private data for vendor implementation
> + * @np: Device tree node associated with this cooling device
> + * @lock: Mutex to protect cooling device operations
> + */
> +struct remoteproc_cdev {
> + struct thermal_cooling_device *cdev;
> + const struct remoteproc_cooling_ops *ops;
> + void *devdata;
> + struct device_node *np;
> + struct mutex lock;
> +};
> +
> +
> +/* Thermal cooling device callbacks */
> +
> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
> + .get_max_state = remoteproc_get_max_state,
> + .get_cur_state = remoteproc_get_cur_state,
> + .set_cur_state = remoteproc_set_cur_state,
> +};
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name, const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + struct remoteproc_cdev *rproc_cdev;
> + struct thermal_cooling_device *cdev;
> + int ret;
> +
> + if (!name || !ops) {
> + return ERR_PTR(-EINVAL);
> + }
> +
May I know which ops callbacks are required and which are optional?
If the callback is optional, should we check for null before calling it?
> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
> + if (!rproc_cdev)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc_cdev->ops = ops;
> + rproc_cdev->devdata = devdata;
> + rproc_cdev->np = np;
> + mutex_init(&rproc_cdev->lock);
> +
> + char *rproc_name __free(kfree) =
> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
It should have a NULL check when alloc memory.
> + /* Register with thermal framework */
> + if (np) {
> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
> + &remoteproc_cooling_ops);
> + }
> +
> + if (IS_ERR(cdev)) {
> + ret = PTR_ERR(cdev);
> + goto free_rproc_cdev;
> + }
> +
> + rproc_cdev->cdev = cdev;
> +
> + return rproc_cdev;
> +
> +free_rproc_cdev:
> + kfree(rproc_cdev);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> + if (!rproc_cdev)
> + return;
> +
> + thermal_cooling_device_unregister(rproc_cdev->cdev);
> + mutex_destroy(&rproc_cdev->lock);
> + kfree(rproc_cdev);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
> new file mode 100644
> index 000000000000..ef94019d220d
> --- /dev/null
> +++ b/include/linux/remoteproc_cooling.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center
> + */
> +
> +#ifndef __REMOTEPROC_COOLING_H__
> +#define __REMOTEPROC_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +struct device;
> +struct device_node;
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
> +
> +struct remoteproc_cdev;
> +
> +#ifdef CONFIG_REMOTEPROC_THERMAL
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
> +
> +#else /* !CONFIG_REMOTEPROC_THERMAL */
> +
> +static inline struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + return ERR_PTR(-EINVAL);
> +}
> +
> +static inline void
> +remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> +}
> +
> +#endif /* CONFIG_REMOTEPROC_THERMAL */
> +
> +#endif /* __REMOTEPROC_COOLING_H__ */
--
Thx and BRs,
Zhongqiu Han
On 1/8/2026 5:29 PM, Zhongqiu Han wrote:
> On 12/23/2025 8:32 PM, Gaurav Kohli wrote:
>> Add a new generic driver for thermal cooling devices that control
>> remote processors (modem, DSP, etc.) through various communication
>> channels.
>>
>> This driver provides an abstraction layer between the thermal
>> subsystem and vendor-specific remote processor communication
>> mechanisms.
>>
>> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
>> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> ---
>> MAINTAINERS | 8 ++
>> drivers/thermal/Kconfig | 11 ++
>> drivers/thermal/Makefile | 2 +
>> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
>> include/linux/remoteproc_cooling.h | 52 +++++++++
>> 5 files changed, 227 insertions(+)
>> create mode 100644 drivers/thermal/remoteproc_cooling.c
>> create mode 100644 include/linux/remoteproc_cooling.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 679e5f11e672..c1ba87315cdf 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
>> F: drivers/thermal/cpuidle_cooling.c
>> F: include/linux/cpu_cooling.h
>> +THERMAL/REMOTEPROC_COOLING
>> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> +L: linux-pm@vger.kernel.org
>> +S: Supported
>> +F: drivers/thermal/remoteproc_cooling.c
>> +F: include/linux/remoteproc_cooling.h
>> +
>> +
>> THERMAL/POWER_ALLOCATOR
>> M: Lukasz Luba <lukasz.luba@arm.com>
>> L: linux-pm@vger.kernel.org
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index b10080d61860..31e92be34387 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>> If you want this support, you should say Y here.
>> +
>> +config REMOTEPROC_THERMAL
>> + bool "Remote processor cooling support"
>
> Hi Gaurav,
>
> May I know any depends here?
Thanks for review.
Apologies for the late reply, this was missed on my end.
This does not require a dependency, as error check is there for cooling
registration api.
>
>> + help
>> + This implements a generic cooling mechanism for remote processors
>> + (modem, DSP, etc.) that allows vendor-specific implementations to
>> + register thermal cooling devices and provide callbacks for
>> thermal
>> + mitigation.
>> +
>> + If you want this support, you should say Y here.
>> +
>> config THERMAL_EMULATION
>> bool "Thermal emulation mode support"
>> help
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index bb21e7ea7fc6..ae747dde54fe 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) +=
>> devfreq_cooling.o
>> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
>> +
>> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
>> # platform thermal drivers
>> obj-y += broadcom/
>> diff --git a/drivers/thermal/remoteproc_cooling.c
>> b/drivers/thermal/remoteproc_cooling.c
>> new file mode 100644
>> index 000000000000..a1f948cbde0f
>> --- /dev/null
>> +++ b/drivers/thermal/remoteproc_cooling.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> + */
>> +
>> +#include <linux/err.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of.h>
>> +#include <linux/slab.h>
>> +#include <linux/thermal.h>
>> +
>> +#define REMOTEPROC_PREFIX "rproc_"
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
> It's better to have a document to explain member?
>
> And may I know why double define here(another is in .h file)? should it
> include .h file remoteproc_cooling.h ?
>
Sure, i will add comment for each callback and will fix this double
definition.
>
>> +
>> +/**
>> + * struct remoteproc_cdev - Remote processor cooling device
>> + * @cdev: Thermal cooling device handle
>> + * @ops: Vendor-specific operation callbacks
>> + * @devdata: Private data for vendor implementation
>> + * @np: Device tree node associated with this cooling device
>> + * @lock: Mutex to protect cooling device operations
>> + */
>> +struct remoteproc_cdev {
>> + struct thermal_cooling_device *cdev;
>> + const struct remoteproc_cooling_ops *ops;
>> + void *devdata;
>> + struct device_node *np;
>> + struct mutex lock;
>> +};
>> +
>> +
>> +/* Thermal cooling device callbacks */
>> +
>> +static int remoteproc_get_max_state(struct thermal_cooling_device
>> *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_get_cur_state(struct thermal_cooling_device
>> *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_set_cur_state(struct thermal_cooling_device
>> *cdev,
>> + unsigned long state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct thermal_cooling_device_ops
>> remoteproc_cooling_ops = {
>> + .get_max_state = remoteproc_get_max_state,
>> + .get_cur_state = remoteproc_get_cur_state,
>> + .set_cur_state = remoteproc_set_cur_state,
>> +};
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name, const struct
>> remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + struct remoteproc_cdev *rproc_cdev;
>> + struct thermal_cooling_device *cdev;
>> + int ret;
>> +
>> + if (!name || !ops) {
>> + return ERR_PTR(-EINVAL);
>> + }
>> +
>
> May I know which ops callbacks are required and which are optional?
> If the callback is optional, should we check for null before calling it?
>
Actually all are mandatory for cooling api, will put null check for all
to be on safer side.
>
>> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
>> + if (!rproc_cdev)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + rproc_cdev->ops = ops;
>> + rproc_cdev->devdata = devdata;
>> + rproc_cdev->np = np;
>> + mutex_init(&rproc_cdev->lock);
>> +
>> + char *rproc_name __free(kfree) =
>> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
>
> It should have a NULL check when alloc memory.
>
>
Thanks for pointing this, will update this.
>> + /* Register with thermal framework */
>> + if (np) {
>> + cdev = thermal_of_cooling_device_register(np, rproc_name,
>> rproc_cdev,
>> + &remoteproc_cooling_ops);
>> + }
>> +
>> + if (IS_ERR(cdev)) {
>> + ret = PTR_ERR(cdev);
>> + goto free_rproc_cdev;
>> + }
>> +
>> + rproc_cdev->cdev = cdev;
>> +
>> + return rproc_cdev;
>> +
>> +free_rproc_cdev:
>> + kfree(rproc_cdev);
>> + return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>> +{
>> + if (!rproc_cdev)
>> + return;
>> +
>> + thermal_cooling_device_unregister(rproc_cdev->cdev);
>> + mutex_destroy(&rproc_cdev->lock);
>> + kfree(rproc_cdev);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
>> diff --git a/include/linux/remoteproc_cooling.h
>> b/include/linux/remoteproc_cooling.h
>> new file mode 100644
>> index 000000000000..ef94019d220d
>> --- /dev/null
>> +++ b/include/linux/remoteproc_cooling.h
>> @@ -0,0 +1,52 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center
>> + */
>> +
>> +#ifndef __REMOTEPROC_COOLING_H__
>> +#define __REMOTEPROC_COOLING_H__
>> +
>> +#include <linux/thermal.h>
>> +
>> +struct device;
>> +struct device_node;
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>> +
>> +struct remoteproc_cdev;
>> +
>> +#ifdef CONFIG_REMOTEPROC_THERMAL
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
>> +
>> +#else /* !CONFIG_REMOTEPROC_THERMAL */
>> +
>> +static inline struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + return ERR_PTR(-EINVAL);
>> +}
>> +
>> +static inline void
>> +remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>> +{
>> +}
>> +
>> +#endif /* CONFIG_REMOTEPROC_THERMAL */
>> +
>> +#endif /* __REMOTEPROC_COOLING_H__ */
>
>
On Tue, Dec 23, 2025 at 06:02:20PM +0530, Gaurav Kohli wrote:
> Add a new generic driver for thermal cooling devices that control
> remote processors (modem, DSP, etc.) through various communication
> channels.
>
> This driver provides an abstraction layer between the thermal
> subsystem and vendor-specific remote processor communication
> mechanisms.
>
> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> ---
> MAINTAINERS | 8 ++
> drivers/thermal/Kconfig | 11 ++
> drivers/thermal/Makefile | 2 +
> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
> include/linux/remoteproc_cooling.h | 52 +++++++++
> 5 files changed, 227 insertions(+)
> create mode 100644 drivers/thermal/remoteproc_cooling.c
> create mode 100644 include/linux/remoteproc_cooling.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 679e5f11e672..c1ba87315cdf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
> F: drivers/thermal/cpuidle_cooling.c
> F: include/linux/cpu_cooling.h
>
> +THERMAL/REMOTEPROC_COOLING
> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> +L: linux-pm@vger.kernel.org
> +S: Supported
> +F: drivers/thermal/remoteproc_cooling.c
> +F: include/linux/remoteproc_cooling.h
> +
> +
Two empty lines here...
> THERMAL/POWER_ALLOCATOR
> M: Lukasz Luba <lukasz.luba@arm.com>
> L: linux-pm@vger.kernel.org
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index b10080d61860..31e92be34387 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>
> If you want this support, you should say Y here.
>
> +
And two here...
> +config REMOTEPROC_THERMAL
> + bool "Remote processor cooling support"
> + help
> + This implements a generic cooling mechanism for remote processors
> + (modem, DSP, etc.) that allows vendor-specific implementations to
> + register thermal cooling devices and provide callbacks for thermal
> + mitigation.
> +
> + If you want this support, you should say Y here.
> +
> config THERMAL_EMULATION
> bool "Thermal emulation mode support"
> help
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index bb21e7ea7fc6..ae747dde54fe 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>
> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>
> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
> +
> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
> # platform thermal drivers
> obj-y += broadcom/
> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
> new file mode 100644
> index 000000000000..a1f948cbde0f
> --- /dev/null
> +++ b/drivers/thermal/remoteproc_cooling.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
No, that's not the right one.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/thermal.h>
> +
> +#define REMOTEPROC_PREFIX "rproc_"
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
> +
> +/**
> + * struct remoteproc_cdev - Remote processor cooling device
> + * @cdev: Thermal cooling device handle
> + * @ops: Vendor-specific operation callbacks
> + * @devdata: Private data for vendor implementation
> + * @np: Device tree node associated with this cooling device
> + * @lock: Mutex to protect cooling device operations
> + */
> +struct remoteproc_cdev {
> + struct thermal_cooling_device *cdev;
> + const struct remoteproc_cooling_ops *ops;
> + void *devdata;
> + struct device_node *np;
This is a local variable in remoteproc_cooling_register().
> + struct mutex lock;
> +};
> +
> +
> +/* Thermal cooling device callbacks */
> +
> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
How is this possible? Is there some race condition or something that you
need to guard against?
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
> + .get_max_state = remoteproc_get_max_state,
> + .get_cur_state = remoteproc_get_cur_state,
> + .set_cur_state = remoteproc_set_cur_state,
> +};
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name, const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + struct remoteproc_cdev *rproc_cdev;
> + struct thermal_cooling_device *cdev;
> + int ret;
> +
> + if (!name || !ops) {
> + return ERR_PTR(-EINVAL);
> + }
> +
> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
> + if (!rproc_cdev)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc_cdev->ops = ops;
> + rproc_cdev->devdata = devdata;
> + rproc_cdev->np = np;
> + mutex_init(&rproc_cdev->lock);
> +
> + char *rproc_name __free(kfree) =
> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
> + /* Register with thermal framework */
Technically it's "Optionally register with thermal framework" but that's
immediately understood by the name of the function you're calling. What
isn't clear, and where a comment would be beneficial would be to
document why it's optional (or rather conditional on np).
> + if (np) {
> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
> + &remoteproc_cooling_ops);
> + }
> +
> + if (IS_ERR(cdev)) {
> + ret = PTR_ERR(cdev);
> + goto free_rproc_cdev;
> + }
> +
> + rproc_cdev->cdev = cdev;
> +
> + return rproc_cdev;
> +
> +free_rproc_cdev:
> + kfree(rproc_cdev);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> + if (!rproc_cdev)
> + return;
> +
> + thermal_cooling_device_unregister(rproc_cdev->cdev);
> + mutex_destroy(&rproc_cdev->lock);
> + kfree(rproc_cdev);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
> new file mode 100644
> index 000000000000..ef94019d220d
> --- /dev/null
> +++ b/include/linux/remoteproc_cooling.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center
That was never the right one.
> + */
> +
> +#ifndef __REMOTEPROC_COOLING_H__
> +#define __REMOTEPROC_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +struct device;
> +struct device_node;
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
> +
> +struct remoteproc_cdev;
> +
> +#ifdef CONFIG_REMOTEPROC_THERMAL
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
> +
> +#else /* !CONFIG_REMOTEPROC_THERMAL */
> +
> +static inline struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + return ERR_PTR(-EINVAL);
This means that if I build my kernel with CONFIG_REMOTEPROC_THERMAL=n it
will not just disable remoteproc thermal management, but my remoteproc
driver will stop probing.
The remoteproc thermal feature isn't critical to the remtoeproc, so
wouldn't it make more sense to return 0 here and let the remoteproc
continue to operate in such configuration?
Regards,
Bjorn
> +}
> +
> +static inline void
> +remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> +}
> +
> +#endif /* CONFIG_REMOTEPROC_THERMAL */
> +
> +#endif /* __REMOTEPROC_COOLING_H__ */
> --
> 2.34.1
>
On 1/3/2026 8:35 PM, Bjorn Andersson wrote:
> On Tue, Dec 23, 2025 at 06:02:20PM +0530, Gaurav Kohli wrote:
>> Add a new generic driver for thermal cooling devices that control
>> remote processors (modem, DSP, etc.) through various communication
>> channels.
>>
>> This driver provides an abstraction layer between the thermal
>> subsystem and vendor-specific remote processor communication
>> mechanisms.
>>
>> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
>> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> ---
>> MAINTAINERS | 8 ++
>> drivers/thermal/Kconfig | 11 ++
>> drivers/thermal/Makefile | 2 +
>> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
>> include/linux/remoteproc_cooling.h | 52 +++++++++
>> 5 files changed, 227 insertions(+)
>> create mode 100644 drivers/thermal/remoteproc_cooling.c
>> create mode 100644 include/linux/remoteproc_cooling.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 679e5f11e672..c1ba87315cdf 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
>> F: drivers/thermal/cpuidle_cooling.c
>> F: include/linux/cpu_cooling.h
>>
>> +THERMAL/REMOTEPROC_COOLING
>> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> +L: linux-pm@vger.kernel.org
>> +S: Supported
>> +F: drivers/thermal/remoteproc_cooling.c
>> +F: include/linux/remoteproc_cooling.h
>> +
>> +
> Two empty lines here...
thanks, will fix.
>
>> THERMAL/POWER_ALLOCATOR
>> M: Lukasz Luba <lukasz.luba@arm.com>
>> L: linux-pm@vger.kernel.org
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index b10080d61860..31e92be34387 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>>
>> If you want this support, you should say Y here.
>>
>> +
> And two here...
>
>> +config REMOTEPROC_THERMAL
>> + bool "Remote processor cooling support"
>> + help
>> + This implements a generic cooling mechanism for remote processors
>> + (modem, DSP, etc.) that allows vendor-specific implementations to
>> + register thermal cooling devices and provide callbacks for thermal
>> + mitigation.
>> +
>> + If you want this support, you should say Y here.
>> +
>> config THERMAL_EMULATION
>> bool "Thermal emulation mode support"
>> help
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index bb21e7ea7fc6..ae747dde54fe 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>>
>> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>>
>> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
>> +
>> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
>> # platform thermal drivers
>> obj-y += broadcom/
>> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
>> new file mode 100644
>> index 000000000000..a1f948cbde0f
>> --- /dev/null
>> +++ b/drivers/thermal/remoteproc_cooling.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
> No, that's not the right one.
will update this.
>
>> + */
>> +
>> +#include <linux/err.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of.h>
>> +#include <linux/slab.h>
>> +#include <linux/thermal.h>
>> +
>> +#define REMOTEPROC_PREFIX "rproc_"
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>> +
>> +/**
>> + * struct remoteproc_cdev - Remote processor cooling device
>> + * @cdev: Thermal cooling device handle
>> + * @ops: Vendor-specific operation callbacks
>> + * @devdata: Private data for vendor implementation
>> + * @np: Device tree node associated with this cooling device
>> + * @lock: Mutex to protect cooling device operations
>> + */
>> +struct remoteproc_cdev {
>> + struct thermal_cooling_device *cdev;
>> + const struct remoteproc_cooling_ops *ops;
>> + void *devdata;
>> + struct device_node *np;
> This is a local variable in remoteproc_cooling_register().
>
>> + struct mutex lock;
>> +};
>> +
>> +
>> +/* Thermal cooling device callbacks */
>> +
>> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
> How is this possible? Is there some race condition or something that you
> need to guard against?
Ideally this can be controlled from the calling driver only, so i can
remove this.
>
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
>> + unsigned long state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
>> + .get_max_state = remoteproc_get_max_state,
>> + .get_cur_state = remoteproc_get_cur_state,
>> + .set_cur_state = remoteproc_set_cur_state,
>> +};
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name, const struct remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + struct remoteproc_cdev *rproc_cdev;
>> + struct thermal_cooling_device *cdev;
>> + int ret;
>> +
>> + if (!name || !ops) {
>> + return ERR_PTR(-EINVAL);
>> + }
>> +
>> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
>> + if (!rproc_cdev)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + rproc_cdev->ops = ops;
>> + rproc_cdev->devdata = devdata;
>> + rproc_cdev->np = np;
>> + mutex_init(&rproc_cdev->lock);
>> +
>> + char *rproc_name __free(kfree) =
>> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
>> + /* Register with thermal framework */
> Technically it's "Optionally register with thermal framework" but that's
> immediately understood by the name of the function you're calling. What
> isn't clear, and where a comment would be beneficial would be to
> document why it's optional (or rather conditional on np).
Sure will add comment.
>
>> + if (np) {
>> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
>> + &remoteproc_cooling_ops);
>> + }
>> +
>> + if (IS_ERR(cdev)) {
>> + ret = PTR_ERR(cdev);
>> + goto free_rproc_cdev;
>> + }
>> +
>> + rproc_cdev->cdev = cdev;
>> +
>> + return rproc_cdev;
>> +
>> +free_rproc_cdev:
>> + kfree(rproc_cdev);
>> + return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>> +{
>> + if (!rproc_cdev)
>> + return;
>> +
>> + thermal_cooling_device_unregister(rproc_cdev->cdev);
>> + mutex_destroy(&rproc_cdev->lock);
>> + kfree(rproc_cdev);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
>> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
>> new file mode 100644
>> index 000000000000..ef94019d220d
>> --- /dev/null
>> +++ b/include/linux/remoteproc_cooling.h
>> @@ -0,0 +1,52 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center
> That was never the right one.
>
>> + */
>> +
>> +#ifndef __REMOTEPROC_COOLING_H__
>> +#define __REMOTEPROC_COOLING_H__
>> +
>> +#include <linux/thermal.h>
>> +
>> +struct device;
>> +struct device_node;
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>> +
>> +struct remoteproc_cdev;
>> +
>> +#ifdef CONFIG_REMOTEPROC_THERMAL
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
>> +
>> +#else /* !CONFIG_REMOTEPROC_THERMAL */
>> +
>> +static inline struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + return ERR_PTR(-EINVAL);
> This means that if I build my kernel with CONFIG_REMOTEPROC_THERMAL=n it
> will not just disable remoteproc thermal management, but my remoteproc
> driver will stop probing.
>
> The remoteproc thermal feature isn't critical to the remtoeproc, so
> wouldn't it make more sense to return 0 here and let the remoteproc
> continue to operate in such configuration?
Yes it will work, as probe of this child node won't impact the remote
proc probe, as we are not returning error condition there during
of_platform_populate.
But yes, this should not block remote proc probe in any condition, so
return 0 is more better option. will update this.
>
> Regards,
> Bjorn
>
>> +}
>> +
>> +static inline void
>> +remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>> +{
>> +}
>> +
>> +#endif /* CONFIG_REMOTEPROC_THERMAL */
>> +
>> +#endif /* __REMOTEPROC_COOLING_H__ */
>> --
>> 2.34.1
>>
On Tue, Dec 23, 2025 at 06:02:20PM +0530, Gaurav Kohli wrote:
> Add a new generic driver for thermal cooling devices that control
> remote processors (modem, DSP, etc.) through various communication
> channels.
>
> This driver provides an abstraction layer between the thermal
> subsystem and vendor-specific remote processor communication
> mechanisms.
This driver simply wraps 3 callbacks. Please explain in the commit
message, why do we need it? Why can't the consumer of this API simply
provide those callbacks directly?
>
> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> ---
> MAINTAINERS | 8 ++
> drivers/thermal/Kconfig | 11 ++
> drivers/thermal/Makefile | 2 +
> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
> include/linux/remoteproc_cooling.h | 52 +++++++++
> 5 files changed, 227 insertions(+)
> create mode 100644 drivers/thermal/remoteproc_cooling.c
> create mode 100644 include/linux/remoteproc_cooling.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 679e5f11e672..c1ba87315cdf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
> F: drivers/thermal/cpuidle_cooling.c
> F: include/linux/cpu_cooling.h
>
> +THERMAL/REMOTEPROC_COOLING
> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> +L: linux-pm@vger.kernel.org
> +S: Supported
> +F: drivers/thermal/remoteproc_cooling.c
> +F: include/linux/remoteproc_cooling.h
> +
> +
> THERMAL/POWER_ALLOCATOR
> M: Lukasz Luba <lukasz.luba@arm.com>
> L: linux-pm@vger.kernel.org
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index b10080d61860..31e92be34387 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>
> If you want this support, you should say Y here.
>
> +
> +config REMOTEPROC_THERMAL
> + bool "Remote processor cooling support"
Why this is 'bool' rather than 'tristate'?
> + help
> + This implements a generic cooling mechanism for remote processors
> + (modem, DSP, etc.) that allows vendor-specific implementations to
> + register thermal cooling devices and provide callbacks for thermal
> + mitigation.
> +
> + If you want this support, you should say Y here.
> +
> config THERMAL_EMULATION
> bool "Thermal emulation mode support"
> help
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index bb21e7ea7fc6..ae747dde54fe 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>
> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>
> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
> +
> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
> # platform thermal drivers
> obj-y += broadcom/
> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
> new file mode 100644
> index 000000000000..a1f948cbde0f
> --- /dev/null
> +++ b/drivers/thermal/remoteproc_cooling.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/thermal.h>
> +
> +#define REMOTEPROC_PREFIX "rproc_"
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
> +
> +/**
> + * struct remoteproc_cdev - Remote processor cooling device
> + * @cdev: Thermal cooling device handle
> + * @ops: Vendor-specific operation callbacks
> + * @devdata: Private data for vendor implementation
> + * @np: Device tree node associated with this cooling device
> + * @lock: Mutex to protect cooling device operations
> + */
> +struct remoteproc_cdev {
> + struct thermal_cooling_device *cdev;
> + const struct remoteproc_cooling_ops *ops;
> + void *devdata;
> + struct device_node *np;
> + struct mutex lock;
> +};
> +
> +
> +/* Thermal cooling device callbacks */
> +
> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + if (!rproc_cdev || !rproc_cdev->ops)
> + return -EINVAL;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
> + .get_max_state = remoteproc_get_max_state,
> + .get_cur_state = remoteproc_get_cur_state,
> + .set_cur_state = remoteproc_set_cur_state,
> +};
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name, const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + struct remoteproc_cdev *rproc_cdev;
> + struct thermal_cooling_device *cdev;
> + int ret;
> +
> + if (!name || !ops) {
> + return ERR_PTR(-EINVAL);
> + }
> +
> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
> + if (!rproc_cdev)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc_cdev->ops = ops;
> + rproc_cdev->devdata = devdata;
> + rproc_cdev->np = np;
> + mutex_init(&rproc_cdev->lock);
> +
> + char *rproc_name __free(kfree) =
> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
> + /* Register with thermal framework */
> + if (np) {
> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
> + &remoteproc_cooling_ops);
> + }
> +
> + if (IS_ERR(cdev)) {
> + ret = PTR_ERR(cdev);
> + goto free_rproc_cdev;
> + }
So, if np == NULL, we register nothing, but still return 0 (aka no
error). Why?
> +
> + rproc_cdev->cdev = cdev;
> +
> + return rproc_cdev;
> +
> +free_rproc_cdev:
> + kfree(rproc_cdev);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> + if (!rproc_cdev)
> + return;
> +
> + thermal_cooling_device_unregister(rproc_cdev->cdev);
> + mutex_destroy(&rproc_cdev->lock);
> + kfree(rproc_cdev);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
> new file mode 100644
> index 000000000000..ef94019d220d
> --- /dev/null
> +++ b/include/linux/remoteproc_cooling.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) 2025, Qualcomm Innovation Center
> + */
> +
> +#ifndef __REMOTEPROC_COOLING_H__
> +#define __REMOTEPROC_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +struct device;
> +struct device_node;
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
> +
> +struct remoteproc_cdev;
> +
> +#ifdef CONFIG_REMOTEPROC_THERMAL
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
> +
> +#else /* !CONFIG_REMOTEPROC_THERMAL */
> +
> +static inline struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name,
> + const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + return ERR_PTR(-EINVAL);
> +}
> +
> +static inline void
> +remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> +}
> +
> +#endif /* CONFIG_REMOTEPROC_THERMAL */
> +
> +#endif /* __REMOTEPROC_COOLING_H__ */
> --
> 2.34.1
>
--
With best wishes
Dmitry
On 12/24/2025 12:53 AM, Dmitry Baryshkov wrote:
> On Tue, Dec 23, 2025 at 06:02:20PM +0530, Gaurav Kohli wrote:
>> Add a new generic driver for thermal cooling devices that control
>> remote processors (modem, DSP, etc.) through various communication
>> channels.
>>
>> This driver provides an abstraction layer between the thermal
>> subsystem and vendor-specific remote processor communication
>> mechanisms.
> This driver simply wraps 3 callbacks. Please explain in the commit
> message, why do we need it? Why can't the consumer of this API simply
> provide those callbacks directly?
thanks for review, sure i will update in next post.
>> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
>> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> ---
>> MAINTAINERS | 8 ++
>> drivers/thermal/Kconfig | 11 ++
>> drivers/thermal/Makefile | 2 +
>> drivers/thermal/remoteproc_cooling.c | 154 +++++++++++++++++++++++++++
>> include/linux/remoteproc_cooling.h | 52 +++++++++
>> 5 files changed, 227 insertions(+)
>> create mode 100644 drivers/thermal/remoteproc_cooling.c
>> create mode 100644 include/linux/remoteproc_cooling.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 679e5f11e672..c1ba87315cdf 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -25935,6 +25935,14 @@ F: drivers/thermal/cpufreq_cooling.c
>> F: drivers/thermal/cpuidle_cooling.c
>> F: include/linux/cpu_cooling.h
>>
>> +THERMAL/REMOTEPROC_COOLING
>> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>> +L: linux-pm@vger.kernel.org
>> +S: Supported
>> +F: drivers/thermal/remoteproc_cooling.c
>> +F: include/linux/remoteproc_cooling.h
>> +
>> +
>> THERMAL/POWER_ALLOCATOR
>> M: Lukasz Luba <lukasz.luba@arm.com>
>> L: linux-pm@vger.kernel.org
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index b10080d61860..31e92be34387 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -229,6 +229,17 @@ config PCIE_THERMAL
>>
>> If you want this support, you should say Y here.
>>
>> +
>> +config REMOTEPROC_THERMAL
>> + bool "Remote processor cooling support"
> Why this is 'bool' rather than 'tristate'?
tristate will be fine, will update this.
>
>> + help
>> + This implements a generic cooling mechanism for remote processors
>> + (modem, DSP, etc.) that allows vendor-specific implementations to
>> + register thermal cooling devices and provide callbacks for thermal
>> + mitigation.
>> +
>> + If you want this support, you should say Y here.
>> +
>> config THERMAL_EMULATION
>> bool "Thermal emulation mode support"
>> help
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index bb21e7ea7fc6..ae747dde54fe 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>>
>> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>>
>> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
>> +
>> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
>> # platform thermal drivers
>> obj-y += broadcom/
>> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
>> new file mode 100644
>> index 000000000000..a1f948cbde0f
>> --- /dev/null
>> +++ b/drivers/thermal/remoteproc_cooling.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
>> + */
>> +
>> +#include <linux/err.h>
>> +#include <linux/export.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of.h>
>> +#include <linux/slab.h>
>> +#include <linux/thermal.h>
>> +
>> +#define REMOTEPROC_PREFIX "rproc_"
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>> +
>> +/**
>> + * struct remoteproc_cdev - Remote processor cooling device
>> + * @cdev: Thermal cooling device handle
>> + * @ops: Vendor-specific operation callbacks
>> + * @devdata: Private data for vendor implementation
>> + * @np: Device tree node associated with this cooling device
>> + * @lock: Mutex to protect cooling device operations
>> + */
>> +struct remoteproc_cdev {
>> + struct thermal_cooling_device *cdev;
>> + const struct remoteproc_cooling_ops *ops;
>> + void *devdata;
>> + struct device_node *np;
>> + struct mutex lock;
>> +};
>> +
>> +
>> +/* Thermal cooling device callbacks */
>> +
>> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
>> + unsigned long *state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
>> + unsigned long state)
>> +{
>> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
>> + int ret;
>> +
>> + if (!rproc_cdev || !rproc_cdev->ops)
>> + return -EINVAL;
>> +
>> + mutex_lock(&rproc_cdev->lock);
>> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
>> + mutex_unlock(&rproc_cdev->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
>> + .get_max_state = remoteproc_get_max_state,
>> + .get_cur_state = remoteproc_get_cur_state,
>> + .set_cur_state = remoteproc_set_cur_state,
>> +};
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name, const struct remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + struct remoteproc_cdev *rproc_cdev;
>> + struct thermal_cooling_device *cdev;
>> + int ret;
>> +
>> + if (!name || !ops) {
>> + return ERR_PTR(-EINVAL);
>> + }
>> +
>> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
>> + if (!rproc_cdev)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + rproc_cdev->ops = ops;
>> + rproc_cdev->devdata = devdata;
>> + rproc_cdev->np = np;
>> + mutex_init(&rproc_cdev->lock);
>> +
>> + char *rproc_name __free(kfree) =
>> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
>> + /* Register with thermal framework */
>> + if (np) {
>> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
>> + &remoteproc_cooling_ops);
>> + }
>> +
>> + if (IS_ERR(cdev)) {
>> + ret = PTR_ERR(cdev);
>> + goto free_rproc_cdev;
>> + }
> So, if np == NULL, we register nothing, but still return 0 (aka no
> error). Why?
>
>> +
>> + rproc_cdev->cdev = cdev;
>> +
>> + return rproc_cdev;
>> +
>> +free_rproc_cdev:
>> + kfree(rproc_cdev);
>> + return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>> +{
>> + if (!rproc_cdev)
>> + return;
>> +
>> + thermal_cooling_device_unregister(rproc_cdev->cdev);
>> + mutex_destroy(&rproc_cdev->lock);
>> + kfree(rproc_cdev);
>> +}
>> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
>> diff --git a/include/linux/remoteproc_cooling.h b/include/linux/remoteproc_cooling.h
>> new file mode 100644
>> index 000000000000..ef94019d220d
>> --- /dev/null
>> +++ b/include/linux/remoteproc_cooling.h
>> @@ -0,0 +1,52 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Remote Processor Cooling Device
>> + *
>> + * Copyright (c) 2025, Qualcomm Innovation Center
>> + */
>> +
>> +#ifndef __REMOTEPROC_COOLING_H__
>> +#define __REMOTEPROC_COOLING_H__
>> +
>> +#include <linux/thermal.h>
>> +
>> +struct device;
>> +struct device_node;
>> +
>> +struct remoteproc_cooling_ops {
>> + int (*get_max_level)(void *devdata, unsigned long *level);
>> + int (*get_cur_level)(void *devdata, unsigned long *level);
>> + int (*set_cur_level)(void *devdata, unsigned long level);
>> +};
>> +
>> +struct remoteproc_cdev;
>> +
>> +#ifdef CONFIG_REMOTEPROC_THERMAL
>> +
>> +struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata);
>> +
>> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev);
>> +
>> +#else /* !CONFIG_REMOTEPROC_THERMAL */
>> +
>> +static inline struct remoteproc_cdev *
>> +remoteproc_cooling_register(struct device_node *np,
>> + const char *name,
>> + const struct remoteproc_cooling_ops *ops,
>> + void *devdata)
>> +{
>> + return ERR_PTR(-EINVAL);
>> +}
>> +
>> +static inline void
>> +remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
>> +{
>> +}
>> +
>> +#endif /* CONFIG_REMOTEPROC_THERMAL */
>> +
>> +#endif /* __REMOTEPROC_COOLING_H__ */
>> --
>> 2.34.1
>>
© 2016 - 2026 Red Hat, Inc.