On 7/22/2026 4:37 AM, Dmitry Baryshkov wrote:
On Tue, Jul 21, 2026 at 11:59:00AM +0530, Gaurav Kohli wrote:
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 <[email protected]>
---
  drivers/remoteproc/Kconfig         |  1 +
  drivers/remoteproc/qcom_q6v5_pas.c | 90 +++++++++++++++++++++++++++++++++++++-
  2 files changed, 90 insertions(+), 1 deletion(-)

diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 65befdbfa5f7..f7c02edf395f 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -229,6 +229,7 @@ config QCOM_Q6V5_PAS
        select QCOM_PIL_INFO
        select QCOM_MDT_LOADER
        select QCOM_Q6V5_COMMON
+       select QCOM_QMI_TMD

Sashiko warned here about the missing NET dependencies.

thanks for review, will fix this.

        select QCOM_RPROC_COMMON
        select QCOM_SCM
        select QCOM_PAS
diff --git a/drivers/remoteproc/qcom_q6v5_pas.c 
b/drivers/remoteproc/qcom_q6v5_pas.c
index 25599d728208..dc5be2c030cf 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>
@@ -26,8 +27,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/thermal/qcom,qmi-tmd.h>
#include "qcom_common.h"
  #include "qcom_pil_info.h"
@@ -38,6 +41,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;
@@ -58,6 +71,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;
@@ -122,6 +139,8 @@ struct qcom_pas {
struct qcom_pas_context *pas_ctx;
        struct qcom_pas_context *dtb_pas_ctx;
+
+       struct qmi_tmd_client *tmd_inst;
  };
static void qcom_pas_segment_dump(struct rproc *rproc,
@@ -798,6 +817,64 @@ 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;
+
+       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;
+
+       for (i = 0; i < desc->num_tmd; i++) {
+               tmd = &desc->tmd_name[i];
+
+               if (tmd->id < 0 || tmd->id >= desc->num_tmd) {

The < 0 check is totally redundant.

Ack, this is redundant only.

For the second check, currently the driver will set num_tmd to
ARRAY_SIZE(desc->tmds), which makes second part either redundant or
invalid. I assume that here you want to compare to some maximum TMD
number to be supported rather than a fixed ARRAY_SIZE().

This is just a defensive check to prevent mistakes in the static mapping
table. The current mappings use dense IDs starting from 0, so
desc->num_tmd matches the valid ID range today.


+                       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);

And this makes it even more strange. Can there be holes in the
tmd_names? Can platform omit some of the TMDs? Assumingly yes, this
is not correct (nor is devm_kcalloc).

thanks for review.
For example, if the static table contains:

  tmd1 -> id 0
  tmd2 -> id 1
  tmd3 -> id 2

but firmware reports only tmd1 and tmd3, then cooling devices are
registered only for IDs 0 and 2. No cooling device is registered for
tmd2, so the corresponding DT cooling-device binding will not happen.

The devm_kcalloc() is still needed because qmi_tmd_init() expects an
indexed name array matching the DT cooling-device IDs. The current static mapping assumes those IDs are dense, so there are no holes in the mapping array itself.


+       if (IS_ERR(tmd_inst)) {
+               ret = PTR_ERR(tmd_inst);
+               if (ret == -ENODEV)
+                       return 0;
+
+               return ret;
+       }
+
+       pas->tmd_inst = tmd_inst;
+
+       return 0;
+}
+
  static int qcom_pas_probe(struct platform_device *pdev)
  {
        const struct qcom_pas_data *desc;
@@ -925,16 +1002,24 @@ static int qcom_pas_probe(struct platform_device *pdev)
        if (desc->early_boot)
                pas->rproc->state = RPROC_DETACHED;
- 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;
+
        node = of_get_compatible_child(pdev->dev.of_node, "qcom,bam-dmux");
        pas->bam_dmux = of_platform_device_create(node, NULL, &pdev->dev);
        of_node_put(node);
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);
@@ -960,6 +1045,9 @@ static void qcom_pas_remove(struct platform_device *pdev)
        if (pas->bam_dmux)
                of_platform_device_destroy(&pas->bam_dmux->dev, NULL);
+ if (pas->tmd_inst)
+               qmi_tmd_exit(pas->tmd_inst);
+
        rproc_del(pas->rproc);
qcom_q6v5_deinit(&pas->q6v5);

--
2.34.1




Reply via email to