Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ab3da72b authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "thermal: qti_qmi_cooling: Add remote subsystem cooling devices"

parents 3e0e65a3 fc6fdf30
Loading
Loading
Loading
Loading
+113 −0
Original line number Diff line number Diff line
QMI thermal mitigation(TMD) cooling devices.

The QMI TMD cooling device, will be used for various mitigations for remote
subsystem including remote processor mitigation, rail voltage restriction etc.
This cooling device uses kernel qti QMI interface to send the message to
remote subsystem.

Each child node of the QMI TMD devicetree node represents each remote
subsystem and each child of this subsystem represents separate cooling
devices. It requires minimum one remote subsystem node and each subsystem
node requires minimum one cooling device node.

Properties:

- compatible:
	Usage: required
	Value type: <string>
	Definition: should be "qcom,qmi_cooling_devices"


Subsystem properties:
- qcom,instance-id:
	Usage: required
	Value type: <integer>
	Definition: Remote subsystem QMI server instance id to be used for
			communicating with QMI.

	Minimum one child node is required. Child node name and its alias are
	used as cooling device name and phandle for that cooling device.

	cooling device node properties:
	-qcom,qmi-dev-name:
		Usage: required
		Value type: <string>
		Definition: Remote subsystem device identifier. Below strings
			are the only acceptable device names,
			"pa" -> for pa cooling device,
			"cpuv_restriction_cold" -> for vdd restriction,
			"cx_vdd_limit" -> for vdd limit,
			"modem" -> for processor passive cooling device,
			"modem_current" -> for current limiting device,
			"modem_bw" ->  for bus bandwidth limiting device,
			"cpr_cold" -> for cpr restriction.

	-#cooling-cells:
		Usage: required
		Value type: <integer>
		Definition: Must be 2. Needed for of_thermal as cooling device
			identifier. Please refer to
			<devicetree/bindings/thermal/thermal.txt> for more
			details.
Example:

	qmi-tmd-devices {
		compatible = "qcom,qmi_cooling_devices";

		modem {
			qcom,instance-id = <0x0>;

			modem_pa: modem_pa {
				qcom,qmi-dev-name = "pa";
				#cooling-cells = <2>;
			};

			modem_proc: modem_proc {
				qcom,qmi-dev-name = "modem";
				#cooling-cells = <2>;
			};

			modem_vdd: modem_vdd {
				qcom,qmi-dev-name = "cpuv_restriction_cold";
				#cooling-cells = <2>;
			};

			modem_current: modem_current {
				qcom,qmi-dev-name = "modem_current";
				#cooling-cells = <2>;
			};

			modem_cpr_cold: modem_cpr_cold {
				qcom,qmi-dev-name = "cpr_cold";
				#cooling-cells = <2>;
			};
		};

		adsp {
			qcom,instance-id = <0x1>;

			adsp_vdd: adsp_vdd {
				qcom,qmi-dev-name = "cpuv_restriction_cold";
				#cooling-cells = <2>;
			};
		};

		cdsp {
			qcom,instance-id = <0x43>;

			cdsp_vdd: cdsp_vdd {
				qcom,qmi-dev-name = "cpuv_restriction_cold";
				#cooling-cells = <2>;
			};
		};

		slpi {
			qcom,instance-id = <0x53>;

			slpi_vdd: slpi_vdd {
				qcom,qmi-dev-name = "cpuv_restriction_cold";
				#cooling-cells = <2>;
			};
		};
	};
+11 −0
Original line number Diff line number Diff line
@@ -49,3 +49,14 @@ config QTI_REG_COOLING_DEVICE
	  will be used by QTI chipset to place a floor voltage restriction at
	  low temperatures. The regulator cooling device will message the AOP
	  using mail box to establish the floor voltage.

config QTI_QMI_COOLING_DEVICE
	bool "QTI QMI cooling devices"
	depends on MSM_QMI_INTERFACE && THERMAL_OF
	help
	   This enables the QTI remote subsystem cooling devices. These cooling
	   devices will be used by QTI chipset to place various remote
	   subsystem mitigations like remote processor passive mitigation,
	   remote subsystem voltage restriction at low temperatures etc.
	   The QMI cooling device will interface with remote subsystem
	   using QTI QMI interface.
+1 −0
Original line number Diff line number Diff line
@@ -4,3 +4,4 @@ obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o
obj-$(CONFIG_QTI_VIRTUAL_SENSOR) += qti_virtual_sensor.o
obj-$(CONFIG_QTI_REG_COOLING_DEVICE) += regulator_cooling.o
obj-$(CONFIG_QTI_QMI_COOLING_DEVICE) += thermal_mitigation_device_service_v01.o qmi_cooling.o
+681 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <soc/qcom/msm_qmi_interface.h>

#include "thermal_mitigation_device_service_v01.h"

#define QMI_CDEV_DRIVER		"qmi-cooling-device"
#define QMI_TMD_RESP_TOUT_MSEC	50
#define QMI_CLIENT_NAME_LENGTH	40

enum qmi_device_type {
	QMI_CDEV_MAX_LIMIT_TYPE,
	QMI_CDEV_MIN_LIMIT_TYPE,
	QMI_CDEV_TYPE_NR,
};

struct qmi_cooling_device {
	struct device_node		*np;
	char				cdev_name[THERMAL_NAME_LENGTH];
	char				qmi_name[QMI_CLIENT_NAME_LENGTH];
	bool                            connection_active;
	enum qmi_device_type		type;
	struct list_head		qmi_node;
	struct thermal_cooling_device	*cdev;
	unsigned int			mtgn_state;
	unsigned int			max_level;
	struct qmi_tmd_instance		*tmd;
};

struct qmi_tmd_instance {
	struct device			*dev;
	struct qmi_handle		*handle;
	struct mutex			mutex;
	struct work_struct		work_svc_arrive;
	struct work_struct		work_svc_exit;
	struct work_struct		work_rcv_msg;
	struct notifier_block		nb;
	uint32_t			inst_id;
	struct list_head		tmd_cdev_list;
};

struct qmi_dev_info {
	char				*dev_name;
	enum qmi_device_type		type;
};

static struct workqueue_struct *qmi_tmd_wq;
static struct qmi_tmd_instance *tmd_instances;
static int tmd_inst_cnt;

static struct qmi_dev_info device_clients[] = {
	{
		.dev_name = "pa",
		.type = QMI_CDEV_MAX_LIMIT_TYPE,
	},
	{
		.dev_name = "cx_vdd_limit",
		.type = QMI_CDEV_MAX_LIMIT_TYPE,
	},
	{
		.dev_name = "modem",
		.type = QMI_CDEV_MAX_LIMIT_TYPE,
	},
	{
		.dev_name = "modem_current",
		.type = QMI_CDEV_MAX_LIMIT_TYPE,
	},
	{
		.dev_name = "modem_bw",
		.type = QMI_CDEV_MAX_LIMIT_TYPE,
	},
	{
		.dev_name = "cpuv_restriction_cold",
		.type = QMI_CDEV_MIN_LIMIT_TYPE,
	},
	{
		.dev_name = "cpr_cold",
		.type = QMI_CDEV_MIN_LIMIT_TYPE,
	}
};

static int qmi_get_max_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct qmi_cooling_device *qmi_cdev = cdev->devdata;

	if (!qmi_cdev)
		return -EINVAL;

	*state = qmi_cdev->max_level;

	return 0;
}

static int qmi_get_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct qmi_cooling_device *qmi_cdev = cdev->devdata;

	if (!qmi_cdev)
		return -EINVAL;

	if (qmi_cdev->type == QMI_CDEV_MIN_LIMIT_TYPE) {
		*state = 0;
		return 0;
	}
	*state = qmi_cdev->mtgn_state;

	return 0;
}

static int qmi_tmd_send_state_request(struct qmi_cooling_device *qmi_cdev,
				uint8_t state)
{
	int ret = 0;
	struct tmd_set_mitigation_level_req_msg_v01 req;
	struct tmd_set_mitigation_level_resp_msg_v01 tmd_resp;
	struct msg_desc req_desc, resp_desc;
	struct qmi_tmd_instance *tmd = qmi_cdev->tmd;

	memset(&req, 0, sizeof(req));
	memset(&tmd_resp, 0, sizeof(tmd_resp));

	strlcpy(req.mitigation_dev_id.mitigation_dev_id, qmi_cdev->qmi_name,
		QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01);
	req.mitigation_level = state;

	req_desc.max_msg_len = TMD_SET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = QMI_TMD_SET_MITIGATION_LEVEL_REQ_V01;
	req_desc.ei_array = tmd_set_mitigation_level_req_msg_v01_ei;

	resp_desc.max_msg_len =
		TMD_SET_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN;
	resp_desc.msg_id = QMI_TMD_SET_MITIGATION_LEVEL_RESP_V01;
	resp_desc.ei_array = tmd_set_mitigation_level_resp_msg_v01_ei;

	mutex_lock(&tmd->mutex);
	ret = qmi_send_req_wait(tmd->handle,
				&req_desc, &req, sizeof(req),
				&resp_desc, &tmd_resp, sizeof(tmd_resp),
				QMI_TMD_RESP_TOUT_MSEC);
	if (ret < 0) {
		pr_err("qmi set state:%d failed for %s ret:%d\n",
			state, qmi_cdev->cdev_name, ret);
		goto qmi_send_exit;
	}

	if (tmd_resp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ret = tmd_resp.resp.result;
		pr_err("qmi set state:%d NOT success for %s ret:%d\n",
			state, qmi_cdev->cdev_name, ret);
		goto qmi_send_exit;
	}
	pr_debug("Requested qmi state:%d for %s\n", state, qmi_cdev->cdev_name);

qmi_send_exit:
	mutex_unlock(&tmd->mutex);
	return ret;
}

static int qmi_set_cur_or_min_state(struct qmi_cooling_device *qmi_cdev,
				 unsigned long state)
{
	int ret = 0;
	struct qmi_tmd_instance *tmd = qmi_cdev->tmd;

	if (!tmd)
		return -EINVAL;

	if (qmi_cdev->mtgn_state == state)
		return ret;

	/* save it and return if server exit */
	if (!qmi_cdev->connection_active) {
		qmi_cdev->mtgn_state = state;
		pr_debug("Pending request:%ld for %s\n", state,
				qmi_cdev->cdev_name);
		return ret;
	}

	/* It is best effort to save state even if QMI fail */
	ret = qmi_tmd_send_state_request(qmi_cdev, (uint8_t)state);

	qmi_cdev->mtgn_state = state;

	return ret;
}

static int qmi_set_cur_state(struct thermal_cooling_device *cdev,
				 unsigned long state)
{
	struct qmi_cooling_device *qmi_cdev = cdev->devdata;

	if (!qmi_cdev)
		return -EINVAL;

	if (qmi_cdev->type == QMI_CDEV_MIN_LIMIT_TYPE)
		return 0;

	if (state > qmi_cdev->max_level)
		state = qmi_cdev->max_level;

	return qmi_set_cur_or_min_state(qmi_cdev, state);
}

static int qmi_set_min_state(struct thermal_cooling_device *cdev,
				 unsigned long state)
{
	struct qmi_cooling_device *qmi_cdev = cdev->devdata;

	if (!qmi_cdev)
		return -EINVAL;

	if (qmi_cdev->type == QMI_CDEV_MAX_LIMIT_TYPE)
		return 0;

	if (state > qmi_cdev->max_level)
		state = qmi_cdev->max_level;

	/* Convert state into QMI client expects for min state */
	state = qmi_cdev->max_level - state;

	return qmi_set_cur_or_min_state(qmi_cdev, state);
}

static int qmi_get_min_state(struct thermal_cooling_device *cdev,
				 unsigned long *state)
{
	struct qmi_cooling_device *qmi_cdev = cdev->devdata;

	if (!qmi_cdev)
		return -EINVAL;

	if (qmi_cdev->type == QMI_CDEV_MAX_LIMIT_TYPE) {
		*state = 0;
		return 0;
	}
	*state = qmi_cdev->max_level - qmi_cdev->mtgn_state;

	return 0;
}

static struct thermal_cooling_device_ops qmi_device_ops = {
	.get_max_state = qmi_get_max_state,
	.get_cur_state = qmi_get_cur_state,
	.set_cur_state = qmi_set_cur_state,
	.set_min_state = qmi_set_min_state,
	.get_min_state = qmi_get_min_state,
};

static int qmi_register_cooling_device(struct qmi_cooling_device *qmi_cdev)
{
	qmi_cdev->cdev = thermal_of_cooling_device_register(
					qmi_cdev->np,
					qmi_cdev->cdev_name,
					qmi_cdev,
					&qmi_device_ops);
	if (IS_ERR(qmi_cdev->cdev)) {
		pr_err("Cooling register failed for %s, ret:%ld\n",
			qmi_cdev->cdev_name, PTR_ERR(qmi_cdev->cdev));
		return PTR_ERR(qmi_cdev->cdev);
	}
	pr_debug("Cooling register success for %s\n", qmi_cdev->cdev_name);

	return 0;
}

static int verify_devices_and_register(struct qmi_tmd_instance *tmd)
{
	struct tmd_get_mitigation_device_list_req_msg_v01 req;
	struct tmd_get_mitigation_device_list_resp_msg_v01 *tmd_resp;
	struct msg_desc req_desc, resp_desc;
	int ret = 0, i;

	memset(&req, 0, sizeof(req));
	/* size of tmd_resp is very high, use heap memory rather than stack */
	tmd_resp = kzalloc(sizeof(*tmd_resp), GFP_KERNEL);
	if (!tmd_resp)
		return -ENOMEM;

	req_desc.max_msg_len =
		TMD_GET_MITIGATION_DEVICE_LIST_REQ_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = QMI_TMD_GET_MITIGATION_DEVICE_LIST_REQ_V01;
	req_desc.ei_array = tmd_get_mitigation_device_list_req_msg_v01_ei;

	resp_desc.max_msg_len =
		TMD_GET_MITIGATION_DEVICE_LIST_RESP_MSG_V01_MAX_MSG_LEN;
	resp_desc.msg_id = QMI_TMD_GET_MITIGATION_DEVICE_LIST_RESP_V01;
	resp_desc.ei_array = tmd_get_mitigation_device_list_resp_msg_v01_ei;

	mutex_lock(&tmd->mutex);
	ret = qmi_send_req_wait(tmd->handle,
			&req_desc, &req, sizeof(req),
			&resp_desc, tmd_resp, sizeof(*tmd_resp),
			0);
	if (ret < 0) {
		pr_err("qmi get device list failed for inst_id:0x%x ret:%d\n",
			tmd->inst_id, ret);
		goto reg_exit;
	}

	if (tmd_resp->resp.result != QMI_RESULT_SUCCESS_V01) {
		ret = tmd_resp->resp.result;
		pr_err("Get device list NOT success for inst_id:0x%x ret:%d\n",
			tmd->inst_id, ret);
		goto reg_exit;
	}
	mutex_unlock(&tmd->mutex);

	for (i = 0; i < tmd_resp->mitigation_device_list_len; i++) {
		struct qmi_cooling_device *qmi_cdev = NULL;

		list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list,
					qmi_node) {
			struct tmd_mitigation_dev_list_type_v01 *device =
				&tmd_resp->mitigation_device_list[i];

			if ((strncasecmp(qmi_cdev->qmi_name,
				device->mitigation_dev_id.mitigation_dev_id,
				QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01)))
				continue;

			qmi_cdev->connection_active = true;
			qmi_cdev->max_level = device->max_mitigation_level;
			/*
			 * It is better to set current state
			 * initially or during restart
			 */
			qmi_tmd_send_state_request(qmi_cdev,
						qmi_cdev->mtgn_state);
			if (!qmi_cdev->cdev)
				ret = qmi_register_cooling_device(qmi_cdev);
			break;
		}
	}

	kfree(tmd_resp);
	return ret;

reg_exit:
	mutex_unlock(&tmd->mutex);
	kfree(tmd_resp);

	return ret;
}

static void qmi_tmd_rcv_msg(struct work_struct *work)
{
	int rc;
	struct qmi_tmd_instance *tmd = container_of(work,
						struct qmi_tmd_instance,
						work_rcv_msg);

	do {
		pr_debug("Notified about a Receive Event\n");
	} while ((rc = qmi_recv_msg(tmd->handle)) == 0);

	if (rc != -ENOMSG)
		pr_err("Error receiving message for SVC:0x%x, ret:%d\n",
			tmd->inst_id, rc);
}

static void qmi_tmd_clnt_notify(struct qmi_handle *handle,
		enum qmi_event_type event, void *priv_data)
{
	struct qmi_tmd_instance *tmd =
		(struct qmi_tmd_instance *)priv_data;

	if (!tmd) {
		pr_debug("tmd is NULL\n");
		return;
	}

	switch (event) {
	case QMI_RECV_MSG:
		queue_work(qmi_tmd_wq, &tmd->work_rcv_msg);
		break;
	default:
		break;
	}
}

static void qmi_tmd_svc_arrive(struct work_struct *work)
{
	int ret = 0;
	struct qmi_tmd_instance *tmd = container_of(work,
						struct qmi_tmd_instance,
						work_svc_arrive);

	mutex_lock(&tmd->mutex);
	tmd->handle = qmi_handle_create(qmi_tmd_clnt_notify, tmd);
	if (!tmd->handle) {
		pr_err("QMI TMD client handle alloc failed for 0x%x\n",
			tmd->inst_id);
		goto arrive_exit;
	}

	ret = qmi_connect_to_service(tmd->handle, TMD_SERVICE_ID_V01,
				   TMD_SERVICE_VERS_V01,
				   tmd->inst_id);
	if (ret < 0) {
		pr_err("Could not connect handle to service for 0x%x, ret:%d\n",
			tmd->inst_id, ret);
		qmi_handle_destroy(tmd->handle);
		tmd->handle = NULL;
		goto arrive_exit;
	}
	mutex_unlock(&tmd->mutex);

	verify_devices_and_register(tmd);

	return;

arrive_exit:
	mutex_unlock(&tmd->mutex);
}

static void qmi_tmd_svc_exit(struct work_struct *work)
{
	struct qmi_tmd_instance *tmd = container_of(work,
						struct qmi_tmd_instance,
						work_svc_exit);
	struct qmi_cooling_device *qmi_cdev;

	mutex_lock(&tmd->mutex);
	qmi_handle_destroy(tmd->handle);
	tmd->handle = NULL;

	list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list, qmi_node)
		qmi_cdev->connection_active = false;

	mutex_unlock(&tmd->mutex);
}

static int qmi_tmd_svc_event_notify(struct notifier_block *this,
				    unsigned long event,
				    void *data)
{
	struct qmi_tmd_instance *tmd = container_of(this,
						struct qmi_tmd_instance,
						nb);

	if (!tmd) {
		pr_debug("tmd is NULL\n");
		return -EINVAL;
	}

	switch (event) {
	case QMI_SERVER_ARRIVE:
		schedule_work(&tmd->work_svc_arrive);
		break;
	case QMI_SERVER_EXIT:
		schedule_work(&tmd->work_svc_exit);
		break;
	default:
		break;
	}
	return 0;
}

static void qmi_tmd_cleanup(void)
{
	int idx = 0;
	struct qmi_tmd_instance *tmd = tmd_instances;
	struct qmi_cooling_device *qmi_cdev, *c_next;

	for (; idx < tmd_inst_cnt; idx++) {
		mutex_lock(&tmd[idx].mutex);
		list_for_each_entry_safe(qmi_cdev, c_next,
				&tmd[idx].tmd_cdev_list, qmi_node) {
			if (qmi_cdev->cdev)
				thermal_cooling_device_unregister(
					qmi_cdev->cdev);

			list_del(&qmi_cdev->qmi_node);
		}
		if (tmd[idx].handle)
			qmi_handle_destroy(tmd[idx].handle);

		if (tmd[idx].nb.notifier_call)
			qmi_svc_event_notifier_unregister(TMD_SERVICE_ID_V01,
						TMD_SERVICE_VERS_V01,
						tmd[idx].inst_id,
						&tmd[idx].nb);
		mutex_unlock(&tmd[idx].mutex);
	}

	if (qmi_tmd_wq) {
		destroy_workqueue(qmi_tmd_wq);
		qmi_tmd_wq = NULL;
	}
}

static int of_get_qmi_tmd_platform_data(struct device *dev)
{
	int ret = 0, idx = 0, i = 0, subsys_cnt = 0;
	struct device_node *np = dev->of_node;
	struct device_node *subsys_np, *cdev_np;
	struct qmi_tmd_instance *tmd;
	struct qmi_cooling_device *qmi_cdev;

	subsys_cnt = of_get_available_child_count(np);
	if (!subsys_cnt) {
		dev_err(dev, "No child node to process\n");
		return -EFAULT;
	}

	tmd = devm_kcalloc(dev, subsys_cnt, sizeof(*tmd), GFP_KERNEL);
	if (!tmd)
		return -ENOMEM;

	for_each_available_child_of_node(np, subsys_np) {
		if (idx >= subsys_cnt)
			break;

		ret = of_property_read_u32(subsys_np, "qcom,instance-id",
				&tmd[idx].inst_id);
		if (ret) {
			dev_err(dev, "error reading qcom,insance-id. ret:%d\n",
				ret);
			return ret;
		}

		tmd[idx].dev = dev;
		mutex_init(&tmd[idx].mutex);
		INIT_LIST_HEAD(&tmd[idx].tmd_cdev_list);

		for_each_available_child_of_node(subsys_np, cdev_np) {
			const char *qmi_name;

			qmi_cdev = devm_kzalloc(dev, sizeof(*qmi_cdev),
					GFP_KERNEL);
			if (!qmi_cdev) {
				ret = -ENOMEM;
				return ret;
			}

			strlcpy(qmi_cdev->cdev_name, cdev_np->name,
				THERMAL_NAME_LENGTH);

			if (!of_property_read_string(cdev_np,
					"qcom,qmi-dev-name",
					&qmi_name)) {
				strlcpy(qmi_cdev->qmi_name, qmi_name,
						QMI_CLIENT_NAME_LENGTH);
			} else {
				dev_err(dev, "Fail to parse dev name for %s\n",
					cdev_np->name);
				break;
			}
			/* Check for supported qmi dev*/
			for (i = 0; i < ARRAY_SIZE(device_clients); i++) {
				if (strcmp(device_clients[i].dev_name,
					qmi_cdev->qmi_name) == 0)
					break;
			}

			if (i >= ARRAY_SIZE(device_clients)) {
				dev_err(dev, "Not supported dev name for %s\n",
					cdev_np->name);
				break;
			}
			qmi_cdev->type = device_clients[i].type;
			qmi_cdev->tmd = &tmd[idx];
			qmi_cdev->np = cdev_np;
			qmi_cdev->mtgn_state = 0;
			list_add(&qmi_cdev->qmi_node, &tmd[idx].tmd_cdev_list);
		}
		idx++;
	}
	tmd_instances = tmd;
	tmd_inst_cnt = subsys_cnt;

	return 0;
}

static int qmi_device_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	int ret = 0, idx = 0;

	ret = of_get_qmi_tmd_platform_data(dev);
	if (ret)
		goto probe_err;

	if (!tmd_instances || !tmd_inst_cnt) {
		dev_err(dev, "Empty tmd instances\n");
		return -EINVAL;
	}

	qmi_tmd_wq = create_singlethread_workqueue("qmi_tmd_wq");
	if (!qmi_tmd_wq) {
		dev_err(dev, "Failed to create single thread workqueue\n");
		ret = -EFAULT;
		goto probe_err;
	}

	for (; idx < tmd_inst_cnt; idx++) {
		struct qmi_tmd_instance *tmd = &tmd_instances[idx];

		if (list_empty(&tmd->tmd_cdev_list))
			continue;

		tmd->nb.notifier_call = qmi_tmd_svc_event_notify;
		INIT_WORK(&tmd->work_svc_arrive, qmi_tmd_svc_arrive);
		INIT_WORK(&tmd->work_svc_exit, qmi_tmd_svc_exit);
		INIT_WORK(&tmd->work_rcv_msg, qmi_tmd_rcv_msg);

		ret = qmi_svc_event_notifier_register(TMD_SERVICE_ID_V01,
						TMD_SERVICE_VERS_V01,
						tmd->inst_id,
						&tmd->nb);
		if (ret < 0) {
			dev_err(dev, "QMI register failed for 0x%x, ret:%d\n",
				tmd->inst_id, ret);
			goto probe_err;
		}
	}

	return 0;

probe_err:
	qmi_tmd_cleanup();
	return ret;
}

static int qmi_device_remove(struct platform_device *pdev)
{
	qmi_tmd_cleanup();

	return 0;
}

static const struct of_device_id qmi_device_match[] = {
	{.compatible = "qcom,qmi_cooling_devices"},
	{}
};

static struct platform_driver qmi_device_driver = {
	.probe          = qmi_device_probe,
	.remove         = qmi_device_remove,
	.driver         = {
		.name   = "QMI_CDEV_DRIVER",
		.owner  = THIS_MODULE,
		.of_match_table = qmi_device_match,
	},
};

static int __init qmi_device_init(void)
{
	return platform_driver_register(&qmi_device_driver);
}
module_init(qmi_device_init);

static void __exit qmi_device_exit(void)
{
	platform_driver_unregister(&qmi_device_driver);
}
module_exit(qmi_device_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("QTI QMI cooling device driver");
+359 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading