[PATCH v4 04/10] remoteproc: qcom: pas: add support for TMD thermal cooling devices

From: Gaurav Kohli

Date: Fri Jul 03 2026 - 01:10:37 EST


Register Thermal Mitigation Devices (TMDs) for PAS-managed remote
processors to enable thermal throttling through QMI.

This allows the thermal framework to request mitigation when remote
subsystems such as modem and CDSP contribute to thermal pressure.

Signed-off-by: Gaurav Kohli <gaurav.kohli@xxxxxxxxxxxxxxxx>
---
drivers/remoteproc/Kconfig | 1 +
drivers/remoteproc/qcom_q6v5_pas.c | 93 +++++++++++++++++++++++++++++++++++++-
2 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index c78e431b7b2d..cd8cc911e1be 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -228,6 +228,7 @@ config QCOM_Q6V5_PAS
select QCOM_PIL_INFO
select QCOM_MDT_LOADER
select QCOM_Q6V5_COMMON
+ select QCOM_QMI_TMD
select QCOM_RPROC_COMMON
select QCOM_SCM
help
diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
index da27d1d3c9da..ab5bcccc91a6 100644
--- a/drivers/remoteproc/qcom_q6v5_pas.c
+++ b/drivers/remoteproc/qcom_q6v5_pas.c
@@ -5,6 +5,7 @@
* Copyright (C) 2016 Linaro Ltd
* Copyright (C) 2014 Sony Mobile Communications AB
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/

#include <linux/clk.h>
@@ -24,8 +25,10 @@
#include <linux/regulator/consumer.h>
#include <linux/remoteproc.h>
#include <linux/soc/qcom/mdt_loader.h>
+#include <linux/soc/qcom/qmi_tmd.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
+#include <dt-bindings/firmware/qcom,qmi-tmd.h>

#include "qcom_common.h"
#include "qcom_pil_info.h"
@@ -36,6 +39,16 @@

#define MAX_ASSIGN_COUNT 3

+/**
+ * struct tmd_name - TMD device name to cooling-device index mapping
+ * @name: TMD device name
+ * @id: Cooling-device index used as #cooling-cells cell 0 in DT
+ */
+struct tmd_name {
+ const char *name;
+ int id;
+};
+
struct qcom_pas_data {
int crash_reason_smem;
const char *firmware_name;
@@ -56,6 +69,10 @@ struct qcom_pas_data {
int ssctl_id;
unsigned int smem_host_id;

+ unsigned int tmd_instance_id;
+ const struct tmd_name *tmd_name;
+ int num_tmd;
+
int region_assign_idx;
int region_assign_count;
bool region_assign_shared;
@@ -120,6 +137,8 @@ struct qcom_pas {

struct qcom_scm_pas_context *pas_ctx;
struct qcom_scm_pas_context *dtb_pas_ctx;
+
+ struct qmi_tmd_client *tmd_inst;
};

static void qcom_pas_segment_dump(struct rproc *rproc,
@@ -733,6 +752,66 @@ static void qcom_pas_unassign_memory_region(struct qcom_pas *pas)
}
}

+static int qcom_pas_setup_tmd(struct qcom_pas *pas, const struct qcom_pas_data *desc)
+{
+ struct qmi_tmd_client *tmd_inst;
+ const struct tmd_name *tmd;
+ const char **tmd_names;
+ int i, ret;
+
+ if (!device_property_present(pas->dev, "#cooling-cells"))
+ return 0;
+
+ /* Check if TMD mappings are defined */
+ if (!desc->tmd_name || desc->num_tmd == 0)
+ return 0;
+
+ tmd_names = devm_kcalloc(pas->dev, desc->num_tmd,
+ sizeof(*tmd_names), GFP_KERNEL);
+ if (!tmd_names)
+ return -ENOMEM;
+
+ /* Build the name array from the TMD mappings */
+ for (i = 0; i < desc->num_tmd; i++) {
+ tmd = &desc->tmd_name[i];
+
+ if (tmd->id < 0 || tmd->id >= desc->num_tmd) {
+ dev_err(pas->dev, "Invalid TMD id %d for '%s'\n",
+ tmd->id, tmd->name);
+ return -EINVAL;
+ }
+
+ if (tmd_names[tmd->id]) {
+ dev_err(pas->dev, "Duplicate TMD id %d for '%s'\n",
+ tmd->id, tmd->name);
+ return -EINVAL;
+ }
+
+ tmd_names[tmd->id] = tmd->name;
+ }
+
+ for (i = 0; i < desc->num_tmd; i++) {
+ if (!tmd_names[i]) {
+ dev_err(pas->dev, "Missing TMD mapping for id %d\n", i);
+ return -EINVAL;
+ }
+ }
+
+ tmd_inst = qmi_tmd_init(pas->dev, desc->tmd_instance_id, tmd_names, desc->num_tmd);
+ if (IS_ERR(tmd_inst)) {
+ ret = PTR_ERR(tmd_inst);
+ if (ret == -ENODEV)
+ return 0;
+
+ dev_err(pas->dev, "Failed to initialize TMD: %d\n", ret);
+ return ret;
+ }
+
+ pas->tmd_inst = tmd_inst;
+
+ return 0;
+}
+
static int qcom_pas_probe(struct platform_device *pdev)
{
const struct qcom_pas_data *desc;
@@ -855,12 +934,21 @@ static int qcom_pas_probe(struct platform_device *pdev)

pas->pas_ctx->use_tzmem = rproc->has_iommu;
pas->dtb_pas_ctx->use_tzmem = rproc->has_iommu;
- ret = rproc_add(rproc);
+
+ ret = qcom_pas_setup_tmd(pas, desc);
if (ret)
goto remove_ssr_sysmon;

+ ret = rproc_add(rproc);
+ if (ret)
+ goto remove_setup_tmd;
+
return 0;

+remove_setup_tmd:
+ if (pas->tmd_inst)
+ qmi_tmd_exit(pas->tmd_inst);
+
remove_ssr_sysmon:
qcom_remove_ssr_subdev(rproc, &pas->ssr_subdev);
qcom_remove_sysmon_subdev(pas->sysmon);
@@ -883,6 +971,9 @@ static void qcom_pas_remove(struct platform_device *pdev)
{
struct qcom_pas *pas = platform_get_drvdata(pdev);

+ if (pas->tmd_inst)
+ qmi_tmd_exit(pas->tmd_inst);
+
rproc_del(pas->rproc);

qcom_q6v5_deinit(&pas->q6v5);

--
2.34.1