blob: b4a036e8a45438971541c78029109637ad30b751 [file] [log] [blame]
/* Copyright (c) 2018-2020, 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 <linux/soc/qcom/qmi.h>
#include <linux/net.h>
#include <linux/kernel.h>
#include <linux/suspend.h>
#include "thermal_sensor_service_v01.h"
#include "../thermal_core.h"
#define QMI_SENS_DRIVER "qmi-therm-sensors"
#define QMI_TS_RESP_TOUT msecs_to_jiffies(100)
#define QMI_CLIENT_NAME_LENGTH 40
#define QMI_FL_SIGN 0x80000000
#define QMI_FL_EXP 0x7f800000
#define QMI_FL_MANTISSA 0x007fffff
#define QMI_FL_NORM 0x00800000
#define QMI_FL_SIGN_BIT 31
#define QMI_MANTISSA_MSB 23
enum qmi_ts_sensor {
QMI_TS_PA,
QMI_TS_PA_1,
QMI_TS_PA_2,
QMI_TS_QFE_PA_0,
QMI_TS_QFE_WTR_0,
QMI_TS_MODEM_MODEM,
QMI_TS_MMW_0,
QMI_TS_MMW_1,
QMI_TS_MMW_2,
QMI_TS_MMW_3,
QMI_TS_MODEM_SKIN,
QMI_TS_QFE_PA_MDM,
QMI_TS_QFE_PA_WTR,
QMI_TS_STREAMER_0,
QMI_TS_MOD_MMW_0,
QMI_TS_MOD_MMW_1,
QMI_TS_MOD_MMW_2,
QMI_TS_MOD_MMW_3,
QMI_TS_RET_PA_0,
QMI_TS_WTR_PA_0,
QMI_TS_WTR_PA_1,
QMI_TS_WTR_PA_2,
QMI_TS_WTR_PA_3,
QMI_SYS_THERM1,
QMI_SYS_THERM2,
QMI_TS_TSENS_1,
QMI_TS_RET_PA_0_FR1,
QMI_TS_WTR_PA_0_FR1,
QMI_TS_WTR_PA_1_FR1,
QMI_TS_WTR_PA_2_FR1,
QMI_TS_WTR_PA_3_FR1,
QMI_TS_MAX_NR
};
struct qmi_sensor {
struct device *dev;
char qmi_name[QMI_CLIENT_NAME_LENGTH];
bool connection_active;
struct list_head ts_node;
struct thermal_zone_device *tz_dev;
int32_t last_reading;
int32_t high_thresh;
int32_t low_thresh;
struct qmi_ts_instance *ts;
enum qmi_ts_sensor sens_type;
struct work_struct therm_notify_work;
};
struct qmi_ts_instance {
struct device *dev;
struct qmi_handle handle;
struct mutex mutex;
uint32_t inst_id;
struct list_head ts_sensor_list;
struct work_struct svc_arrive_work;
};
static struct qmi_ts_instance *ts_instances;
static int ts_inst_cnt;
static atomic_t in_suspend;
static char sensor_clients[QMI_TS_MAX_NR][QMI_CLIENT_NAME_LENGTH] = {
{"pa"},
{"pa_1"},
{"pa_2"},
{"qfe_pa0"},
{"qfe_wtr0"},
{"modem_tsens"},
{"qfe_mmw0"},
{"qfe_mmw1"},
{"qfe_mmw2"},
{"qfe_mmw3"},
{"xo_therm"},
{"qfe_pa_mdm"},
{"qfe_pa_wtr"},
{"qfe_mmw_streamer0"},
{"qfe_mmw0_mod"},
{"qfe_mmw1_mod"},
{"qfe_mmw2_mod"},
{"qfe_mmw3_mod"},
{"qfe_ret_pa0"},
{"qfe_wtr_pa0"},
{"qfe_wtr_pa1"},
{"qfe_wtr_pa2"},
{"qfe_wtr_pa3"},
{"sys_therm1"},
{"sys_therm2"},
{"modem_tsens1"},
{"qfe_ret_pa0_fr1"},
{"qfe_wtr_pa0_fr1"},
{"qfe_wtr_pa1_fr1"},
{"qfe_wtr_pa2_fr1"},
{"qfe_wtr_pa3_fr1"},
};
static int32_t encode_qmi(int32_t val)
{
uint32_t shift = 0, local_val = 0;
int32_t temp_val = 0;
if (val == INT_MAX || val == INT_MIN)
return 0;
temp_val = val = val / 1000;
if (val < 0) {
temp_val *= -1;
local_val |= 1 << QMI_FL_SIGN_BIT;
}
shift = find_last_bit((const unsigned long *)&temp_val,
sizeof(temp_val) * 8);
local_val |= ((shift + 127) << QMI_MANTISSA_MSB);
temp_val &= ~(1 << shift);
local_val |= temp_val << (QMI_MANTISSA_MSB - shift);
pr_debug("inp:%d shift:%d out:%x temp_val:%x\n",
val, shift, local_val, temp_val);
return local_val;
}
static int32_t decode_qmi(int32_t val)
{
int32_t sign = 0, shift = 0, local_val;
sign = (val & QMI_FL_SIGN) ? -1 : 1;
shift = (val & QMI_FL_EXP) >> QMI_MANTISSA_MSB;
shift = QMI_MANTISSA_MSB - (shift - 127);
local_val = (val & QMI_FL_MANTISSA) | QMI_FL_NORM;
pr_debug("val:0x%x sign:%d shift:%d mantissa:%x temp:%d\n",
val, sign, shift, local_val,
sign * (local_val >> shift));
return sign * (local_val >> shift);
}
static int qmi_sensor_pm_notify(struct notifier_block *nb,
unsigned long mode, void *_unused)
{
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
atomic_set(&in_suspend, 1);
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
atomic_set(&in_suspend, 0);
break;
default:
break;
}
return 0;
}
static struct notifier_block qmi_sensor_pm_nb = {
.notifier_call = qmi_sensor_pm_notify,
};
static void qmi_ts_thresh_notify(struct work_struct *work)
{
struct qmi_sensor *qmi_sens = container_of(work,
struct qmi_sensor,
therm_notify_work);
of_thermal_handle_trip(qmi_sens->tz_dev);
};
static void qmi_ts_update_temperature(struct qmi_ts_instance *ts,
const struct ts_temp_report_ind_msg_v01 *ind_msg,
uint8_t notify)
{
struct qmi_sensor *qmi_sens;
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if ((strncasecmp(qmi_sens->qmi_name,
ind_msg->sensor_id.sensor_id,
QMI_TS_SENSOR_ID_LENGTH_MAX_V01)))
continue;
qmi_sens->last_reading =
decode_qmi(ind_msg->temp) * 1000;
pr_debug("sensor:%s temperature:%d\n",
qmi_sens->qmi_name, qmi_sens->last_reading);
if (!qmi_sens->tz_dev)
return;
if (notify &&
((qmi_sens->high_thresh != INT_MAX &&
qmi_sens->last_reading >= qmi_sens->high_thresh) ||
(qmi_sens->low_thresh != INT_MIN &&
qmi_sens->last_reading <= qmi_sens->low_thresh))) {
pr_debug("Sensor:%s Notify. temp:%d\n",
ind_msg->sensor_id.sensor_id,
qmi_sens->last_reading);
queue_work(system_highpri_wq,
&qmi_sens->therm_notify_work);
}
return;
}
}
void qmi_ts_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
struct qmi_txn *txn, const void *decoded)
{
const struct ts_temp_report_ind_msg_v01 *ind_msg = decoded;
uint8_t notify = 0;
struct qmi_ts_instance *ts = container_of(qmi, struct qmi_ts_instance,
handle);
if (!txn) {
pr_err("Invalid transaction\n");
return;
}
if ((ind_msg->report_type != QMI_TS_TEMP_REPORT_CURRENT_TEMP_V01) ||
ind_msg->seq_num_valid)
notify = 1;
if (ind_msg->temp_valid)
qmi_ts_update_temperature(ts, ind_msg, notify);
else
pr_err("Error invalid temperature field.");
}
static int qmi_ts_request(struct qmi_sensor *qmi_sens,
bool send_current_temp_report)
{
int ret = 0;
struct ts_register_notification_temp_resp_msg_v01 resp;
struct ts_register_notification_temp_req_msg_v01 req;
struct qmi_ts_instance *ts = qmi_sens->ts;
struct qmi_txn txn;
memset(&req, 0, sizeof(req));
memset(&resp, 0, sizeof(resp));
strlcpy(req.sensor_id.sensor_id, qmi_sens->qmi_name,
QMI_TS_SENSOR_ID_LENGTH_MAX_V01);
req.seq_num = 0;
if (send_current_temp_report) {
req.send_current_temp_report = 1;
req.seq_num_valid = true;
} else {
req.seq_num_valid = false;
req.temp_threshold_high_valid =
qmi_sens->high_thresh != INT_MAX;
req.temp_threshold_high =
encode_qmi(qmi_sens->high_thresh);
req.temp_threshold_low_valid =
qmi_sens->low_thresh != INT_MIN;
req.temp_threshold_low =
encode_qmi(qmi_sens->low_thresh);
}
mutex_lock(&ts->mutex);
ret = qmi_txn_init(&ts->handle, &txn,
ts_register_notification_temp_resp_msg_v01_ei, &resp);
if (ret < 0) {
pr_err("qmi txn init failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
goto qmi_send_exit;
}
ret = qmi_send_request(&ts->handle, NULL, &txn,
QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V01,
TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V01_MAX_MSG_LEN,
ts_register_notification_temp_req_msg_v01_ei, &req);
if (ret < 0) {
pr_err("qmi txn send failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
qmi_txn_cancel(&txn);
goto qmi_send_exit;
}
ret = qmi_txn_wait(&txn, QMI_TS_RESP_TOUT);
if (ret < 0) {
pr_err("qmi txn wait failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
goto qmi_send_exit;
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ret = resp.resp.result;
pr_err("qmi NOT success for %s ret:%d\n",
qmi_sens->qmi_name, ret);
goto qmi_send_exit;
}
ret = 0;
qmi_send_exit:
mutex_unlock(&ts->mutex);
return ret;
}
static int qmi_sensor_read(void *data, int *temp)
{
struct qmi_sensor *qmi_sens = (struct qmi_sensor *)data;
if (qmi_sens->connection_active && !atomic_read(&in_suspend))
qmi_ts_request(qmi_sens, true);
*temp = qmi_sens->last_reading;
return 0;
}
static int qmi_sensor_set_trips(void *data, int low, int high)
{
struct qmi_sensor *qmi_sens = (struct qmi_sensor *)data;
int ret = 0;
if (qmi_sens->high_thresh == high &&
qmi_sens->low_thresh == low)
return ret;
qmi_sens->high_thresh = high;
qmi_sens->low_thresh = low;
if (!qmi_sens->connection_active)
return ret;
ret = qmi_ts_request(qmi_sens, false);
if (ret)
pr_err("Sensor:%s set high trip:%d low trip:%d error%d\n",
qmi_sens->qmi_name,
qmi_sens->high_thresh,
qmi_sens->low_thresh,
ret);
return ret;
}
static struct thermal_zone_of_device_ops qmi_sensor_ops = {
.get_temp = qmi_sensor_read,
.set_trips = qmi_sensor_set_trips,
};
static struct qmi_msg_handler handlers[] = {
{
.type = QMI_INDICATION,
.msg_id = QMI_TS_TEMP_REPORT_IND_V01,
.ei = ts_temp_report_ind_msg_v01_ei,
.decoded_size = sizeof(struct ts_temp_report_ind_msg_v01),
.fn = qmi_ts_ind_cb
},
{}
};
static int qmi_register_sensor_device(struct qmi_sensor *qmi_sens)
{
int ret = 0;
qmi_sens->tz_dev = thermal_zone_of_sensor_register(
qmi_sens->dev,
qmi_sens->sens_type + qmi_sens->ts->inst_id,
qmi_sens, &qmi_sensor_ops);
if (IS_ERR(qmi_sens->tz_dev)) {
ret = PTR_ERR(qmi_sens->tz_dev);
if (ret != -ENODEV)
pr_err("sensor register failed for %s, ret:%ld\n",
qmi_sens->qmi_name, ret);
qmi_sens->tz_dev = NULL;
return ret;
}
pr_debug("Sensor register success for %s\n", qmi_sens->qmi_name);
return 0;
}
static int verify_sensor_and_register(struct qmi_ts_instance *ts)
{
struct ts_get_sensor_list_req_msg_v01 req;
struct ts_get_sensor_list_resp_msg_v01 *ts_resp;
int ret = 0, i;
struct qmi_txn txn;
memset(&req, 0, sizeof(req));
/* size of ts_resp is very high, use heap memory rather than stack */
ts_resp = kzalloc(sizeof(*ts_resp), GFP_KERNEL);
if (!ts_resp)
return -ENOMEM;
mutex_lock(&ts->mutex);
ret = qmi_txn_init(&ts->handle, &txn,
ts_get_sensor_list_resp_msg_v01_ei, ts_resp);
if (ret < 0) {
pr_err("Transaction Init error for inst_id:0x%x ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
ret = qmi_send_request(&ts->handle, NULL, &txn,
QMI_TS_GET_SENSOR_LIST_REQ_V01,
TS_GET_SENSOR_LIST_REQ_MSG_V01_MAX_MSG_LEN,
ts_get_sensor_list_req_msg_v01_ei,
&req);
if (ret < 0) {
qmi_txn_cancel(&txn);
goto reg_exit;
}
ret = qmi_txn_wait(&txn, QMI_TS_RESP_TOUT);
if (ret < 0) {
pr_err("Transaction wait error for inst_id:0x%x ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
if (ts_resp->resp.result != QMI_RESULT_SUCCESS_V01) {
ret = ts_resp->resp.result;
pr_err("Get sensor list NOT success for inst_id:0x%x ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
mutex_unlock(&ts->mutex);
for (i = 0; i < ts_resp->sensor_list_len; i++) {
struct qmi_sensor *qmi_sens = NULL;
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if ((strncasecmp(qmi_sens->qmi_name,
ts_resp->sensor_list[i].sensor_id,
QMI_TS_SENSOR_ID_LENGTH_MAX_V01)))
continue;
qmi_sens->connection_active = true;
/*
* Send a temperature request notification.
*/
qmi_ts_request(qmi_sens, true);
if (!qmi_sens->tz_dev)
ret = qmi_register_sensor_device(qmi_sens);
break;
}
}
kfree(ts_resp);
return ret;
reg_exit:
mutex_unlock(&ts->mutex);
kfree(ts_resp);
return ret;
}
static void qmi_ts_svc_arrive(struct work_struct *work)
{
struct qmi_ts_instance *ts = container_of(work,
struct qmi_ts_instance,
svc_arrive_work);
verify_sensor_and_register(ts);
}
static void thermal_qmi_net_reset(struct qmi_handle *qmi)
{
struct qmi_ts_instance *ts = container_of(qmi,
struct qmi_ts_instance,
handle);
struct qmi_sensor *qmi_sens = NULL;
int ret;
pr_debug("reset QMI server\n");
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if (!qmi_sens->connection_active)
continue;
qmi_ts_request(qmi_sens, true);
ret = qmi_ts_request(qmi_sens, false);
if (ret)
pr_err("Sensor:%s set high trip:%d low trip:%d err%d\n",
qmi_sens->tz_dev->type,
qmi_sens->high_thresh,
qmi_sens->low_thresh,
ret);
}
}
static void thermal_qmi_del_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct qmi_ts_instance *ts = container_of(qmi,
struct qmi_ts_instance,
handle);
struct qmi_sensor *qmi_sens = NULL;
pr_debug("QMI server deleted\n");
list_for_each_entry(qmi_sens, &ts->ts_sensor_list, ts_node)
qmi_sens->connection_active = false;
}
static int thermal_qmi_new_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct qmi_ts_instance *ts = container_of(qmi,
struct qmi_ts_instance,
handle);
struct sockaddr_qrtr sq = {AF_QIPCRTR, service->node, service->port};
mutex_lock(&ts->mutex);
kernel_connect(qmi->sock, (struct sockaddr *)&sq, sizeof(sq), 0);
mutex_unlock(&ts->mutex);
queue_work(system_highpri_wq, &ts->svc_arrive_work);
return 0;
}
static struct qmi_ops thermal_qmi_event_ops = {
.new_server = thermal_qmi_new_server,
.del_server = thermal_qmi_del_server,
.net_reset = thermal_qmi_net_reset,
};
static void qmi_ts_cleanup(void)
{
struct qmi_ts_instance *ts;
struct qmi_sensor *qmi_sens, *c_next;
int idx = 0;
for (; idx < ts_inst_cnt; idx++) {
ts = &ts_instances[idx];
mutex_lock(&ts->mutex);
list_for_each_entry_safe(qmi_sens, c_next,
&ts->ts_sensor_list, ts_node) {
qmi_sens->connection_active = false;
if (qmi_sens->tz_dev)
thermal_zone_of_sensor_unregister(
qmi_sens->dev, qmi_sens->tz_dev);
list_del(&qmi_sens->ts_node);
}
qmi_handle_release(&ts->handle);
mutex_unlock(&ts->mutex);
}
ts_inst_cnt = 0;
}
static int of_get_qmi_ts_platform_data(struct device *dev)
{
int ret = 0, i = 0, idx = 0;
struct device_node *np = dev->of_node;
struct device_node *subsys_np;
struct qmi_ts_instance *ts;
struct qmi_sensor *qmi_sens;
int sens_name_max = 0, sens_idx = 0, subsys_cnt = 0;
subsys_cnt = of_get_available_child_count(np);
if (!subsys_cnt) {
dev_err(dev, "No child node to process\n");
return -EFAULT;
}
ts = devm_kcalloc(dev, subsys_cnt, sizeof(*ts), GFP_KERNEL);
if (!ts)
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",
&ts[idx].inst_id);
if (ret) {
dev_err(dev, "error reading qcom,insance-id. ret:%d\n",
ret);
goto data_fetch_err;
}
ts[idx].dev = dev;
mutex_init(&ts[idx].mutex);
INIT_LIST_HEAD(&ts[idx].ts_sensor_list);
INIT_WORK(&ts[idx].svc_arrive_work, qmi_ts_svc_arrive);
sens_name_max = of_property_count_strings(subsys_np,
"qcom,qmi-sensor-names");
if (sens_name_max <= 0) {
dev_err(dev, "Invalid or no sensor. err:%d\n",
sens_name_max);
ret = -EINVAL;
goto data_fetch_err;
}
for (sens_idx = 0; sens_idx < sens_name_max; sens_idx++) {
const char *qmi_name;
qmi_sens = devm_kzalloc(dev, sizeof(*qmi_sens),
GFP_KERNEL);
if (!qmi_sens) {
ret = -ENOMEM;
goto data_fetch_err;
}
of_property_read_string_index(subsys_np,
"qcom,qmi-sensor-names", sens_idx,
&qmi_name);
strlcpy(qmi_sens->qmi_name, qmi_name,
QMI_CLIENT_NAME_LENGTH);
/* Check for supported qmi sensors */
for (i = 0; i < QMI_TS_MAX_NR; i++) {
if (!strcmp(sensor_clients[i],
qmi_sens->qmi_name))
break;
}
if (i >= QMI_TS_MAX_NR) {
dev_err(dev, "Unknown sensor:%s\n",
qmi_sens->qmi_name);
ret = -EINVAL;
goto data_fetch_err;
}
dev_dbg(dev, "QMI sensor:%s available\n", qmi_name);
qmi_sens->sens_type = i;
qmi_sens->ts = &ts[idx];
qmi_sens->dev = dev;
qmi_sens->last_reading = 0;
qmi_sens->high_thresh = INT_MAX;
qmi_sens->low_thresh = INT_MIN;
INIT_WORK(&qmi_sens->therm_notify_work,
qmi_ts_thresh_notify);
list_add(&qmi_sens->ts_node, &ts[idx].ts_sensor_list);
}
idx++;
}
ts_instances = ts;
ts_inst_cnt = subsys_cnt;
data_fetch_err:
of_node_put(subsys_np);
return ret;
}
static int qmi_sens_device_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0, idx = 0;
struct qmi_ts_instance *ts;
ret = of_get_qmi_ts_platform_data(dev);
if (ret)
goto probe_err;
if (!ts_instances || !ts_inst_cnt) {
dev_err(dev, "Empty ts instances\n");
return -EINVAL;
}
for (; idx < ts_inst_cnt; idx++) {
ts = &ts_instances[idx];
if (list_empty(&ts->ts_sensor_list)) {
ret = -ENODEV;
goto probe_err;
}
ret = qmi_handle_init(&ts->handle,
TS_GET_SENSOR_LIST_RESP_MSG_V01_MAX_MSG_LEN,
&thermal_qmi_event_ops, handlers);
if (ret < 0) {
dev_err(dev, "QMI[0x%x] handle init failed. err:%d\n",
ts->inst_id, ret);
goto probe_err;
}
ret = qmi_add_lookup(&ts->handle, TS_SERVICE_ID_V01,
TS_SERVICE_VERS_V01, ts->inst_id);
if (ret < 0) {
dev_err(dev, "QMI register failed for 0x%x, ret:%d\n",
ts->inst_id, ret);
goto probe_err;
}
}
atomic_set(&in_suspend, 0);
register_pm_notifier(&qmi_sensor_pm_nb);
return 0;
probe_err:
qmi_ts_cleanup();
return ret;
}
static int qmi_sens_device_remove(struct platform_device *pdev)
{
qmi_ts_cleanup();
unregister_pm_notifier(&qmi_sensor_pm_nb);
return 0;
}
static const struct of_device_id qmi_sens_device_match[] = {
{.compatible = "qcom,qmi-sensors"},
{}
};
static struct platform_driver qmi_sens_device_driver = {
.probe = qmi_sens_device_probe,
.remove = qmi_sens_device_remove,
.driver = {
.name = QMI_SENS_DRIVER,
.owner = THIS_MODULE,
.of_match_table = qmi_sens_device_match,
},
};
builtin_platform_driver(qmi_sens_device_driver);