123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685 |
- /* 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_skin",
- .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");
|