From: Casey Connolly <[email protected]>

Add a Qualcomm QMI Thermal Mitigation Device (TMD) to support thermal
cooling devices backed by remote subsystems.

On several Qualcomm platforms, remote processors (for example modem and
CDSP) expose thermal mitigation controls through the TMD QMI service.
Client drivers need a way to discover that service, map DT thermal
mitigation endpoints to cooling devices, and forward cooling state
updates to the remote subsystem.

Co-developed-by: Gaurav Kohli <[email protected]>
Signed-off-by: Gaurav Kohli <[email protected]>
Signed-off-by: Casey Connolly <[email protected]>
Signed-off-by: Daniel Lezcano <[email protected]>
---
 MAINTAINERS                      |   6 +
 drivers/soc/qcom/Kconfig         |  10 +
 drivers/soc/qcom/Makefile        |   1 +
 drivers/soc/qcom/qmi_tmd.c       | 604 +++++++++++++++++++++++++++++++++++++++
 include/linux/soc/qcom/qmi.h     |   1 +
 include/linux/soc/qcom/qmi_tmd.h |  23 ++
 6 files changed, 645 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 57656ec0e9d5..3d60702a655a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22286,6 +22286,12 @@ F:     
Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml
 F:     Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst
 F:     drivers/net/ethernet/qualcomm/ppe/
 
+QUALCOMM QMI (REMOTEPROC THERMAL MITIGATION) TMD
+M:     Gaurav Kohli <[email protected]>
+L:     [email protected]
+L:     [email protected]
+F:     drivers/soc/qcom/qmi_tmd.c
+
 QUALCOMM QSEECOM DRIVER
 M:     Maximilian Luz <[email protected]>
 L:     [email protected]
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 2caadbbcf830..a292ce57fd4a 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -128,6 +128,16 @@ config QCOM_QMI_HELPERS
        tristate
        depends on NET
 
+config QCOM_QMI_TMD
+       bool "Qualcomm QMI TMD library" if COMPILE_TEST
+       depends on ARCH_QCOM
+       select QCOM_QMI_HELPERS
+       help
+         This enables the QMI-based Thermal Mitigation Device (TMD) library
+         for Qualcomm remote subsystems. The library manages TMD messaging and
+         handles QMI communication with remote processors (modem, CDSP) to
+         exchange mitigation state and apply thermal mitigation requests.
+
 config QCOM_RAMP_CTRL
        tristate "Qualcomm Ramp Controller driver"
        depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index b7f1d2a57367..4544e61c74e7 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)  += pmic_glink_altmode.o
 obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG) += pmic_pdcharger_ulog.o
 CFLAGS_pmic_pdcharger_ulog.o   :=  -I$(src)
+obj-$(CONFIG_QCOM_QMI_TMD) += qmi_tmd.o
 obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
 qmi_helpers-y  += qmi_encdec.o qmi_interface.o
 obj-$(CONFIG_QCOM_RAMP_CTRL)   += ramp_controller.o
diff --git a/drivers/soc/qcom/qmi_tmd.c b/drivers/soc/qcom/qmi_tmd.c
new file mode 100644
index 000000000000..9d88ae48c864
--- /dev/null
+++ b/drivers/soc/qcom/qmi_tmd.c
@@ -0,0 +1,604 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Linaro Limited
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * QMI Thermal Mitigation Device (TMD) library.
+ * This library provides cooling device support for remote subsystems
+ * (modem and CDSP) running the TMD service via QMI.
+ */
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/qmi.h>
+#include <linux/soc/qcom/qmi_tmd.h>
+#include <linux/thermal.h>
+
+#define QMI_TMD_INSTANCE_MODEM 0x0
+#define QMI_TMD_INSTANCE_CDSP  0x43
+#define QMI_TMD_INSTANCE_CDSP1 0x44
+
+#define QMI_TMD_SERVICE_VERS_V01 0x01
+
+#define QMI_TMD_SET_LEVEL_REQ 0x0021
+#define QMI_TMD_GET_DEV_LIST_REQ 0x0020
+
+#define QMI_TMD_DEV_ID_LEN_MAX 32
+#define QMI_TMD_DEV_LIST_MAX 32
+#define QMI_TMD_RESP_TIMEOUT   msecs_to_jiffies(100)
+#define TMD_GET_LEVEL_REQ_MAX_LEN 36
+#define TMD_SET_LEVEL_REQ_MAX_LEN 40
+
+#define TMD_GET_DEV_LIST_REQ_MAX_LEN 0
+#define TMD_GET_DEV_LIST_RESP_MAX_LEN 1099
+
+struct tmd_dev_id {
+       char mitigation_dev_id[QMI_TMD_DEV_ID_LEN_MAX + 1];
+};
+
+static const struct qmi_elem_info tmd_dev_id_ei[] = {
+       {
+               .data_type = QMI_STRING,
+               .elem_len = QMI_TMD_DEV_ID_LEN_MAX + 1,
+               .elem_size = sizeof(char),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0,
+               .offset = offsetof(struct tmd_dev_id,
+                                  mitigation_dev_id),
+       },
+       {
+               .data_type = QMI_EOTI,
+               .array_type = NO_ARRAY,
+               .tlv_type = QMI_COMMON_TLV_TYPE,
+       },
+};
+
+struct tmd_dev_list {
+       struct tmd_dev_id mitigation_dev_id;
+       u8 max_mitigation_level;
+};
+
+static const struct qmi_elem_info tmd_dev_list_ei[] = {
+       {
+               .data_type = QMI_STRUCT,
+               .elem_len = 1,
+               .elem_size = sizeof(struct tmd_dev_id),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0,
+               .offset = offsetof(struct tmd_dev_list,
+                                  mitigation_dev_id),
+               .ei_array = tmd_dev_id_ei,
+       },
+       {
+               .data_type = QMI_UNSIGNED_1_BYTE,
+               .elem_len = 1,
+               .elem_size = sizeof(uint8_t),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0,
+               .offset = offsetof(struct tmd_dev_list,
+                                  max_mitigation_level),
+       },
+       {
+               .data_type = QMI_EOTI,
+               .array_type = NO_ARRAY,
+               .tlv_type = QMI_COMMON_TLV_TYPE,
+       },
+};
+
+struct tmd_get_dev_list_req {
+       char placeholder;
+};
+
+static const struct qmi_elem_info tmd_get_dev_list_req_ei[] = {
+       {
+               .data_type = QMI_EOTI,
+               .array_type = NO_ARRAY,
+               .tlv_type = QMI_COMMON_TLV_TYPE,
+       },
+};
+
+struct tmd_get_dev_list_resp {
+       struct qmi_response_type_v01 resp;
+       u8 mitigation_device_list_valid;
+       u32 mitigation_device_list_len;
+       struct tmd_dev_list
+               mitigation_device_list[QMI_TMD_DEV_LIST_MAX];
+};
+
+static const struct qmi_elem_info tmd_get_dev_list_resp_ei[] = {
+       {
+               .data_type = QMI_STRUCT,
+               .elem_len = 1,
+               .elem_size = sizeof(struct qmi_response_type_v01),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0x02,
+               .offset = offsetof(struct tmd_get_dev_list_resp,
+                                  resp),
+               .ei_array = qmi_response_type_v01_ei,
+       },
+       {
+               .data_type = QMI_OPT_FLAG,
+               .elem_len = 1,
+               .elem_size = sizeof(uint8_t),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0x10,
+               .offset = offsetof(struct tmd_get_dev_list_resp,
+                                  mitigation_device_list_valid),
+       },
+       {
+               .data_type = QMI_DATA_LEN,
+               .elem_len = 1,
+               .elem_size = sizeof(uint8_t),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0x10,
+               .offset = offsetof(struct tmd_get_dev_list_resp,
+                                  mitigation_device_list_len),
+       },
+       {
+               .data_type = QMI_STRUCT,
+               .elem_len = QMI_TMD_DEV_LIST_MAX,
+               .elem_size = sizeof(struct tmd_dev_list),
+               .array_type = VAR_LEN_ARRAY,
+               .tlv_type = 0x10,
+               .offset = offsetof(struct tmd_get_dev_list_resp,
+                                  mitigation_device_list),
+               .ei_array = tmd_dev_list_ei,
+       },
+       {
+               .data_type = QMI_EOTI,
+               .array_type = NO_ARRAY,
+               .tlv_type = QMI_COMMON_TLV_TYPE,
+       },
+};
+
+struct tmd_set_level_req {
+       struct tmd_dev_id mitigation_dev_id;
+       u8 mitigation_level;
+};
+
+static const struct qmi_elem_info tmd_set_level_req_ei[] = {
+       {
+               .data_type = QMI_STRUCT,
+               .elem_len = 1,
+               .elem_size = sizeof(struct tmd_dev_id),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0x01,
+               .offset = offsetof(struct tmd_set_level_req,
+                                  mitigation_dev_id),
+               .ei_array = tmd_dev_id_ei,
+       },
+       {
+               .data_type = QMI_UNSIGNED_1_BYTE,
+               .elem_len = 1,
+               .elem_size = sizeof(uint8_t),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0x02,
+               .offset = offsetof(struct tmd_set_level_req,
+                                  mitigation_level),
+       },
+       {
+               .data_type = QMI_EOTI,
+               .array_type = NO_ARRAY,
+               .tlv_type = QMI_COMMON_TLV_TYPE,
+       },
+};
+
+struct tmd_set_level_resp {
+       struct qmi_response_type_v01 resp;
+};
+
+static const struct qmi_elem_info tmd_set_level_resp_ei[] = {
+       {
+               .data_type = QMI_STRUCT,
+               .elem_len = 1,
+               .elem_size = sizeof(struct qmi_response_type_v01),
+               .array_type = NO_ARRAY,
+               .tlv_type = 0x02,
+               .offset = offsetof(struct tmd_set_level_resp, resp),
+               .ei_array = qmi_response_type_v01_ei,
+       },
+       {
+               .data_type = QMI_EOTI,
+               .array_type = NO_ARRAY,
+               .tlv_type = QMI_COMMON_TLV_TYPE,
+       },
+};
+
+/**
+ * struct qmi_tmd - A TMD cooling device
+ * @name:      The name of this tmd shared by the remote subsystem
+ * @cdev:      Thermal cooling device handle
+ * @cur_state: The current mitigation state
+ * @max_state: The maximum state
+ * @qmi_tmd_cli:       Parent QMI TMD client
+ */
+struct qmi_tmd {
+       const char *name;
+       struct thermal_cooling_device *cdev;
+       unsigned int cur_state;
+       unsigned int max_state;
+       struct qmi_tmd_client *qmi_tmd_cli;
+};
+
+/**
+ * struct qmi_tmd_client - QMI TMD client state
+ * @dev:               Device associated with this instance
+ * @handle:            QMI connection handle
+ * @mutex:             Lock to synchronise QMI communication
+ * @connection_active: Whether or not we're connected to the QMI TMD service
+ * @svc_arrive_work:   Work item for initialising when the TMD service starts
+ * @num_tmds:          Number of tmds described in the device tree
+ * @tmds:              An array of tmd structures
+ */
+struct qmi_tmd_client {
+       struct device *dev;
+       struct qmi_handle handle;
+       /* protects QMI transactions and connection_active */
+       struct mutex mutex;
+       bool connection_active;
+       struct work_struct svc_arrive_work;
+       int num_tmds;
+       struct qmi_tmd tmds[] __counted_by(num_tmds);
+};
+
+/* Notify the remote subsystem of the requested cooling state */
+static int qmi_tmd_send_state_request(struct qmi_tmd *tmd, int state)
+{
+       struct tmd_set_level_resp resp = { 0 };
+       struct tmd_set_level_req req = { 0 };
+       struct qmi_tmd_client *qmi_tmd_cli = tmd->qmi_tmd_cli;
+       struct qmi_txn txn;
+       int ret = 0;
+
+       guard(mutex)(&qmi_tmd_cli->mutex);
+
+       if (!qmi_tmd_cli->connection_active)
+               return 0;
+
+       strscpy(req.mitigation_dev_id.mitigation_dev_id, tmd->name,
+               QMI_TMD_DEV_ID_LEN_MAX + 1);
+       req.mitigation_level = state;
+
+       ret = qmi_txn_init(&qmi_tmd_cli->handle, &txn,
+                          tmd_set_level_resp_ei, &resp);
+       if (ret < 0) {
+               dev_err(qmi_tmd_cli->dev, "qmi set state %d txn init failed for 
%s ret %d\n",
+                       state, tmd->name, ret);
+               return ret;
+       }
+
+       ret = qmi_send_request(&qmi_tmd_cli->handle, NULL, &txn,
+                              QMI_TMD_SET_LEVEL_REQ,
+                              TMD_SET_LEVEL_REQ_MAX_LEN,
+                              tmd_set_level_req_ei, &req);
+       if (ret < 0) {
+               dev_err(qmi_tmd_cli->dev, "qmi set state %d txn send failed for 
%s ret %d\n",
+                       state, tmd->name, ret);
+               qmi_txn_cancel(&txn);
+               return ret;
+       }
+
+       ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TIMEOUT);
+       if (ret < 0) {
+               dev_err(qmi_tmd_cli->dev, "qmi set state %d txn wait failed for 
%s ret %d\n",
+                       state, tmd->name, ret);
+               return ret;
+       }
+
+       if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
+               dev_err(qmi_tmd_cli->dev,
+                       "qmi set state %d failed for %s result %#x error %#x\n",
+                       state, tmd->name,
+                       resp.resp.result, resp.resp.error);
+               return -EREMOTEIO;
+       }
+
+       dev_dbg(qmi_tmd_cli->dev, "Requested state %d/%d for %s\n", state,
+               tmd->max_state, tmd->name);
+
+       return 0;
+}
+
+static int qmi_tmd_get_max_state(struct thermal_cooling_device *cdev,
+                                unsigned long *state)
+{
+       struct qmi_tmd *tmd = cdev->devdata;
+
+       *state = tmd->max_state;
+
+       return 0;
+}
+
+static int qmi_tmd_get_cur_state(struct thermal_cooling_device *cdev,
+                                unsigned long *state)
+{
+       struct qmi_tmd *tmd = cdev->devdata;
+
+       *state = tmd->cur_state;
+
+       return 0;
+}
+
+static int qmi_tmd_set_cur_state(struct thermal_cooling_device *cdev,
+                                unsigned long state)
+{
+       struct qmi_tmd *tmd = cdev->devdata;
+       int ret;
+
+       if (state > tmd->max_state)
+               return -EINVAL;
+
+       if (tmd->cur_state == state)
+               return 0;
+
+       ret = qmi_tmd_send_state_request(tmd, state);
+       if (!ret)
+               tmd->cur_state = state;
+
+       return ret;
+}
+
+static const struct thermal_cooling_device_ops qmi_tmd_cooling_ops = {
+       .get_max_state = qmi_tmd_get_max_state,
+       .get_cur_state = qmi_tmd_get_cur_state,
+       .set_cur_state = qmi_tmd_set_cur_state,
+};
+
+static int qmi_tmd_register(struct qmi_tmd_client *qmi_tmd_cli,
+                           const char *label, u8 max_state)
+{
+       struct device *dev = qmi_tmd_cli->dev;
+       struct qmi_tmd *tmd;
+       int index;
+
+       for (index = 0; index < qmi_tmd_cli->num_tmds; index++) {
+               tmd = &qmi_tmd_cli->tmds[index];
+
+               if (!strncasecmp(tmd->name, label,
+                                QMI_TMD_DEV_ID_LEN_MAX + 1))
+                       goto found;
+       }
+
+       dev_dbg(qmi_tmd_cli->dev,
+               "TMD '%s' available in firmware but not specified in DT\n",
+               label);
+       return 0;
+
+found:
+       tmd->max_state = max_state;
+
+       /*
+        * If the cooling device already exists then the QMI service went away 
and
+        * came back. So just make sure the current cooling device state is
+        * reflected on the remote side and then return.
+        */
+       if (tmd->cdev)
+               return qmi_tmd_send_state_request(tmd, tmd->cur_state);
+
+       tmd->cdev = thermal_of_cooling_device_register(dev->of_node, index,
+                                                      label, tmd, 
&qmi_tmd_cooling_ops);
+       if (IS_ERR(tmd->cdev))
+               return PTR_ERR(tmd->cdev);
+
+       return 0;
+}
+
+static void qmi_tmd_unregister(struct qmi_tmd_client *qmi_tmd_cli)
+{
+       struct qmi_tmd *tmd;
+       int index;
+
+       for (index = 0; index < qmi_tmd_cli->num_tmds; index++) {
+               tmd = &qmi_tmd_cli->tmds[index];
+
+               if (!tmd->cdev)
+                       continue;
+
+               thermal_cooling_device_unregister(tmd->cdev);
+               tmd->cdev = NULL;
+       }
+}
+
+static void qmi_tmd_svc_arrive(struct work_struct *work)
+{
+       struct qmi_tmd_client *qmi_tmd_cli =
+               container_of(work, struct qmi_tmd_client, svc_arrive_work);
+
+       struct tmd_get_dev_list_req req = { 0 };
+       struct tmd_get_dev_list_resp *resp __free(kfree) = NULL;
+       int ret, i;
+       struct qmi_txn txn;
+
+       resp = kzalloc_obj(*resp, GFP_KERNEL);
+       if (!resp) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       scoped_guard(mutex, &qmi_tmd_cli->mutex) {
+               ret = qmi_txn_init(&qmi_tmd_cli->handle, &txn,
+                                  tmd_get_dev_list_resp_ei, resp);
+               if (ret < 0)
+                       goto out;
+
+               ret = qmi_send_request(&qmi_tmd_cli->handle, NULL, &txn,
+                                      QMI_TMD_GET_DEV_LIST_REQ,
+                               TMD_GET_DEV_LIST_REQ_MAX_LEN,
+                               tmd_get_dev_list_req_ei, &req);
+               if (ret < 0) {
+                       qmi_txn_cancel(&txn);
+                       goto out;
+               }
+
+               ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TIMEOUT);
+               if (ret < 0)
+                       goto out;
+
+               if (resp->resp.result != QMI_RESULT_SUCCESS_V01) {
+                       ret = -EPROTO;
+                       goto out;
+               }
+
+               qmi_tmd_cli->connection_active = true;
+       }
+
+       for (i = 0; i < resp->mitigation_device_list_len; i++) {
+               struct tmd_dev_list *device =
+                       &resp->mitigation_device_list[i];
+
+               ret = qmi_tmd_register(qmi_tmd_cli,
+                                      
device->mitigation_dev_id.mitigation_dev_id,
+                                      device->max_mitigation_level);
+               if (ret)
+                       break;
+       }
+
+out:
+       if (ret)
+               dev_err(qmi_tmd_cli->dev, "Failed to initialize TMD service: 
%d\n", ret);
+}
+
+static void qmi_tmd_del_server(struct qmi_handle *qmi, struct qmi_service 
*service)
+{
+       struct qmi_tmd_client *qmi_tmd_cli =
+               container_of(qmi, struct qmi_tmd_client, handle);
+
+       kernel_sock_shutdown(qmi->sock, SHUT_RDWR);
+
+       scoped_guard(mutex, &qmi_tmd_cli->mutex)
+               qmi_tmd_cli->connection_active = false;
+}
+
+static int qmi_tmd_new_server(struct qmi_handle *qmi, struct qmi_service 
*service)
+{
+       struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port };
+       struct qmi_tmd_client *qmi_tmd_cli;
+       int ret;
+
+       qmi_tmd_cli = container_of(qmi, struct qmi_tmd_client, handle);
+
+       scoped_guard(mutex, &qmi_tmd_cli->mutex) {
+               ret = kernel_connect(qmi->sock, (struct sockaddr_unsized *)&sq,
+                                    sizeof(sq), 0);
+       }
+
+       if (ret < 0) {
+               dev_err(qmi_tmd_cli->dev, "QMI connect failed for node %u port 
%u: %d\n",
+                       service->node, service->port, ret);
+               return ret;
+       }
+
+       queue_work(system_highpri_wq, &qmi_tmd_cli->svc_arrive_work);
+
+       return 0;
+}
+
+static const struct qmi_ops qmi_tmd_ops = {
+       .new_server = qmi_tmd_new_server,
+       .del_server = qmi_tmd_del_server,
+};
+
+static int qmi_tmd_get_instance_id(const char *remoteproc_name)
+{
+       if (!strcmp(remoteproc_name, "modem"))
+               return QMI_TMD_INSTANCE_MODEM;
+
+       if (!strcmp(remoteproc_name, "cdsp"))
+               return QMI_TMD_INSTANCE_CDSP;
+
+       if (!strcmp(remoteproc_name, "cdsp1"))
+               return QMI_TMD_INSTANCE_CDSP1;
+
+       return -ENODEV;
+}
+
+/**
+ * qmi_tmd_init() - Initialize QMI TMD instance
+ * @dev: Device pointer
+ * @remoteproc_name: Remoteproc name (for example modem, cdsp)
+ * @tmd_names: Array of TMD names
+ * @num_tmds: Number of TMD names
+ *
+ * Return: Pointer to qmi_tmd_client on success, ERR_PTR on failure
+ */
+struct qmi_tmd_client *qmi_tmd_init(struct device *dev,
+                                   const char *remoteproc_name,
+                                   const char * const *tmd_names,
+                                   int num_tmds)
+{
+       struct qmi_tmd_client *qmi_tmd_cli;
+       int ret, i, instance_id;
+
+       if (!dev || !remoteproc_name || !tmd_names || num_tmds <= 0)
+               return ERR_PTR(-EINVAL);
+
+       instance_id = qmi_tmd_get_instance_id(remoteproc_name);
+       if (instance_id < 0) {
+               dev_err(dev, "Unsupported remoteproc name '%s' for TMD 
lookup\n",
+                       remoteproc_name);
+               return ERR_PTR(instance_id);
+       }
+
+       qmi_tmd_cli = devm_kzalloc(dev, struct_size(qmi_tmd_cli, tmds, 
num_tmds), GFP_KERNEL);
+       if (!qmi_tmd_cli)
+               return ERR_PTR(-ENOMEM);
+
+       qmi_tmd_cli->dev = dev;
+       qmi_tmd_cli->num_tmds = num_tmds;
+       mutex_init(&qmi_tmd_cli->mutex);
+       INIT_WORK(&qmi_tmd_cli->svc_arrive_work, qmi_tmd_svc_arrive);
+
+       /* Initialize TMD structures */
+       for (i = 0; i < num_tmds; i++) {
+               qmi_tmd_cli->tmds[i].name = tmd_names[i];
+               qmi_tmd_cli->tmds[i].qmi_tmd_cli = qmi_tmd_cli;
+       }
+
+       ret = qmi_handle_init(&qmi_tmd_cli->handle,
+                             TMD_GET_DEV_LIST_RESP_MAX_LEN,
+                             &qmi_tmd_ops, NULL);
+       if (ret < 0) {
+               dev_err(dev, "QMI handle init failed: %d\n", ret);
+               return ERR_PTR(ret);
+       }
+
+       ret = qmi_add_lookup(&qmi_tmd_cli->handle, QMI_SERVICE_ID_TMD,
+                            QMI_TMD_SERVICE_VERS_V01, instance_id);
+       if (ret < 0) {
+               dev_err(dev, "QMI add lookup failed: %d\n", ret);
+               goto err_release_handle;
+       }
+
+       return qmi_tmd_cli;
+
+err_release_handle:
+       qmi_handle_release(&qmi_tmd_cli->handle);
+
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(qmi_tmd_init);
+
+/**
+ * qmi_tmd_exit() - Deinitialize QMI TMD instance
+ * @qmi_tmd_cli: QMI TMD client to deinitialize
+ */
+void qmi_tmd_exit(struct qmi_tmd_client *qmi_tmd_cli)
+{
+       if (!qmi_tmd_cli)
+               return;
+
+       cancel_work_sync(&qmi_tmd_cli->svc_arrive_work);
+       qmi_handle_release(&qmi_tmd_cli->handle);
+       qmi_tmd_unregister(qmi_tmd_cli);
+
+       scoped_guard(mutex, &qmi_tmd_cli->mutex)
+               qmi_tmd_cli->connection_active = false;
+}
+EXPORT_SYMBOL_GPL(qmi_tmd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Qualcomm QMI Thermal Mitigation library");
diff --git a/include/linux/soc/qcom/qmi.h b/include/linux/soc/qcom/qmi.h
index b9dcb437a0be..683d27cee413 100644
--- a/include/linux/soc/qcom/qmi.h
+++ b/include/linux/soc/qcom/qmi.h
@@ -96,6 +96,7 @@ struct qmi_elem_info {
  * Enumerate the IDs of the QMI services
  */
 #define QMI_SERVICE_ID_TEST            0x0f    /*   15 */
+#define QMI_SERVICE_ID_TMD             0x18    /*   24 */
 #define QMI_SERVICE_ID_SSCTL           0x2b    /*   43 */
 #define QMI_SERVICE_ID_IPA             0x31    /*   49 */
 #define QMI_SERVICE_ID_SERVREG_LOC     0x40    /*   64 */
diff --git a/include/linux/soc/qcom/qmi_tmd.h b/include/linux/soc/qcom/qmi_tmd.h
new file mode 100644
index 000000000000..75373eb09d61
--- /dev/null
+++ b/include/linux/soc/qcom/qmi_tmd.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025, Linaro Limited
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * QMI Thermal Mitigation Device (TMD) library header.
+ */
+
+#ifndef __QMI_TMD_H__
+#define __QMI_TMD_H__
+
+struct device;
+struct qmi_tmd_client;
+
+struct qmi_tmd_client *qmi_tmd_init(struct device *dev,
+                                   const char *remoteproc_name,
+                                   const char * const *tmd_names,
+                                   int num_tmds);
+
+void qmi_tmd_exit(struct qmi_tmd_client *tmd_cli);
+
+#endif /* __QMI_TMD_H__ */
+

-- 
2.34.1


Reply via email to