On Tue, Jun 09, 2026 at 03:52:57PM +0530, Gaurav Kohli wrote: > 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]>
Wrong SoB chain. > --- > 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. Why are you limiting it to these DSPs only? I quickly checked, my X13s (sc8280xp) also has one on the ADSP. > + */ > +#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 Other instances? Are those numbers fixed? Should we pass the instance ID from the PAS driver instead? > + > +#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 What is it protecting? > + * @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; Mutex protection? > + > + 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; Hmm, again, mutex protection for the cur_state? Or is it provided by the thermal core? > + > + 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); If we can read them from the firmware, why do you need to specify them in DT? > + 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); So, connection is protected by the mutex, but socket shutdown isn't. Why? > + > + 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; Okay, this definitely should be coming from the PAS driver, being a part of the platform data. > +} > + > +/** > + * 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 */ Is it a useful comment? > + 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); -- With best wishes Dmitry

