Merge "driver: thermal: userspace_cdev: Add a snapshot of userspace cooling driver"

This commit is contained in:
qctecmdr 2022-11-11 05:32:29 -08:00 committed by Gerrit - the friendly Code Review server
commit 4c27283738
19 changed files with 4076 additions and 6 deletions

View File

@ -62,3 +62,81 @@ config QTI_BCL_PMIC5
voltage sensors with the thermal core framework and can take
threshold input and notify the thermal core when the threshold is
reached.
config QTI_BCL_SOC_DRIVER
tristate "QTI Battery state of charge sensor driver"
depends on THERMAL && POWER_SUPPLY
help
This driver registers battery state of charge as a sensor with
thermal framework. This sensor can monitor for state of charge
thresholds and notify the thermal framework when the thresholds
are reached and cleared. This will help to monitor and apply any
mitigation when state of charge goes below a certain threshold.
config QTI_POLICY_ENGINE_SENSOR
tristate "QTI Policy Engine Sensor device"
depends on THERMAL
help
This enables the QTI Policy Engine sensor device. This driver will
register the policy engine recommendation with thermal framework as
a sensor. This will enable to provide configuration to mitigate
cooling devices when a recommendation is sent from hardware.
config QTI_QMI_COOLING_DEVICE
tristate "QTI QMI cooling devices"
depends on QCOM_QMI_HELPERS && THERMAL
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.
config QTI_QMI_SENSOR_V2
tristate "QTI QMI TS V2 sensor driver"
depends on THERMAL_OF
select QCOM_QMI_HELPERS
help
This enables to list the QTI remote subsystem temperature sensors
with QMI TS V2 interface. This driver can read the temperature of
the remote sensor. These sensors can take thresholds and notify the
thermal framework when the threshold is reached.
config QTI_CPU_VOLTAGE_COOLING_DEVICE
tristate "QTI CPU VOLTAGE cooling devices"
depends on CPU_FREQ && THERMAL
help
This enables the QTI CPU Voltage cooling devices. This cooling
device will allow the CPUs with different frequency plan in a
cluster to be mitigated together based on the voltages. This will
decrease or increase the voltages in a cluster based on thermal
conditions.
config QTI_CPU_HOTPLUG_COOLING_DEVICE
tristate "QTI CPU Hotplug cooling devices"
depends on THERMAL && HOTPLUG_CPU
help
This enables the QTI CPU Hotplug cooling devices. These cooling
devices will be used by QTI chipset to hotplug a CPU to achieve
thermal cooling. CPU Hotplug will be done after core isolation,
to prevent any process from waking the mitigated CPU.
config QTI_DDR_COOLING_DEVICE
tristate "QTI DDR cooling devices"
depends on THERMAL && INTERCONNECT
help
This enables the QTI DDR cooling devices. These cooling
devices will be used by QTI chipset to place a DDR state request
to meet the performance requirement under thermally constrained
conditions.
config QTI_USERSPACE_CDEV
tristate "QTI Userspace cooling device"
depends on THERMAL_OF
help
This enables the QTI userspace cooling device, which will help
send the mitigation notification via thermal framework netlink
socket, so that userspace cooling device can perform a mitigation
action.

View File

@ -1,10 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
tsens-8960.o
obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_QCOM_LMH) += lmh.o
obj-$(CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE) += thermal_pause.o
obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o
obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o
obj-$(CONFIG_QTI_POLICY_ENGINE_SENSOR) += policy_engine.o
obj-$(CONFIG_QTI_QMI_COOLING_DEVICE) += qti_qmi_cdev.o
qti_qmi_cdev-y += thermal_mitigation_device_service_v01.o qmi_cooling.o
obj-$(CONFIG_QTI_QMI_SENSOR_V2) += qti_qmi_sensor_v2.o
qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o
obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o
obj-$(CONFIG_QTI_CPU_HOTPLUG_COOLING_DEVICE) += cpu_hotplug.o
obj-$(CONFIG_QTI_DDR_COOLING_DEVICE) += ddr_cdev.o
obj-$(CONFIG_QTI_USERSPACE_CDEV) += qti_userspace_cdev.o

View File

@ -22,6 +22,7 @@
#include <linux/slab.h>
#include <linux/nvmem-consumer.h>
#include <linux/ipc_logging.h>
#include "thermal_zone_internal.h"
#define BCL_DRIVER_NAME "bcl_pmic5"
#define BCL_MONITOR_EN 0x46
@ -799,6 +800,7 @@ static void bcl_lvl_init(struct platform_device *pdev,
return;
}
thermal_zone_device_update(lbat->tz_dev, THERMAL_DEVICE_UP);
qti_update_tz_ops(lbat->tz_dev, true);
}
static void bcl_probe_lvls(struct platform_device *pdev,
@ -856,6 +858,7 @@ static int bcl_remove(struct platform_device *pdev)
continue;
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->param[i].tz_dev);
qti_update_tz_ops(bcl_perph->param[i].tz_dev, false);
}
return 0;

View File

@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/power_supply.h>
#include <linux/thermal.h>
#include "thermal_zone_internal.h"
#define BCL_DRIVER_NAME "bcl_soc_peripheral"
struct bcl_device {
struct device *dev;
struct notifier_block psy_nb;
struct work_struct soc_eval_work;
long trip_temp;
int trip_val;
struct mutex state_trans_lock;
bool irq_enabled;
struct thermal_zone_device *tz_dev;
struct thermal_zone_of_device_ops ops;
};
static struct bcl_device *bcl_perph;
static int bcl_soc_get_trend(void *data, int trip,
enum thermal_trend *trend)
{
struct bcl_device *bcl_perph = data;
struct thermal_zone_device *tz = bcl_perph->tz_dev;
int trip_temp = 0, trip_hyst = 0, temp, ret;
if (!tz)
return -EINVAL;
ret = tz->ops->get_trip_temp(tz, trip, &trip_temp);
if (ret)
return ret;
if (tz->ops->get_trip_hyst) {
ret = tz->ops->get_trip_hyst(tz, trip, &trip_hyst);
if (ret)
return ret;
}
temp = READ_ONCE(tz->temperature);
if (temp >= trip_temp)
*trend = THERMAL_TREND_RAISING;
else if (!trip_hyst && temp < trip_temp)
*trend = THERMAL_TREND_DROPPING;
else if (temp <= (trip_temp - trip_hyst))
*trend = THERMAL_TREND_DROPPING;
else
*trend = THERMAL_TREND_STABLE;
return 0;
}
static int bcl_set_soc(void *data, int low, int high)
{
if (high == bcl_perph->trip_temp)
return 0;
mutex_lock(&bcl_perph->state_trans_lock);
pr_debug("socd threshold:%d\n", high);
bcl_perph->trip_temp = high;
if (high == INT_MAX) {
bcl_perph->irq_enabled = false;
goto unlock_and_exit;
}
bcl_perph->irq_enabled = true;
schedule_work(&bcl_perph->soc_eval_work);
unlock_and_exit:
mutex_unlock(&bcl_perph->state_trans_lock);
return 0;
}
static int bcl_read_soc(void *data, int *val)
{
static struct power_supply *batt_psy;
union power_supply_propval ret = {0,};
int err = 0;
*val = 0;
if (!batt_psy)
batt_psy = power_supply_get_by_name("battery");
if (batt_psy) {
err = power_supply_get_property(batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
if (err) {
pr_err("battery percentage read error:%d\n",
err);
return err;
}
*val = 100 - ret.intval;
}
pr_debug("soc:%d\n", *val);
return err;
}
static void bcl_evaluate_soc(struct work_struct *work)
{
int battery_depletion;
if (!bcl_perph->tz_dev)
return;
if (bcl_read_soc(NULL, &battery_depletion))
return;
mutex_lock(&bcl_perph->state_trans_lock);
if (!bcl_perph->irq_enabled)
goto eval_exit;
if (battery_depletion < bcl_perph->trip_temp)
goto eval_exit;
bcl_perph->trip_val = battery_depletion;
mutex_unlock(&bcl_perph->state_trans_lock);
thermal_zone_device_update(bcl_perph->tz_dev,
THERMAL_TRIP_VIOLATED);
return;
eval_exit:
mutex_unlock(&bcl_perph->state_trans_lock);
}
static int battery_supply_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
struct power_supply *psy = data;
if (strcmp(psy->desc->name, "battery"))
return NOTIFY_OK;
schedule_work(&bcl_perph->soc_eval_work);
return NOTIFY_OK;
}
static int bcl_soc_remove(struct platform_device *pdev)
{
power_supply_unreg_notifier(&bcl_perph->psy_nb);
flush_work(&bcl_perph->soc_eval_work);
if (bcl_perph->tz_dev) {
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->tz_dev);
qti_update_tz_ops(bcl_perph->tz_dev, false);
}
return 0;
}
static int bcl_soc_probe(struct platform_device *pdev)
{
int ret = 0;
bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL);
if (!bcl_perph)
return -ENOMEM;
mutex_init(&bcl_perph->state_trans_lock);
bcl_perph->dev = &pdev->dev;
bcl_perph->ops.get_temp = bcl_read_soc;
bcl_perph->ops.set_trips = bcl_set_soc;
bcl_perph->ops.get_trend = bcl_soc_get_trend;
INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc);
bcl_perph->psy_nb.notifier_call = battery_supply_callback;
ret = power_supply_reg_notifier(&bcl_perph->psy_nb);
if (ret < 0) {
pr_err("soc notifier registration error. defer. err:%d\n",
ret);
ret = -EPROBE_DEFER;
goto bcl_soc_probe_exit;
}
bcl_perph->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
0, bcl_perph, &bcl_perph->ops);
if (IS_ERR(bcl_perph->tz_dev)) {
pr_err("soc TZ register failed. err:%ld\n",
PTR_ERR(bcl_perph->tz_dev));
ret = PTR_ERR(bcl_perph->tz_dev);
bcl_perph->tz_dev = NULL;
goto bcl_soc_probe_exit;
}
qti_update_tz_ops(bcl_perph->tz_dev, true);
thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP);
schedule_work(&bcl_perph->soc_eval_work);
dev_set_drvdata(&pdev->dev, bcl_perph);
return 0;
bcl_soc_probe_exit:
bcl_soc_remove(pdev);
return ret;
}
static const struct of_device_id bcl_match[] = {
{
.compatible = "qcom,msm-bcl-soc",
},
{},
};
static struct platform_driver bcl_driver = {
.probe = bcl_soc_probe,
.remove = bcl_soc_remove,
.driver = {
.name = BCL_DRIVER_NAME,
.of_match_table = bcl_match,
},
};
module_platform_driver(bcl_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/device.h>
#define CPU_HOTPLUG_LEVEL 1
struct cpu_hot_cdev {
struct list_head node;
int cpu_id;
bool cpu_hot_state;
bool cpu_cur_state;
struct thermal_cooling_device *cdev;
struct device_node *np;
struct work_struct reg_work;
struct work_struct exec_work;
};
static enum cpuhp_state cpu_hp_online;
static DEFINE_MUTEX(cpu_hot_lock);
static LIST_HEAD(cpu_hot_cdev_list);
static int cpu_hot_hp_online(unsigned int online_cpu)
{
struct cpu_hot_cdev *cpu_hot_cdev;
int ret = 0;
mutex_lock(&cpu_hot_lock);
list_for_each_entry(cpu_hot_cdev, &cpu_hot_cdev_list, node) {
if (online_cpu != cpu_hot_cdev->cpu_id)
continue;
if (cpu_hot_cdev->cdev) {
if (cpu_hot_cdev->cpu_hot_state) {
pr_debug("Offline CPU:%d\n",
online_cpu);
ret = NOTIFY_BAD;
}
} else
queue_work(system_highpri_wq, &cpu_hot_cdev->reg_work);
break;
}
mutex_unlock(&cpu_hot_lock);
return ret;
}
/**
* cpu_hot_set_cur_state - callback function to set the current cooling
* state.
* @cdev: thermal cooling device pointer.
* @state: set this variable to the current cooling state.
*
* Callback for the thermal cooling device to change the cpu hotplug
* current cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int cpu_hot_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cpu_hot_cdev *cpu_hot_cdev = cdev->devdata;
if (cpu_hot_cdev->cpu_id == -1)
return -ENODEV;
/* Request state should be less than max_level */
if (state > CPU_HOTPLUG_LEVEL)
return -EINVAL;
state = !!state;
/* Check if the old cooling action is same as new cooling action */
if (cpu_hot_cdev->cpu_hot_state == state)
return 0;
mutex_lock(&cpu_hot_lock);
cpu_hot_cdev->cpu_hot_state = state;
mutex_unlock(&cpu_hot_lock);
queue_work(system_highpri_wq, &cpu_hot_cdev->exec_work);
return 0;
}
/**
* cpu_hot_get_cur_state - callback function to get the current cooling
* state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the current cooling state.
*
* Callback for the thermal cooling device to return the cpu hotplug
* current cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int cpu_hot_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct cpu_hot_cdev *cpu_hot_cdev = cdev->devdata;
mutex_lock(&cpu_hot_lock);
*state = (cpu_hot_cdev->cpu_hot_state) ?
CPU_HOTPLUG_LEVEL : 0;
mutex_unlock(&cpu_hot_lock);
return 0;
}
/**
* cpu_hot_get_max_state - callback function to get the max cooling state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the max cooling state.
*
* Callback for the thermal cooling device to return the cpu
* hotplug max cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int cpu_hot_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = CPU_HOTPLUG_LEVEL;
return 0;
}
static struct thermal_cooling_device_ops cpu_hot_cooling_ops = {
.get_max_state = cpu_hot_get_max_state,
.get_cur_state = cpu_hot_get_cur_state,
.set_cur_state = cpu_hot_set_cur_state,
};
static void cpu_hot_execute_cdev(struct work_struct *work)
{
struct cpu_hot_cdev *cpu_hot_cdev =
container_of(work, struct cpu_hot_cdev, exec_work);
int ret = 0, cpu = 0;
mutex_lock(&cpu_hot_lock);
cpu = cpu_hot_cdev->cpu_id;
if (cpu_hot_cdev->cpu_hot_state == CPU_HOTPLUG_LEVEL) {
if (!cpu_hot_cdev->cpu_cur_state)
goto unlock_exit;
mutex_unlock(&cpu_hot_lock);
ret = remove_cpu(cpu);
mutex_lock(&cpu_hot_lock);
if (ret < 0)
pr_err("CPU:%d offline error:%d\n", cpu, ret);
else
cpu_hot_cdev->cpu_cur_state = false;
} else {
if (cpu_hot_cdev->cpu_cur_state)
goto unlock_exit;
mutex_unlock(&cpu_hot_lock);
ret = add_cpu(cpu);
mutex_lock(&cpu_hot_lock);
if (ret)
pr_err("CPU:%d online error:%d\n", cpu, ret);
else
cpu_hot_cdev->cpu_cur_state = true;
}
unlock_exit:
mutex_unlock(&cpu_hot_lock);
}
static void cpu_hot_register_cdev(struct work_struct *work)
{
struct cpu_hot_cdev *cpu_hot_cdev =
container_of(work, struct cpu_hot_cdev, reg_work);
char cdev_name[THERMAL_NAME_LENGTH] = "";
int ret = 0;
snprintf(cdev_name, THERMAL_NAME_LENGTH, "cpu-hotplug%d",
cpu_hot_cdev->cpu_id);
cpu_hot_cdev->cdev = thermal_of_cooling_device_register(
cpu_hot_cdev->np,
cdev_name,
cpu_hot_cdev,
&cpu_hot_cooling_ops);
if (IS_ERR(cpu_hot_cdev->cdev)) {
ret = PTR_ERR(cpu_hot_cdev->cdev);
pr_err("Cooling register failed for %s, ret:%d\n",
cdev_name, ret);
cpu_hot_cdev->cdev = NULL;
return;
}
pr_debug("Cooling device [%s] registered.\n", cdev_name);
}
static int cpu_hot_probe(struct platform_device *pdev)
{
int ret = 0, cpu = 0;
struct device_node *dev_phandle, *subsys_np = NULL;
struct device *cpu_dev;
struct cpu_hot_cdev *cpu_hot_cdev = NULL;
struct device_node *np = pdev->dev.of_node;
INIT_LIST_HEAD(&cpu_hot_cdev_list);
for_each_available_child_of_node(np, subsys_np) {
cpu_hot_cdev = devm_kzalloc(&pdev->dev,
sizeof(*cpu_hot_cdev), GFP_KERNEL);
if (!cpu_hot_cdev) {
of_node_put(subsys_np);
return -ENOMEM;
}
cpu_hot_cdev->cpu_id = -1;
cpu_hot_cdev->cpu_hot_state = false;
cpu_hot_cdev->cpu_cur_state = true;
cpu_hot_cdev->cdev = NULL;
cpu_hot_cdev->np = subsys_np;
dev_phandle = of_parse_phandle(subsys_np, "qcom,cpu", 0);
for_each_possible_cpu(cpu) {
cpu_dev = get_cpu_device(cpu);
if (cpu_dev && cpu_dev->of_node == dev_phandle) {
cpu_hot_cdev->cpu_id = cpu;
break;
}
}
if (cpu_hot_cdev->cpu_id == -1) {
dev_err(&pdev->dev, "Invalid CPU phandle\n");
continue;
}
INIT_WORK(&cpu_hot_cdev->reg_work,
cpu_hot_register_cdev);
INIT_WORK(&cpu_hot_cdev->exec_work,
cpu_hot_execute_cdev);
list_add(&cpu_hot_cdev->node, &cpu_hot_cdev_list);
}
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cpu-hotplug/cdev:online",
cpu_hot_hp_online, NULL);
if (ret < 0)
return ret;
cpu_hp_online = ret;
return 0;
}
static int cpu_hot_remove(struct platform_device *pdev)
{
struct cpu_hot_cdev *cpu_hot_cdev = NULL, *next = NULL;
int ret = 0;
if (cpu_hp_online) {
cpuhp_remove_state_nocalls(cpu_hp_online);
cpu_hp_online = 0;
}
mutex_lock(&cpu_hot_lock);
list_for_each_entry_safe(cpu_hot_cdev, next, &cpu_hot_cdev_list, node) {
if (!cpu_hot_cdev->cdev)
goto loop_skip;
thermal_cooling_device_unregister(cpu_hot_cdev->cdev);
if (cpu_hot_cdev->cpu_hot_state) {
cpu_hot_cdev->cpu_hot_state = false;
ret = add_cpu(cpu_hot_cdev->cpu_id);
if (ret)
pr_err("CPU:%d online error:%d\n",
cpu_hot_cdev->cpu_id, ret);
}
loop_skip:
list_del(&cpu_hot_cdev->node);
}
mutex_unlock(&cpu_hot_lock);
return 0;
}
static const struct of_device_id cpu_hot_match[] = {
{ .compatible = "qcom,cpu-hotplug", },
{},
};
static struct platform_driver cpu_hot_driver = {
.probe = cpu_hot_probe,
.remove = cpu_hot_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = cpu_hot_match,
},
};
module_platform_driver(cpu_hot_driver);
MODULE_DESCRIPTION("CPU Hotplug cooling device driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <linux/thermal.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/pm_opp.h>
#include <linux/pm_qos.h>
#define CPU_MAP_CT 2
#define CC_CDEV_DRIVER "CPU-voltage-cdev"
struct limits_freq_table {
unsigned long frequency;
unsigned long volt;
};
struct limits_freq_map {
unsigned long frequency[CPU_MAP_CT];
};
struct cc_limits_data {
struct list_head node;
int map_freq_ct;
int thermal_state;
int cpu_map[CPU_MAP_CT];
struct limits_freq_map *map_freq;
struct freq_qos_request cc_qos_req[CPU_MAP_CT];
char cdev_name[THERMAL_NAME_LENGTH];
struct thermal_cooling_device *cdev;
};
static DEFINE_MUTEX(cc_list_lock);
static LIST_HEAD(cc_cdev_list);
static int cc_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cc_limits_data *cc_cdev = cdev->devdata;
int idx = 0, ret = 0;
if (state > cc_cdev->map_freq_ct)
return -EINVAL;
if (state == cc_cdev->thermal_state)
return 0;
cc_cdev->thermal_state = state;
for (idx = 0; idx < CPU_MAP_CT; idx++) {
pr_debug("Mitigate CPU:%d to freq:%lu\n", cc_cdev->cpu_map[idx],
cc_cdev->map_freq[state].frequency[idx]);
ret = freq_qos_update_request(&cc_cdev->cc_qos_req[idx],
cc_cdev->map_freq[state].frequency[idx]);
if (ret < 0)
return ret;
}
return 0;
}
static int cc_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct cc_limits_data *cc_cdev = cdev->devdata;
*state = cc_cdev->thermal_state;
return 0;
}
static int cc_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct cc_limits_data *cc_cdev = cdev->devdata;
*state = cc_cdev->map_freq_ct;
return 0;
}
static struct thermal_cooling_device_ops cc_cooling_ops = {
.get_max_state = cc_get_max_state,
.get_cur_state = cc_get_cur_state,
.set_cur_state = cc_set_cur_state,
};
static int fetch_opp_table(struct device *dev,
struct limits_freq_table **freq_table_inp)
{
int idx = 0, max_opp_ct;
struct limits_freq_table *freq_table = NULL;
struct dev_pm_opp *opp;
unsigned long freq = 0;
max_opp_ct = dev_pm_opp_get_opp_count(dev);
if (max_opp_ct <= 0)
return max_opp_ct;
freq_table = kcalloc(max_opp_ct, sizeof(*freq_table), GFP_KERNEL);
if (!freq_table)
return -ENOMEM;
for (; idx < max_opp_ct; idx++, freq++) {
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
if (IS_ERR(opp)) {
pr_err("Error fetching freq\n");
goto fetch_err_exit;
}
freq_table[idx].frequency = freq / 1000; //MHz
freq_table[idx].volt = dev_pm_opp_get_voltage(opp) / 1000; //mV
pr_debug("%d: freq:%lu Mhz volt:%lu mv\n", idx,
freq_table[idx].frequency,
freq_table[idx].volt);
dev_pm_opp_put(opp);
}
*freq_table_inp = freq_table;
return max_opp_ct;
fetch_err_exit:
kfree(freq_table);
return -EINVAL;
}
static int build_unified_table(struct cc_limits_data *cc_cdev,
struct limits_freq_table **table, int *table_ct,
int *cpu, int cpu_ct)
{
struct limits_freq_map *freq_map = NULL;
int idx = 0, idy = 0, idz = 0, min_idx = 0, max_v = 0, max_idx = 0;
for (idx = 0; idx < cpu_ct; idx++) {
int table_v = table[idx][table_ct[idx] - 1].volt;
if ((table_v > max_v) || (table_v == max_v &&
table_ct[idx] > table_ct[max_idx])) {
max_v = table_v;
max_idx = idx;
}
}
cc_cdev->thermal_state = 0;
cc_cdev->map_freq_ct = table_ct[max_idx] - 1;
min_idx = !max_idx;
cc_cdev->cpu_map[0] = cpu[max_idx];
cc_cdev->cpu_map[1] = cpu[min_idx];
freq_map = kcalloc(table_ct[max_idx], sizeof(*freq_map), GFP_KERNEL);
if (!freq_map)
return -ENOMEM;
pr_info("CPU1:%d CPU2:%d\n", cc_cdev->cpu_map[0], cc_cdev->cpu_map[1]);
for (idx = table_ct[max_idx] - 1, idy = table_ct[min_idx] - 1, idz = 0;
idx >= 0 && idz < table_ct[max_idx]; idx--, idz++) {
int volt = table[max_idx][idx].volt;
freq_map[idz].frequency[0] = table[max_idx][idx].frequency;
for (; idy >= 0 ; idy--) {
if (table[min_idx][idy].volt <= volt)
break;
}
if (idy < 0)
idy = 0;
freq_map[idz].frequency[1] = table[min_idx][idy].frequency;
pr_info("freq1:%u freq2:%u\n", freq_map[idz].frequency[0],
freq_map[idz].frequency[1]);
}
cc_cdev->map_freq = freq_map;
return 0;
}
static struct cc_limits_data *opp_init(int *cpus)
{
int cpu1, cpu2;
struct device *cpu1_dev, *cpu2_dev;
struct limits_freq_table *cpu1_freq_table, *cpu2_freq_table;
struct limits_freq_table *cpu_freq_table[CPU_MAP_CT];
int table_ct[CPU_MAP_CT], ret = 0;
struct cc_limits_data *cc_cdev = NULL;
cpu1 = cpus[0];
cpu2 = cpus[1];
cpu1_dev = get_cpu_device(cpu1);
if (!cpu1_dev) {
pr_err("couldn't find cpu:%d\n", cpu1);
return ERR_PTR(-ENODEV);
}
cpu2_dev = get_cpu_device(cpu2);
if (!cpu2_dev) {
pr_err("couldn't find cpu:%d\n", cpu2);
return ERR_PTR(-ENODEV);
}
table_ct[0] = fetch_opp_table(cpu1_dev, &cpu1_freq_table);
if (table_ct[0] <= 0)
goto opp_err_exit;
table_ct[1] = fetch_opp_table(cpu2_dev, &cpu2_freq_table);
if (table_ct[1] <= 0)
goto opp_err_exit;
cc_cdev = kzalloc(sizeof(*cc_cdev), GFP_KERNEL);
if (!cc_cdev)
goto opp_err_exit;
cpu_freq_table[0] = cpu1_freq_table;
cpu_freq_table[1] = cpu2_freq_table;
ret = build_unified_table(cc_cdev, cpu_freq_table, table_ct, cpus,
CPU_MAP_CT);
if (ret < 0)
goto opp_err_exit;
kfree(cpu1_freq_table);
kfree(cpu2_freq_table);
return cc_cdev;
opp_err_exit:
kfree(cpu1_freq_table);
kfree(cpu2_freq_table);
if (cc_cdev) {
kfree(cc_cdev->map_freq);
kfree(cc_cdev);
}
return ERR_PTR(-EPROBE_DEFER);
}
static int cc_init(struct device_node *np, int *cpus)
{
struct cc_limits_data *cc_cdev;
int idx = 0, ret = 0;
struct cpufreq_policy *policy;
mutex_lock(&cc_list_lock);
list_for_each_entry(cc_cdev, &cc_cdev_list, node) {
if ((cpus[0] == cc_cdev->cpu_map[0] &&
cpus[1] == cc_cdev->cpu_map[1]) ||
(cpus[0] == cc_cdev->cpu_map[1] &&
cpus[1] == cc_cdev->cpu_map[0])) {
mutex_unlock(&cc_list_lock);
return 0;
}
}
policy = cpufreq_cpu_get(cpus[0]);
if (!policy) {
pr_err("No policy for CPU:%d. Defer.\n", cpus[0]);
mutex_unlock(&cc_list_lock);
return -EPROBE_DEFER;
}
if (cpumask_test_cpu(cpus[1], policy->related_cpus)) {
pr_err("CPUs:%d %d are related.\n", cpus[0], cpus[1]);
cpufreq_cpu_put(policy);
mutex_unlock(&cc_list_lock);
return -EINVAL;
}
cpufreq_cpu_put(policy);
cc_cdev = opp_init(cpus);
if (IS_ERR(cc_cdev)) {
ret = PTR_ERR(cc_cdev);
mutex_unlock(&cc_list_lock);
return ret;
}
for (idx = 0; idx < CPU_MAP_CT; idx++) {
policy = cpufreq_cpu_get(cc_cdev->cpu_map[idx]);
if (!policy) {
pr_err("No policy for CPU:%d\n", cc_cdev->cpu_map[idx]);
ret = -ENODEV;
goto cc_err_exit;
}
ret = freq_qos_add_request(&policy->constraints,
&cc_cdev->cc_qos_req[idx], FREQ_QOS_MAX,
cc_cdev->map_freq[0].frequency[idx]);
cpufreq_cpu_put(policy);
if (ret < 0) {
pr_err("CPU%d Failed to add freq constraint (%d)\n",
cc_cdev->cpu_map[idx], ret);
goto cc_err_exit;
}
}
snprintf(cc_cdev->cdev_name, THERMAL_NAME_LENGTH,
"thermal-cluster-%d-%d", cpus[0], cpus[1]);
cc_cdev->cdev = thermal_of_cooling_device_register(
np, cc_cdev->cdev_name, cc_cdev,
&cc_cooling_ops);
list_add(&cc_cdev->node, &cc_cdev_list);
mutex_unlock(&cc_list_lock);
return 0;
cc_err_exit:
mutex_unlock(&cc_list_lock);
for (idx = 0; idx < CPU_MAP_CT; idx++)
freq_qos_remove_request(&cc_cdev->cc_qos_req[idx]);
kfree(cc_cdev->map_freq);
kfree(cc_cdev);
return ret;
}
static int cc_cooling_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *dev_phandle, *subsys_np = NULL;
struct device *cpu_dev;
int ret = 0, idx = 0, cpu;
u32 cpu_map[CPU_MAP_CT];
for_each_available_child_of_node(np, subsys_np) {
for (idx = 0; idx < CPU_MAP_CT; idx++) {
dev_phandle = of_parse_phandle(subsys_np, "qcom,cpus",
idx);
for_each_possible_cpu(cpu) {
cpu_dev = get_cpu_device(cpu);
if (cpu_dev && cpu_dev->of_node ==
dev_phandle) {
cpu_map[idx] = cpu;
break;
}
}
}
ret = cc_init(subsys_np, cpu_map);
}
return ret;
}
static int cc_cooling_remove(struct platform_device *pdev)
{
struct cc_limits_data *cc_cdev, *cc_next;
int idx = 0;
mutex_lock(&cc_list_lock);
list_for_each_entry_safe(cc_cdev, cc_next, &cc_cdev_list, node) {
if (cc_cdev->cdev)
thermal_cooling_device_unregister(cc_cdev->cdev);
list_del(&cc_cdev->node);
for (idx = 0; idx < CPU_MAP_CT; idx++)
freq_qos_remove_request(&cc_cdev->cc_qos_req[idx]);
kfree(cc_cdev->map_freq);
kfree(cc_cdev);
}
mutex_unlock(&cc_list_lock);
return 0;
}
static const struct of_device_id cc_cooling_device_match[] = {
{.compatible = "qcom,cc-cooling-devices"},
{}
};
static struct platform_driver cc_cooling_driver = {
.probe = cc_cooling_probe,
.remove = cc_cooling_remove,
.driver = {
.name = CC_CDEV_DRIVER,
.of_match_table = cc_cooling_device_match,
},
};
module_platform_driver(cc_cooling_driver);
MODULE_DESCRIPTION("CPU Voltage cooling device driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,229 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/interconnect.h>
#define DDR_CDEV_NAME "ddr-cdev"
struct ddr_cdev {
uint32_t cur_state;
uint32_t max_state;
struct thermal_cooling_device *cdev;
struct icc_path *icc_path;
struct device *dev;
uint32_t *freq_table;
uint32_t freq_table_size;
};
/**
* ddr_set_cur_state - callback function to set the ddr state.
* @cdev: thermal cooling device pointer.
* @state: set this variable to the current cooling state.
*
* Callback for the thermal cooling device to change the
* DDR state.
*
* Return: 0 on success, an error code otherwise.
*/
static int ddr_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct ddr_cdev *ddr_cdev = cdev->devdata;
int ret = 0;
/* Request state should be less than max_level */
if (state > ddr_cdev->max_state)
return -EINVAL;
/* Check if the old cooling action is same as new cooling action */
if (ddr_cdev->cur_state == state)
return 0;
ret = icc_set_bw(ddr_cdev->icc_path, 0, ddr_cdev->freq_table[state]);
if (ret < 0) {
dev_err(ddr_cdev->dev, "Error placing DDR freq%u. err:%d\n",
ddr_cdev->freq_table[state], ret);
return ret;
}
ddr_cdev->cur_state = state;
return ret;
}
/**
* ddr_get_cur_state - callback function to get the current cooling
* state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the current cooling state.
*
* Callback for the thermal cooling device to return the
* current DDR state request.
*
* Return: 0 on success, an error code otherwise.
*/
static int ddr_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct ddr_cdev *ddr_cdev = cdev->devdata;
*state = ddr_cdev->cur_state;
return 0;
}
/**
* ddr_get_max_state - callback function to get the max cooling state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the max cooling state.
*
* Callback for the thermal cooling device to return the DDR
* max cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int ddr_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct ddr_cdev *ddr_cdev = cdev->devdata;
*state = ddr_cdev->max_state;
return 0;
}
static struct thermal_cooling_device_ops ddr_cdev_ops = {
.get_max_state = ddr_get_max_state,
.get_cur_state = ddr_get_cur_state,
.set_cur_state = ddr_set_cur_state,
};
static int ddr_cdev_probe(struct platform_device *pdev)
{
int ret = 0, opp_ct = 0, bus_width = 1, idx = 0;
struct ddr_cdev *ddr_cdev = NULL;
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
uint32_t *freq_table = NULL;
char cdev_name[THERMAL_NAME_LENGTH] = DDR_CDEV_NAME;
ddr_cdev = devm_kzalloc(dev, sizeof(*ddr_cdev), GFP_KERNEL);
if (!ddr_cdev)
return -ENOMEM;
ddr_cdev->icc_path = of_icc_get(dev, NULL);
if (IS_ERR(ddr_cdev->icc_path)) {
ret = PTR_ERR(ddr_cdev->icc_path);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Unable to register icc path: %d\n",
ret);
return ret;
}
if (!of_find_property(np, "qcom,freq-table", &opp_ct)) {
dev_err(dev, "No DDR frequency entries\n");
ret = -ENODEV;
goto err_exit;
}
if (opp_ct <= 0) {
dev_err(dev, "No DDR frequency\n");
ret = -ENODEV;
goto err_exit;
}
opp_ct = opp_ct / sizeof(*freq_table);
/* Add one more entry for 0 or no DDR BW vote */
opp_ct++;
freq_table = devm_kcalloc(dev, opp_ct, sizeof(*freq_table), GFP_KERNEL);
if (!freq_table) {
ret = -ENOMEM;
goto err_exit;
}
freq_table[0] = 0;
ret = of_property_read_u32_array(np, "qcom,freq-table",
&freq_table[1], opp_ct - 1);
if (ret < 0) {
dev_err(dev, "DDR frequency read error:%d\n", ret);
goto err_exit;
}
ddr_cdev->freq_table = freq_table;
ddr_cdev->freq_table_size = opp_ct;
ddr_cdev->cur_state = 0;
ddr_cdev->max_state = opp_ct - 1;
ddr_cdev->dev = dev;
ret = of_property_read_u32(np, "qcom,bus-width", &bus_width);
if (ret < 0) {
dev_err(dev, "DDR bus width read error:%d\n", ret);
goto err_exit;
}
for (idx = 0; idx < opp_ct; idx++)
ddr_cdev->freq_table[idx] *= bus_width;
ret = icc_set_bw(ddr_cdev->icc_path, 0, freq_table[0]);
if (ret < 0) {
dev_err(dev, "Error placing DDR freq request. err:%d\n",
ret);
goto err_exit;
}
ddr_cdev->cdev = thermal_of_cooling_device_register(np, cdev_name,
ddr_cdev, &ddr_cdev_ops);
if (IS_ERR(ddr_cdev->cdev)) {
ret = PTR_ERR(ddr_cdev->cdev);
dev_err(dev, "Cdev register failed for %s, ret:%d\n",
cdev_name, ret);
goto err_exit;
}
dev_dbg(dev, "Cooling device [%s] registered.\n", cdev_name);
dev_set_drvdata(dev, ddr_cdev);
return 0;
err_exit:
icc_put(ddr_cdev->icc_path);
return ret;
}
static int ddr_cdev_remove(struct platform_device *pdev)
{
struct ddr_cdev *ddr_cdev =
(struct ddr_cdev *)dev_get_drvdata(&pdev->dev);
if (ddr_cdev->cdev) {
thermal_cooling_device_unregister(ddr_cdev->cdev);
ddr_cdev->cdev = NULL;
}
if (ddr_cdev->icc_path) {
icc_set_bw(ddr_cdev->icc_path, 0, ddr_cdev->freq_table[0]);
icc_put(ddr_cdev->icc_path);
ddr_cdev->icc_path = NULL;
}
return 0;
}
static const struct of_device_id ddr_cdev_match[] = {
{ .compatible = "qcom,ddr-cooling-device", },
{},
};
static struct platform_driver ddr_cdev_driver = {
.probe = ddr_cdev_probe,
.remove = ddr_cdev_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = ddr_cdev_match,
},
};
module_platform_driver(ddr_cdev_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,220 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#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/of_address.h>
#include <linux/interrupt.h>
#include "thermal_zone_internal.h"
#define PE_SENS_DRIVER "policy-engine-sensor"
#define PE_INT_ENABLE_OFFSET 0x530
#define PE_STATUS_OFFSET 0x590
#define PE_INT_STATUS_OFFSET 0x620
#define PE_INT_STATUS1_OFFSET 0x630
#define PE_INTR_CFG 0x11000
#define PE_INTR_CLEAR 0x11111
#define PE_STS_CLEAR 0xFFFF
#define PE_READ_MITIGATION_IDX(val) ((val >> 16) & 0x1F)
struct pe_sensor_data {
struct device *dev;
struct thermal_zone_device *tz_dev;
int32_t high_thresh;
int32_t low_thresh;
int32_t irq_num;
void __iomem *regmap;
struct mutex mutex;
};
static int pe_sensor_get_trend(void *data, int trip, enum thermal_trend *trend)
{
struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;
struct thermal_zone_device *tz = pe_sens->tz_dev;
int value, last_value;
if (!tz)
return -EINVAL;
value = READ_ONCE(tz->temperature);
last_value = READ_ONCE(tz->last_temperature);
if (!value)
*trend = THERMAL_TREND_DROPPING;
else if (value > last_value)
*trend = THERMAL_TREND_RAISING;
else if (value < last_value)
*trend = THERMAL_TREND_DROPPING;
else
*trend = THERMAL_TREND_STABLE;
return 0;
}
static int fetch_mitigation_table_idx(struct pe_sensor_data *pe_sens, int *temp)
{
u32 data = 0;
data = readl_relaxed(pe_sens->regmap + PE_STATUS_OFFSET);
*temp = PE_READ_MITIGATION_IDX(data);
dev_dbg(pe_sens->dev, "PE data:%d index:%d\n", data, *temp);
return 0;
}
static int pe_sensor_read(void *data, int *temp)
{
struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;
return fetch_mitigation_table_idx(pe_sens, temp);
}
static int pe_sensor_set_trips(void *data, int low, int high)
{
struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;
mutex_lock(&pe_sens->mutex);
if (pe_sens->high_thresh == high &&
pe_sens->low_thresh == low)
goto unlock_exit;
pe_sens->high_thresh = high;
pe_sens->low_thresh = low;
dev_dbg(pe_sens->dev, "PE rail set trip. high:%d low:%d\n",
high, low);
unlock_exit:
mutex_unlock(&pe_sens->mutex);
return 0;
}
static struct thermal_zone_of_device_ops pe_sensor_ops = {
.get_temp = pe_sensor_read,
.set_trips = pe_sensor_set_trips,
.get_trend = pe_sensor_get_trend,
};
static irqreturn_t pe_handle_irq(int irq, void *data)
{
struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;
int val = 0, ret = 0;
writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET);
writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_OFFSET);
ret = fetch_mitigation_table_idx(pe_sens, &val);
if (ret)
return IRQ_HANDLED;
mutex_lock(&pe_sens->mutex);
dev_dbg(pe_sens->dev, "Policy Engine interrupt fired value:%d\n", val);
if (pe_sens->tz_dev && (val >= pe_sens->high_thresh ||
val <= pe_sens->low_thresh)) {
mutex_unlock(&pe_sens->mutex);
thermal_zone_device_update(pe_sens->tz_dev,
THERMAL_TRIP_VIOLATED);
} else
mutex_unlock(&pe_sens->mutex);
return IRQ_HANDLED;
}
static int pe_sens_device_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
struct pe_sensor_data *pe_sens;
struct resource *res;
pe_sens = devm_kzalloc(dev, sizeof(*pe_sens), GFP_KERNEL);
if (!pe_sens)
return -ENOMEM;
pe_sens->dev = dev;
pe_sens->high_thresh = INT_MAX;
pe_sens->low_thresh = INT_MIN;
mutex_init(&pe_sens->mutex);
dev_set_drvdata(dev, pe_sens);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Couldn't get MEM resource\n");
return -EINVAL;
}
dev_dbg(dev, "pe@0x%x size:%d\n", res->start,
resource_size(res));
pe_sens->regmap = devm_ioremap_resource(dev, res);
if (!pe_sens->regmap) {
dev_err(dev, "Couldn't get regmap\n");
return -EINVAL;
}
pe_sens->irq_num = platform_get_irq(pdev, 0);
if (pe_sens->irq_num < 0) {
dev_err(dev, "Couldn't get irq number\n");
return pe_sens->irq_num;
}
pe_sens->tz_dev = devm_thermal_zone_of_sensor_register(
dev, 0, pe_sens, &pe_sensor_ops);
if (IS_ERR_OR_NULL(pe_sens->tz_dev)) {
ret = PTR_ERR(pe_sens->tz_dev);
if (ret != -ENODEV)
dev_err(dev, "sensor register failed. ret:%d\n", ret);
pe_sens->tz_dev = NULL;
return ret;
}
qti_update_tz_ops(pe_sens->tz_dev, true);
writel_relaxed(PE_INTR_CFG, pe_sens->regmap + PE_INT_ENABLE_OFFSET);
writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET);
writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_OFFSET);
ret = devm_request_threaded_irq(dev, pe_sens->irq_num, NULL,
pe_handle_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
dev_name(dev), pe_sens);
if (ret) {
dev_err(dev, "Couldn't get irq registered\n");
thermal_zone_of_sensor_unregister(pe_sens->dev,
pe_sens->tz_dev);
return ret;
}
enable_irq_wake(pe_sens->irq_num);
dev_dbg(dev, "PE sensor register success\n");
return 0;
}
static int pe_sens_device_remove(struct platform_device *pdev)
{
struct pe_sensor_data *pe_sens =
(struct pe_sensor_data *)dev_get_drvdata(&pdev->dev);
thermal_zone_of_sensor_unregister(pe_sens->dev, pe_sens->tz_dev);
qti_update_tz_ops(pe_sens->tz_dev, false);
return 0;
}
static const struct of_device_id pe_sens_device_match[] = {
{.compatible = "qcom,policy-engine"},
{}
};
static struct platform_driver pe_sens_device_driver = {
.probe = pe_sens_device_probe,
.remove = pe_sens_device_remove,
.driver = {
.name = PE_SENS_DRIVER,
.of_match_table = pe_sens_device_match,
},
};
module_platform_driver(pe_sens_device_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,622 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#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 "thermal_mitigation_device_service_v01.h"
#define QMI_CDEV_DRIVER "qmi-cooling-device"
#define QMI_TMD_RESP_TOUT msecs_to_jiffies(100)
struct qmi_cooling_device {
struct device_node *np;
char cdev_name[QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01];
char qmi_name[QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01];
bool connection_active;
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;
uint32_t inst_id;
struct list_head tmd_cdev_list;
struct work_struct svc_arrive_work;
};
static struct qmi_tmd_instance *tmd_instances;
static int tmd_inst_cnt;
static char device_clients[][QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01] = {
{"pa"},
{"pa_fr1"},
{"cx_vdd_limit"},
{"modem"},
{"modem_current"},
{"modem_skin"},
{"modem_bw"},
{"modem_bw_backoff"},
{"vbatt_low"},
{"charge_state"},
{"mmw0"},
{"mmw1"},
{"mmw2"},
{"mmw3"},
{"mmw_skin0"},
{"mmw_skin1"},
{"mmw_skin2"},
{"mmw_skin3"},
{"wlan"},
{"wlan_bw"},
{"mmw_skin0_dsc"},
{"mmw_skin1_dsc"},
{"mmw_skin2_dsc"},
{"mmw_skin3_dsc"},
{"modem_skin_lte_dsc"},
{"modem_skin_nr_dsc"},
{"pa_dsc"},
{"pa_fr1_dsc"},
{"cdsp_sw"},
{"cdsp_hw"},
{"cpuv_restriction_cold"},
{"cpr_cold"},
{"modem_lte_dsc"},
{"modem_nr_dsc"},
{"modem_nr_scg_dsc"},
{"sdr0_lte_dsc"},
{"sdr1_lte_dsc"},
{"sdr0_nr_dsc"},
{"sdr1_nr_dsc"},
{"sdr0_nr_scg_dsc"},
{"sdr1_nr_scg_dsc"},
{"pa_lte_sdr0_dsc"},
{"pa_lte_sdr1_dsc"},
{"pa_nr_sdr0_dsc"},
{"pa_nr_sdr1_dsc"},
{"pa_nr_sdr0_scg_dsc"},
{"pa_nr_sdr1_scg_dsc"},
{"mmw0_dsc"},
{"mmw1_dsc"},
{"mmw2_dsc"},
{"mmw3_dsc"},
{"mmw_ific_dsc"},
{"modem_lte_sub1_dsc"},
{"modem_nr_sub1_dsc"},
{"modem_nr_scg_sub1_dsc"},
{"sdr0_lte_sub1_dsc"},
{"sdr1_lte_sub1_dsc"},
{"sdr0_nr_sub1_dsc"},
{"sdr1_nr_sub1_dsc"},
{"pa_lte_sdr0_sub1_dsc"},
{"pa_lte_sdr1_sub1_dsc"},
{"pa_nr_sdr0_sub1_dsc"},
{"pa_nr_sdr1_sub1_dsc"},
{"pa_nr_sdr0_scg_sub1_dsc"},
{"pa_nr_sdr1_scg_sub1_dsc"},
{"mmw0_sub1_dsc"},
{"mmw1_sub1_dsc"},
{"mmw2_sub1_dsc"},
{"mmw3_sub1_dsc"},
{"mmw_ific_sub1_dsc"},
};
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;
*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 qmi_tmd_instance *tmd = qmi_cdev->tmd;
struct qmi_txn txn;
memset(&req, 0, sizeof(req));
memset(&tmd_resp, 0, sizeof(tmd_resp));
strscpy(req.mitigation_dev_id.mitigation_dev_id, qmi_cdev->qmi_name,
QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01);
req.mitigation_level = state;
mutex_lock(&tmd->mutex);
ret = qmi_txn_init(&tmd->handle, &txn,
tmd_set_mitigation_level_resp_msg_v01_ei, &tmd_resp);
if (ret < 0) {
pr_err("qmi set state:%d txn init failed for %s ret:%d\n",
state, qmi_cdev->cdev_name, ret);
goto qmi_send_exit;
}
ret = qmi_send_request(&tmd->handle, NULL, &txn,
QMI_TMD_SET_MITIGATION_LEVEL_REQ_V01,
TMD_SET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN,
tmd_set_mitigation_level_req_msg_v01_ei, &req);
if (ret < 0) {
pr_err("qmi set state:%d txn send failed for %s ret:%d\n",
state, qmi_cdev->cdev_name, ret);
qmi_txn_cancel(&txn);
goto qmi_send_exit;
}
ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TOUT);
if (ret < 0) {
pr_err("qmi set state:%d txn wait 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;
}
ret = 0;
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_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct qmi_cooling_device *qmi_cdev = cdev->devdata;
int ret = 0;
if (!qmi_cdev)
return -EINVAL;
if (state > qmi_cdev->max_level)
return -EINVAL;
if (qmi_cdev->mtgn_state == state)
return 0;
/* 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 0;
}
/* 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 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,
};
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;
int ret = 0, i;
struct qmi_txn txn;
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;
mutex_lock(&tmd->mutex);
ret = qmi_txn_init(&tmd->handle, &txn,
tmd_get_mitigation_device_list_resp_msg_v01_ei, tmd_resp);
if (ret < 0) {
pr_err("Transaction Init error for inst_id:0x%x ret:%d\n",
tmd->inst_id, ret);
goto reg_exit;
}
ret = qmi_send_request(&tmd->handle, NULL, &txn,
QMI_TMD_GET_MITIGATION_DEVICE_LIST_REQ_V01,
TMD_GET_MITIGATION_DEVICE_LIST_REQ_MSG_V01_MAX_MSG_LEN,
tmd_get_mitigation_device_list_req_msg_v01_ei,
&req);
if (ret < 0) {
qmi_txn_cancel(&txn);
goto reg_exit;
}
ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TOUT);
if (ret < 0) {
pr_err("Transaction wait error 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;
}
}
for (i = 0; tmd_resp->mitigation_device_list_ext01_valid &&
i < tmd_resp->mitigation_device_list_ext01_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_ext01[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_svc_arrive(struct work_struct *work)
{
struct qmi_tmd_instance *tmd = container_of(work,
struct qmi_tmd_instance,
svc_arrive_work);
verify_devices_and_register(tmd);
}
static void thermal_qmi_net_reset(struct qmi_handle *qmi)
{
struct qmi_tmd_instance *tmd = container_of(qmi,
struct qmi_tmd_instance,
handle);
struct qmi_cooling_device *qmi_cdev = NULL;
list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list,
qmi_node) {
if (qmi_cdev->connection_active)
qmi_tmd_send_state_request(qmi_cdev,
qmi_cdev->mtgn_state);
}
}
static void thermal_qmi_del_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct qmi_tmd_instance *tmd = container_of(qmi,
struct qmi_tmd_instance,
handle);
struct qmi_cooling_device *qmi_cdev = NULL;
list_for_each_entry(qmi_cdev, &tmd->tmd_cdev_list, qmi_node)
qmi_cdev->connection_active = false;
}
static int thermal_qmi_new_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct qmi_tmd_instance *tmd = container_of(qmi,
struct qmi_tmd_instance,
handle);
struct sockaddr_qrtr sq = {AF_QIPCRTR, service->node, service->port};
mutex_lock(&tmd->mutex);
kernel_connect(qmi->sock, (struct sockaddr *)&sq, sizeof(sq), 0);
mutex_unlock(&tmd->mutex);
queue_work(system_highpri_wq, &tmd->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_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) {
qmi_cdev->connection_active = false;
if (qmi_cdev->cdev)
thermal_cooling_device_unregister(
qmi_cdev->cdev);
list_del(&qmi_cdev->qmi_node);
}
qmi_handle_release(&tmd[idx].handle);
mutex_unlock(&tmd[idx].mutex);
}
}
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 = NULL, *cdev_np = NULL;
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) {
of_node_put(subsys_np);
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);
goto data_subsys_error;
}
tmd[idx].dev = dev;
mutex_init(&tmd[idx].mutex);
INIT_LIST_HEAD(&tmd[idx].tmd_cdev_list);
INIT_WORK(&tmd[idx].svc_arrive_work, qmi_tmd_svc_arrive);
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;
goto data_error;
}
strscpy(qmi_cdev->cdev_name, cdev_np->name,
QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01);
if (!of_property_read_string(cdev_np,
"qcom,qmi-dev-name",
&qmi_name)) {
strscpy(qmi_cdev->qmi_name, qmi_name,
QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01);
} 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],
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->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++;
}
of_node_put(np);
tmd_instances = tmd;
tmd_inst_cnt = subsys_cnt;
return 0;
data_error:
of_node_put(cdev_np);
data_subsys_error:
of_node_put(subsys_np);
of_node_put(np);
return ret;
}
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;
}
for (; idx < tmd_inst_cnt; idx++) {
struct qmi_tmd_instance *tmd = &tmd_instances[idx];
if (list_empty(&tmd->tmd_cdev_list))
continue;
ret = qmi_handle_init(&tmd->handle,
TMD_GET_MITIGATION_DEVICE_LIST_RESP_MSG_V01_MAX_MSG_LEN,
&thermal_qmi_event_ops, NULL);
if (ret < 0) {
dev_err(dev, "QMI[0x%x] handle init failed. err:%d\n",
tmd->inst_id, ret);
tmd_inst_cnt = idx;
goto probe_err;
}
ret = qmi_add_lookup(&tmd->handle, TMD_SERVICE_ID_V01,
TMD_SERVICE_VERS_V01,
tmd->inst_id);
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,
.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");
MODULE_DESCRIPTION("QTI QMI cooling device driver");

View File

@ -0,0 +1,167 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __QMI_SENSORS_H__
#define __QMI_SENSORS_H__
#define QMI_CLIENT_NAME_LENGTH 40
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_MMW_PA1,
QMI_TS_MMW_PA2,
QMI_TS_MMW_PA3,
QMI_TS_SDR_MMW,
QMI_TS_QTM_THERM,
QMI_TS_BCL_WARN,
QMI_TS_SDR0_PA0,
QMI_TS_SDR0_PA1,
QMI_TS_SDR0_PA2,
QMI_TS_SDR0_PA3,
QMI_TS_SDR0_PA4,
QMI_TS_SDR0_PA5,
QMI_TS_SDR0,
QMI_TS_SDR1_PA0,
QMI_TS_SDR1_PA1,
QMI_TS_SDR1_PA2,
QMI_TS_SDR1_PA3,
QMI_TS_SDR1_PA4,
QMI_TS_SDR1_PA5,
QMI_TS_SDR1,
QMI_TS_MMW0,
QMI_TS_MMW1,
QMI_TS_MMW2,
QMI_TS_MMW3,
QMI_TS_MMW_IFIC0,
QMI_TS_SUB1_MODEM_CFG,
QMI_TS_SUB1_LTE_CC,
QMI_TS_SUB1_MCG_FR1_CC,
QMI_TS_SUB1_MCG_FR2_CC,
QMI_TS_SUB1_SCG_FR1_CC,
QMI_TS_SUB1_SCG_FR2_CC,
QMI_TS_SUB2_MODEM_CFG,
QMI_TS_SUB2_LTE_CC,
QMI_TS_SUB2_MCG_FR1_CC,
QMI_TS_SUB2_MCG_FR2_CC,
QMI_TS_SUB2_SCG_FR1_CC,
QMI_TS_SUB2_SCG_FR2_CC,
QMI_TS_NSP_ISENSE_TRIM,
QMI_TS_EPM0,
QMI_TS_EPM1,
QMI_TS_EPM2,
QMI_TS_EPM3,
QMI_TS_EPM4,
QMI_TS_EPM5,
QMI_TS_EPM6,
QMI_TS_EPM7,
QMI_TS_SDR0_PA,
QMI_TS_SDR1_PA,
QMI_TS_MAX_NR
};
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"},
{"mmw_pa1"},
{"mmw_pa2"},
{"mmw_pa3"},
{"sdr_mmw_therm"},
{"qtm_therm"},
{"modem_bcl_warn"},
{"sdr0_pa0"},
{"sdr0_pa1"},
{"sdr0_pa2"},
{"sdr0_pa3"},
{"sdr0_pa4"},
{"sdr0_pa5"},
{"sdr0"},
{"sdr1_pa0"},
{"sdr1_pa1"},
{"sdr1_pa2"},
{"sdr1_pa3"},
{"sdr1_pa4"},
{"sdr1_pa5"},
{"sdr1"},
{"mmw0"},
{"mmw1"},
{"mmw2"},
{"mmw3"},
{"mmw_ific0"},
{"sub1_modem_cfg"},
{"sub1_lte_cc"},
{"sub1_mcg_fr1_cc"},
{"sub1_mcg_fr2_cc"},
{"sub1_scg_fr1_cc"},
{"sub1_scg_fr2_cc"},
{"sub2_modem_cfg"},
{"sub2_lte_cc"},
{"sub2_mcg_fr1_cc"},
{"sub2_mcg_fr2_cc"},
{"sub2_scg_fr1_cc"},
{"sub2_scg_fr2_cc"},
{"isense_trim"},
{"epm0"},
{"epm1"},
{"epm2"},
{"epm3"},
{"epm4"},
{"epm5"},
{"epm6"},
{"epm7"},
{"sdr0_pa"},
{"sdr1_pa"},
};
#endif /* __QMI_SENSORS_H__ */

View File

@ -0,0 +1,657 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/soc/qcom/qmi.h>
#include <linux/suspend.h>
#include <linux/thermal.h>
#include "thermal_sensor_service_v02.h"
#include "qmi_sensors.h"
#include "thermal_zone_internal.h"
#define QMI_SENS_DRIVER "qmi-therm-sensors-v2"
#define QMI_TS_RESP_TOUT msecs_to_jiffies(100)
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 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);
thermal_zone_device_update(qmi_sens->tz_dev, THERMAL_TRIP_VIOLATED);
};
static void qmi_ts_update_temperature(struct qmi_ts_instance *ts,
const struct ts_temp_report_ind_msg_v02 *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_V02)))
continue;
qmi_sens->last_reading = ind_msg->temp;
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->last_reading >= qmi_sens->high_thresh ||
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_v02(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
struct qmi_txn *txn, const void *decoded)
{
const struct ts_temp_report_ind_msg_v02 *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_V02) ||
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.\n");
}
static int qmi_ts_send_request(struct qmi_ts_instance *ts, int msg_id,
size_t len, struct qmi_elem_info *req_ei,
struct qmi_elem_info *resp_ei,
void *req, void *resp,
struct qmi_response_type_v01 *resp_data)
{
int ret = 0;
struct qmi_txn txn;
mutex_lock(&ts->mutex);
ret = qmi_txn_init(&ts->handle, &txn, resp_ei, resp);
if (ret < 0) {
pr_err("qmi txn init failed for msg id:0x%x ret:%d\n",
msg_id, ret);
goto qmi_ts_send_exit;
}
ret = qmi_send_request(&ts->handle, NULL, &txn, msg_id, len,
req_ei, req);
if (ret < 0) {
pr_err("qmi txn send failed for msg id:0x%x ret:%d\n",
msg_id, ret);
qmi_txn_cancel(&txn);
goto qmi_ts_send_exit;
}
ret = qmi_txn_wait(&txn, QMI_TS_RESP_TOUT);
if (ret < 0) {
pr_err("qmi txn wait failed for msg id:0x%x ret:%d\n",
msg_id, ret);
goto qmi_ts_send_exit;
}
if (resp_data->result != QMI_RESULT_SUCCESS_V01) {
ret = resp_data->result;
pr_err("qmi NOT success for msg id:0x%x ret:%d\n",
msg_id, ret);
goto qmi_ts_send_exit;
}
ret = 0;
qmi_ts_send_exit:
mutex_unlock(&ts->mutex);
return ret;
}
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_v02 resp;
struct ts_register_notification_temp_req_msg_v02 req;
struct qmi_ts_instance *ts = qmi_sens->ts;
memset(&req, 0, sizeof(req));
memset(&resp, 0, sizeof(resp));
strscpy(req.sensor_id.sensor_id, qmi_sens->qmi_name,
QMI_TS_SENSOR_ID_LENGTH_MAX_V02);
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 = qmi_sens->high_thresh;
req.temp_threshold_low_valid =
qmi_sens->low_thresh != (-INT_MAX);
req.temp_threshold_low = qmi_sens->low_thresh;
pr_debug("sensor:%s high_valid:%d high:%d low_valid:%d low:%d\n",
req.sensor_id.sensor_id, req.temp_threshold_high_valid,
req.temp_threshold_high, req.temp_threshold_low_valid,
req.temp_threshold_low);
}
ret = qmi_ts_send_request(ts, QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V02,
TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V02_MAX_MSG_LEN,
ts_register_notification_temp_req_msg_v02_ei,
ts_register_notification_temp_resp_msg_v02_ei,
&req, &resp, &resp.resp);
if (ret < 0)
pr_err("qmi ts txn failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
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 < 0)
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_V02,
.ei = ts_temp_report_ind_msg_v02_ei,
.decoded_size = sizeof(struct ts_temp_report_ind_msg_v02),
.fn = qmi_ts_ind_cb_v02
},
{}
};
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:%d\n",
qmi_sens->qmi_name, ret);
qmi_sens->tz_dev = NULL;
return ret;
}
qti_update_tz_ops(qmi_sens->tz_dev, true);
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_v02 list_req;
struct ts_get_sensor_list_resp_msg_v02 *list_resp;
struct ts_get_total_num_of_sensors_req_msg_v02 tot_req;
struct ts_get_total_num_of_sensors_resp_msg_v02 tot_resp;
int ret = 0, i, j, sensor_list_itr;
memset(&tot_resp, 0, sizeof(tot_resp));
memset(&tot_req, 0, sizeof(tot_req));
/* size of list_resp is very high, use heap memory rather than stack */
list_resp = kzalloc(sizeof(*list_resp), GFP_KERNEL);
if (!list_resp)
return -ENOMEM;
ret = qmi_ts_send_request(ts, QMI_TS_GET_TOTAL_NUM_OF_SENSORS_REQ_V02,
TS_GET_TOTAL_NUM_OF_SENSORS_REQ_MSG_V02_MAX_MSG_LEN,
ts_get_total_num_of_sensors_req_msg_v02_ei,
ts_get_total_num_of_sensors_resp_msg_v02_ei,
&tot_req, &tot_resp, &tot_resp.resp);
if (ret < 0) {
pr_err("qmi ts txn failed, inst_id:%d ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
sensor_list_itr = (tot_resp.total_num_sensors /
QMI_TS_SENSOR_LIST_MAX_V02 +
(tot_resp.total_num_sensors %
QMI_TS_SENSOR_LIST_MAX_V02 ? 1 : 0));
pr_debug("Total QMI sensors cnt:%d list iteration cnt:%d inst_id:%d\n",
tot_resp.total_num_sensors, sensor_list_itr, ts->inst_id);
for (i = 0; i < sensor_list_itr; i++) {
memset(&list_req, 0, sizeof(list_req));
memset(list_resp, 0, sizeof(*list_resp));
list_req.list_index = i;
ret = qmi_ts_send_request(ts, QMI_TS_GET_SENSOR_LIST_REQ_V02,
TS_GET_SENSOR_LIST_REQ_MSG_V02_MAX_MSG_LEN,
ts_get_sensor_list_req_msg_v02_ei,
ts_get_sensor_list_resp_msg_v02_ei,
&list_req, list_resp, &list_resp->resp);
if (ret < 0) {
pr_err("qmi ts txn failed, inst_id:%d itr:%d ret:%d\n",
ts->inst_id, i, ret);
goto reg_exit;
}
for (j = 0; j < list_resp->sensor_list_len; j++) {
struct qmi_sensor *qmi_sens = NULL;
pr_debug("QMI TS sensor[%d][%d]: %s\n", i, j,
list_resp->sensor_list[j].sensor_id);
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if ((strncasecmp(qmi_sens->qmi_name,
list_resp->sensor_list[j].sensor_id,
QMI_TS_SENSOR_ID_LENGTH_MAX_V02)))
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);
} else {
ret = qmi_ts_request(qmi_sens, false);
if (ret)
pr_err(
"sensor:%s trips:%d %d err%d\n",
qmi_sens->tz_dev->type,
qmi_sens->high_thresh,
qmi_sens->low_thresh,
ret);
}
break;
}
}
}
reg_exit:
kfree(list_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);
pr_debug("new QMI server is up, connecting..\n");
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);
qti_update_tz_ops(qmi_sens->tz_dev, false);
qmi_sens->tz_dev = NULL;
}
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 = NULL;
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);
strscpy(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_MAX;
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;
return 0;
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_V02_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);
ts_inst_cnt = idx;
ret = -EPROBE_DEFER;
goto probe_err;
}
ret = qmi_add_lookup(&ts->handle, TS_SERVICE_ID_V02,
TS_SERVICE_VERS_V02, ts->inst_id);
if (ret < 0) {
dev_err(dev, "QMI register failed for 0x%x, ret:%d\n",
ts->inst_id, ret);
ret = -EPROBE_DEFER;
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,
.of_match_table = qmi_sens_device_match,
},
};
module_platform_driver(qmi_sens_device_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#define USERSPACE_CDEV_DRIVER "userspace-cdev"
struct userspace_cdev {
struct device_node *np;
char cdev_name[THERMAL_NAME_LENGTH];
struct thermal_cooling_device *cdev;
unsigned int cur_level;
unsigned int max_level;
};
static struct userspace_cdev *cdev_instances;
static int inst_cnt;
static int userspace_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct userspace_cdev *usr_cdev = cdev->devdata;
if (!usr_cdev)
return -EINVAL;
*state = usr_cdev->max_level;
return 0;
}
static int userspace_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct userspace_cdev *usr_cdev = cdev->devdata;
if (!usr_cdev)
return -EINVAL;
*state = usr_cdev->cur_level;
return 0;
}
static int userspace_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct userspace_cdev *usr_cdev = cdev->devdata;
if (!usr_cdev)
return -EINVAL;
if (state > usr_cdev->max_level)
return -EINVAL;
if (usr_cdev->cur_level == state)
return 0;
usr_cdev->cur_level = state;
return 0;
}
static struct thermal_cooling_device_ops userspace_cdev_ops = {
.get_max_state = userspace_get_max_state,
.get_cur_state = userspace_get_cur_state,
.set_cur_state = userspace_set_cur_state,
};
static void userspace_cdev_cleanup(void)
{
int idx = 0;
struct userspace_cdev *usr_cdev = cdev_instances;
for (; idx < inst_cnt; idx++) {
if (usr_cdev[idx].cdev)
thermal_cooling_device_unregister(
usr_cdev[idx].cdev);
usr_cdev[idx].cdev = NULL;
}
inst_cnt = 0;
}
static int userspace_device_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0, idx = 0, subsys_cnt = 0;
struct device_node *np = dev->of_node, *subsys_np = NULL;
subsys_cnt = of_get_available_child_count(np);
if (!subsys_cnt) {
dev_err(dev, "No child node to process\n");
return -EFAULT;
}
cdev_instances = devm_kcalloc(dev, subsys_cnt, sizeof(*cdev_instances),
GFP_KERNEL);
if (!cdev_instances)
return -ENOMEM;
inst_cnt = subsys_cnt;
for_each_available_child_of_node(np, subsys_np) {
if (idx >= subsys_cnt) {
of_node_put(subsys_np);
break;
}
ret = of_property_read_u32(subsys_np, "qcom,max-level",
&cdev_instances[idx].max_level);
if (ret) {
dev_err(dev, "error reading qcom,max-level. ret:%d\n",
ret);
goto probe_error;
}
cdev_instances[idx].np = subsys_np;
strscpy(cdev_instances[idx].cdev_name, subsys_np->name,
THERMAL_NAME_LENGTH);
cdev_instances[idx].cdev = thermal_of_cooling_device_register(
subsys_np,
cdev_instances[idx].cdev_name,
&cdev_instances[idx],
&userspace_cdev_ops);
if (IS_ERR(cdev_instances[idx].cdev)) {
dev_err(dev, "Error registering cdev:%s err:%d\n",
cdev_instances[idx].cdev_name,
PTR_ERR(cdev_instances[idx].cdev));
cdev_instances[idx].cdev = NULL;
goto probe_error;
}
dev_info(dev, "cdev:%s lvl:%d registered\n",
cdev_instances[idx].cdev_name,
cdev_instances[idx].max_level);
idx++;
}
of_node_put(np);
return 0;
probe_error:
of_node_put(subsys_np);
of_node_put(np);
inst_cnt = idx;
userspace_cdev_cleanup();
return ret;
}
static int userspace_device_remove(struct platform_device *pdev)
{
userspace_cdev_cleanup();
return 0;
}
static const struct of_device_id userspace_device_match[] = {
{.compatible = "qcom,userspace-cooling-devices"},
{}
};
static struct platform_driver userspace_cdev_driver = {
.probe = userspace_device_probe,
.remove = userspace_device_remove,
.driver = {
.name = USERSPACE_CDEV_DRIVER,
.of_match_table = userspace_device_match,
},
};
static int __init userspace_cdev_init(void)
{
return platform_driver_register(&userspace_cdev_driver);
}
module_init(userspace_cdev_init);
static void __exit userspace_cdev_exit(void)
{
platform_driver_unregister(&userspace_cdev_driver);
}
module_exit(userspace_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Userspace cooling device driver");

View File

@ -0,0 +1,386 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/soc/qcom/qmi.h>
#include "thermal_mitigation_device_service_v01.h"
static struct qmi_elem_info tmd_mitigation_dev_id_type_v01_ei[] = {
{
.data_type = QMI_STRING,
.elem_len = QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01 + 1,
.elem_size = sizeof(char),
.array_type = NO_ARRAY,
.tlv_type = 0,
.offset = offsetof(
struct tmd_mitigation_dev_id_type_v01,
mitigation_dev_id),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
static struct qmi_elem_info tmd_mitigation_dev_list_type_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0,
.offset = offsetof(
struct tmd_mitigation_dev_list_type_v01,
mitigation_dev_id),
.ei_array = tmd_mitigation_dev_id_type_v01_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_mitigation_dev_list_type_v01,
max_mitigation_level),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_get_mitigation_device_list_req_msg_v01_ei[] = {
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_get_mitigation_device_list_resp_msg_v01_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_mitigation_device_list_resp_msg_v01,
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_mitigation_device_list_resp_msg_v01,
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_mitigation_device_list_resp_msg_v01,
mitigation_device_list_len),
},
{
.data_type = QMI_STRUCT,
.elem_len = QMI_TMD_MITIGATION_DEV_LIST_MAX_V01,
.elem_size = sizeof(
struct tmd_mitigation_dev_list_type_v01),
.array_type = VAR_LEN_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(
struct tmd_get_mitigation_device_list_resp_msg_v01,
mitigation_device_list),
.ei_array = tmd_mitigation_dev_list_type_v01_ei,
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct tmd_get_mitigation_device_list_resp_msg_v01,
mitigation_device_list_ext01_valid),
},
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct tmd_get_mitigation_device_list_resp_msg_v01,
mitigation_device_list_ext01_len),
},
{
.data_type = QMI_STRUCT,
.elem_len = QMI_TMD_MITIGATION_DEV_LIST_EXT01_MAX_V01,
.elem_size = sizeof(
struct tmd_mitigation_dev_list_type_v01),
.array_type = VAR_LEN_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct tmd_get_mitigation_device_list_resp_msg_v01,
mitigation_device_list_ext01),
.ei_array = tmd_mitigation_dev_list_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_set_mitigation_level_req_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(
struct tmd_set_mitigation_level_req_msg_v01,
mitigation_dev_id),
.ei_array = tmd_mitigation_dev_id_type_v01_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_mitigation_level_req_msg_v01,
mitigation_level),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_set_mitigation_level_resp_msg_v01_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_mitigation_level_resp_msg_v01,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_get_mitigation_level_req_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(
struct tmd_get_mitigation_level_req_msg_v01,
mitigation_device),
.ei_array = tmd_mitigation_dev_id_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_get_mitigation_level_resp_msg_v01_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_mitigation_level_resp_msg_v01,
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_mitigation_level_resp_msg_v01,
current_mitigation_level_valid),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(
struct tmd_get_mitigation_level_resp_msg_v01,
current_mitigation_level),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct tmd_get_mitigation_level_resp_msg_v01,
requested_mitigation_level_valid),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct tmd_get_mitigation_level_resp_msg_v01,
requested_mitigation_level),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info
tmd_register_notification_mitigation_level_req_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(
struct tmd_register_notification_mitigation_level_req_msg_v01,
mitigation_device),
.ei_array = tmd_mitigation_dev_id_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info
tmd_register_notification_mitigation_level_resp_msg_v01_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_register_notification_mitigation_level_resp_msg_v01,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info
tmd_deregister_notification_mitigation_level_req_msg_v01_ei[]
= {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct
tmd_deregister_notification_mitigation_level_req_msg_v01,
mitigation_device),
.ei_array = tmd_mitigation_dev_id_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info
tmd_deregister_notification_mitigation_level_resp_msg_v01_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_deregister_notification_mitigation_level_resp_msg_v01,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info tmd_mitigation_level_report_ind_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct tmd_mitigation_dev_id_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(
struct tmd_mitigation_level_report_ind_msg_v01,
mitigation_device),
.ei_array = tmd_mitigation_dev_id_type_v01_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_mitigation_level_report_ind_msg_v01,
current_mitigation_level),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};

View File

@ -0,0 +1,126 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef THERMAL_MITIGATION_DEVICE_SERVICE_V01_H
#define THERMAL_MITIGATION_DEVICE_SERVICE_V01_H
#define TMD_SERVICE_ID_V01 0x18
#define TMD_SERVICE_VERS_V01 0x01
#define QMI_TMD_GET_MITIGATION_DEVICE_LIST_RESP_V01 0x0020
#define QMI_TMD_GET_MITIGATION_LEVEL_REQ_V01 0x0022
#define QMI_TMD_GET_SUPPORTED_MSGS_REQ_V01 0x001E
#define QMI_TMD_SET_MITIGATION_LEVEL_REQ_V01 0x0021
#define QMI_TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_V01 0x0023
#define QMI_TMD_GET_SUPPORTED_MSGS_RESP_V01 0x001E
#define QMI_TMD_SET_MITIGATION_LEVEL_RESP_V01 0x0021
#define QMI_TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_V01 0x0024
#define QMI_TMD_MITIGATION_LEVEL_REPORT_IND_V01 0x0025
#define QMI_TMD_GET_MITIGATION_LEVEL_RESP_V01 0x0022
#define QMI_TMD_GET_SUPPORTED_FIELDS_REQ_V01 0x001F
#define QMI_TMD_GET_MITIGATION_DEVICE_LIST_REQ_V01 0x0020
#define QMI_TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_V01 0x0023
#define QMI_TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_V01 0x0024
#define QMI_TMD_GET_SUPPORTED_FIELDS_RESP_V01 0x001F
#define QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01 32
#define QMI_TMD_MITIGATION_DEV_LIST_MAX_V01 32
#define QMI_TMD_MITIGATION_DEV_LIST_EXT01_MAX_V01 64
struct tmd_mitigation_dev_id_type_v01 {
char mitigation_dev_id[QMI_TMD_MITIGATION_DEV_ID_LENGTH_MAX_V01 + 1];
};
struct tmd_mitigation_dev_list_type_v01 {
struct tmd_mitigation_dev_id_type_v01 mitigation_dev_id;
uint8_t max_mitigation_level;
};
struct tmd_get_mitigation_device_list_req_msg_v01 {
char placeholder;
};
#define TMD_GET_MITIGATION_DEVICE_LIST_REQ_MSG_V01_MAX_MSG_LEN 0
extern struct qmi_elem_info tmd_get_mitigation_device_list_req_msg_v01_ei[];
struct tmd_get_mitigation_device_list_resp_msg_v01 {
struct qmi_response_type_v01 resp;
uint8_t mitigation_device_list_valid;
uint32_t mitigation_device_list_len;
struct tmd_mitigation_dev_list_type_v01
mitigation_device_list[QMI_TMD_MITIGATION_DEV_LIST_MAX_V01];
uint8_t mitigation_device_list_ext01_valid;
uint32_t mitigation_device_list_ext01_len;
struct tmd_mitigation_dev_list_type_v01
mitigation_device_list_ext01[QMI_TMD_MITIGATION_DEV_LIST_EXT01_MAX_V01];
};
#define TMD_GET_MITIGATION_DEVICE_LIST_RESP_MSG_V01_MAX_MSG_LEN 3279
extern struct qmi_elem_info tmd_get_mitigation_device_list_resp_msg_v01_ei[];
struct tmd_set_mitigation_level_req_msg_v01 {
struct tmd_mitigation_dev_id_type_v01 mitigation_dev_id;
uint8_t mitigation_level;
};
#define TMD_SET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 40
extern struct qmi_elem_info tmd_set_mitigation_level_req_msg_v01_ei[];
struct tmd_set_mitigation_level_resp_msg_v01 {
struct qmi_response_type_v01 resp;
};
#define TMD_SET_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7
extern struct qmi_elem_info tmd_set_mitigation_level_resp_msg_v01_ei[];
struct tmd_get_mitigation_level_req_msg_v01 {
struct tmd_mitigation_dev_id_type_v01 mitigation_device;
};
#define TMD_GET_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 36
extern struct qmi_elem_info tmd_get_mitigation_level_req_msg_v01_ei[];
struct tmd_get_mitigation_level_resp_msg_v01 {
struct qmi_response_type_v01 resp;
uint8_t current_mitigation_level_valid;
uint8_t current_mitigation_level;
uint8_t requested_mitigation_level_valid;
uint8_t requested_mitigation_level;
};
#define TMD_GET_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 15
extern struct qmi_elem_info tmd_get_mitigation_level_resp_msg_v01_ei[];
struct tmd_register_notification_mitigation_level_req_msg_v01 {
struct tmd_mitigation_dev_id_type_v01 mitigation_device;
};
#define TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 36
extern struct qmi_elem_info
tmd_register_notification_mitigation_level_req_msg_v01_ei[];
struct tmd_register_notification_mitigation_level_resp_msg_v01 {
struct qmi_response_type_v01 resp;
};
#define TMD_REGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7
extern struct qmi_elem_info
tmd_register_notification_mitigation_level_resp_msg_v01_ei[];
struct tmd_deregister_notification_mitigation_level_req_msg_v01 {
struct tmd_mitigation_dev_id_type_v01 mitigation_device;
};
#define TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_REQ_MSG_V01_MAX_MSG_LEN 36
extern struct qmi_elem_info
tmd_deregister_notification_mitigation_level_req_msg_v01_ei[];
struct tmd_deregister_notification_mitigation_level_resp_msg_v01 {
struct qmi_response_type_v01 resp;
};
#define TMD_DEREGISTER_NOTIFICATION_MITIGATION_LEVEL_RESP_MSG_V01_MAX_MSG_LEN 7
extern struct qmi_elem_info
tmd_deregister_notification_mitigation_level_resp_msg_v01_ei[];
struct tmd_mitigation_level_report_ind_msg_v01 {
struct tmd_mitigation_dev_id_type_v01 mitigation_device;
uint8_t current_mitigation_level;
};
#define TMD_MITIGATION_LEVEL_REPORT_IND_MSG_V01_MAX_MSG_LEN 40
extern struct qmi_elem_info tmd_mitigation_level_report_ind_msg_v01_ei[];
#endif

View File

@ -0,0 +1,286 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/soc/qcom/qmi.h>
#include "thermal_sensor_service_v02.h"
static struct qmi_elem_info ts_sensor_type_v02_ei[] = {
{
.data_type = QMI_STRING,
.elem_len = QMI_TS_SENSOR_ID_LENGTH_MAX_V02 + 1,
.elem_size = sizeof(char),
.array_type = NO_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct ts_sensor_type_v02,
sensor_id),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_get_total_num_of_sensors_req_msg_v02_ei[] = {
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_get_total_num_of_sensors_resp_msg_v02_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 ts_get_total_num_of_sensors_resp_msg_v02,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(u32),
.array_type = NO_ARRAY,
.tlv_type = 0x03,
.offset = offsetof(struct ts_get_total_num_of_sensors_resp_msg_v02,
total_num_sensors),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_get_sensor_list_req_msg_v02_ei[] = {
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(u32),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct ts_get_sensor_list_req_msg_v02,
list_index),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_get_sensor_list_resp_msg_v02_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 ts_get_sensor_list_resp_msg_v02,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_get_sensor_list_resp_msg_v02,
sensor_list_valid),
},
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_get_sensor_list_resp_msg_v02,
sensor_list_len),
},
{
.data_type = QMI_STRUCT,
.elem_len = QMI_TS_SENSOR_LIST_MAX_V02,
.elem_size = sizeof(struct ts_sensor_type_v02),
.array_type = VAR_LEN_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_get_sensor_list_resp_msg_v02,
sensor_list),
.ei_array = ts_sensor_type_v02_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_register_notification_temp_req_msg_v02_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct ts_sensor_type_v02),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
sensor_id),
.ei_array = ts_sensor_type_v02_ei,
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
send_current_temp_report),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
temp_threshold_high_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(int),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
temp_threshold_high),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
temp_threshold_low_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(int),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
temp_threshold_low),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x12,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
seq_num_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(u32),
.array_type = NO_ARRAY,
.tlv_type = 0x12,
.offset = offsetof(struct ts_register_notification_temp_req_msg_v02,
seq_num),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_register_notification_temp_resp_msg_v02_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 ts_register_notification_temp_resp_msg_v02,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_temp_report_ind_msg_v02_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct ts_sensor_type_v02),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct ts_temp_report_ind_msg_v02,
sensor_id),
.ei_array = ts_sensor_type_v02_ei,
},
{
.data_type = QMI_SIGNED_4_BYTE_ENUM,
.elem_len = 1,
.elem_size = sizeof(enum ts_temp_report_type_enum_v02),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct ts_temp_report_ind_msg_v02,
report_type),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_temp_report_ind_msg_v02,
temp_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(int),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_temp_report_ind_msg_v02,
temp),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(struct ts_temp_report_ind_msg_v02,
seq_num_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(u32),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(struct ts_temp_report_ind_msg_v02,
seq_num),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};

View File

@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef THERMAL_SENSOR_SERVICE_V02_H
#define THERMAL_SENSOR_SERVICE_V02_H
#define TS_SERVICE_ID_V02 0x17
#define TS_SERVICE_VERS_V02 0x02
#define QMI_TS_GET_SUPPORTED_MSGS_RESP_V02 0x001E
#define QMI_TS_GET_SENSOR_LIST_RESP_V02 0x0031
#define QMI_TS_GET_SUPPORTED_MSGS_REQ_V02 0x001E
#define QMI_TS_GET_TOTAL_NUM_OF_SENSORS_RESP_V02 0x0030
#define QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V02 0x0032
#define QMI_TS_REGISTER_NOTIFICATION_TEMP_RESP_V02 0x0032
#define QMI_TS_GET_TOTAL_NUM_OF_SENSORS_REQ_V02 0x0030
#define QMI_TS_GET_SUPPORTED_FIELDS_RESP_V02 0x001F
#define QMI_TS_GET_SENSOR_LIST_REQ_V02 0x0031
#define QMI_TS_TEMP_REPORT_IND_V02 0x0033
#define QMI_TS_GET_SUPPORTED_FIELDS_REQ_V02 0x001F
#define QMI_TS_SENSOR_ID_LENGTH_MAX_V02 32
#define QMI_TS_SENSOR_LIST_MAX_V02 96
struct ts_sensor_type_v02 {
char sensor_id[QMI_TS_SENSOR_ID_LENGTH_MAX_V02 + 1];
};
struct ts_get_total_num_of_sensors_req_msg_v02 {
char placeholder;
};
#define TS_GET_TOTAL_NUM_OF_SENSORS_REQ_MSG_V02_MAX_MSG_LEN 0
extern struct qmi_elem_info ts_get_total_num_of_sensors_req_msg_v02_ei[];
struct ts_get_total_num_of_sensors_resp_msg_v02 {
struct qmi_response_type_v01 resp;
u32 total_num_sensors;
};
#define TS_GET_TOTAL_NUM_OF_SENSORS_RESP_MSG_V02_MAX_MSG_LEN 14
extern struct qmi_elem_info ts_get_total_num_of_sensors_resp_msg_v02_ei[];
struct ts_get_sensor_list_req_msg_v02 {
u32 list_index;
};
#define TS_GET_SENSOR_LIST_REQ_MSG_V02_MAX_MSG_LEN 7
extern struct qmi_elem_info ts_get_sensor_list_req_msg_v02_ei[];
struct ts_get_sensor_list_resp_msg_v02 {
struct qmi_response_type_v01 resp;
u8 sensor_list_valid;
u32 sensor_list_len;
struct ts_sensor_type_v02 sensor_list[QMI_TS_SENSOR_LIST_MAX_V02];
};
#define TS_GET_SENSOR_LIST_RESP_MSG_V02_MAX_MSG_LEN 3179
extern struct qmi_elem_info ts_get_sensor_list_resp_msg_v02_ei[];
struct ts_register_notification_temp_req_msg_v02 {
struct ts_sensor_type_v02 sensor_id;
u8 send_current_temp_report;
u8 temp_threshold_high_valid;
int temp_threshold_high;
u8 temp_threshold_low_valid;
int temp_threshold_low;
u8 seq_num_valid;
u32 seq_num;
};
#define TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V02_MAX_MSG_LEN 61
extern struct qmi_elem_info ts_register_notification_temp_req_msg_v02_ei[];
struct ts_register_notification_temp_resp_msg_v02 {
struct qmi_response_type_v01 resp;
};
#define TS_REGISTER_NOTIFICATION_TEMP_RESP_MSG_V02_MAX_MSG_LEN 7
extern struct qmi_elem_info ts_register_notification_temp_resp_msg_v02_ei[];
enum ts_temp_report_type_enum_v02 {
TS_TEMP_REPORT_TYPE_ENUM_MIN_VAL_V02 = INT_MIN,
QMI_TS_TEMP_REPORT_CURRENT_TEMP_V02 = 0,
QMI_TS_TEMP_REPORT_THRESHOLD_HIGH_V02 = 1,
QMI_TS_TEMP_REPORT_THRESHOLD_LOW_V02 = 2,
TS_TEMP_REPORT_TYPE_ENUM_MAX_VAL_V02 = INT_MAX,
};
struct ts_temp_report_ind_msg_v02 {
struct ts_sensor_type_v02 sensor_id;
enum ts_temp_report_type_enum_v02 report_type;
u8 temp_valid;
int temp;
u8 seq_num_valid;
u32 seq_num;
};
#define TS_TEMP_REPORT_IND_MSG_V02_MAX_MSG_LEN 57
extern struct qmi_elem_info ts_temp_report_ind_msg_v02_ei[];
#endif

View File

@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __QTI_THERMAL_ZONE_INTERNAL_H
#define __QTI_THERMAL_ZONE_INTERNAL_H
#include <linux/thermal.h>
#include "../thermal_core.h"
/* Generic helpers for thermal zone -> change_mode ops */
static inline int qti_tz_change_mode(struct thermal_zone_device *tz,
enum thermal_device_mode mode)
{
struct thermal_instance *instance;
tz->passive = 0;
tz->temperature = THERMAL_TEMP_INVALID;
tz->prev_low_trip = -INT_MAX;
tz->prev_high_trip = INT_MAX;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
instance->initialized = false;
if (mode == THERMAL_DEVICE_DISABLED) {
instance->target = THERMAL_NO_TARGET;
instance->cdev->updated = false;
thermal_cdev_update(instance->cdev);
}
}
return 0;
}
static inline int qti_update_tz_ops(struct thermal_zone_device *tz, bool enable)
{
if (!tz || !tz->ops)
return -EINVAL;
mutex_lock(&tz->lock);
if (enable) {
if (!tz->ops->change_mode)
tz->ops->change_mode = qti_tz_change_mode;
} else {
tz->ops->change_mode = NULL;
}
mutex_unlock(&tz->lock);
return 0;
}
#endif // __QTI_THERMAL_ZONE_INTERNAL_H

View File

@ -2,6 +2,7 @@
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
* Copyright (c) 2019, 2020, Linaro Ltd.
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/debugfs.h>
@ -20,6 +21,7 @@
#include <linux/thermal.h>
#include "../thermal_hwmon.h"
#include "tsens.h"
#include "thermal_zone_internal.h"
/**
* struct tsens_irq_data - IRQ status and temperature violations
@ -264,7 +266,7 @@ static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
enum tsens_irq_type irq_type, bool enable)
{
dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
TSENS_DBG_1(priv, "[%u] %s: %s -> %s\n", hw_id, __func__,
irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
enable ? "en" : "dis");
if (tsens_version(priv) > VER_1_X)
@ -348,7 +350,7 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
TSENS_DBG_1(priv, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
hw_id, __func__,
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
d->low_viol, d->up_viol, d->crit_viol,
@ -407,7 +409,7 @@ static irqreturn_t tsens_critical_irq_thread(int irq, void *data)
if (ret)
return ret;
if (wdog_count)
dev_dbg(priv->dev, "%s: watchdog count: %d\n",
TSENS_DBG_1(priv, "%s: watchdog count: %d\n",
__func__, wdog_count);
/* Fall through to handle critical interrupts if any */
@ -465,7 +467,7 @@ static irqreturn_t tsens_irq_thread(int irq, void *data)
for (i = 0; i < priv->num_sensors; i++) {
bool trigger = false;
const struct tsens_sensor *s = &priv->sensor[i];
struct tsens_sensor *s = &priv->sensor[i];
u32 hw_id = s->hw_id;
if (!s->tzd)
@ -510,6 +512,7 @@ static irqreturn_t tsens_irq_thread(int irq, void *data)
spin_unlock_irqrestore(&priv->ul_lock, flags);
if (trigger) {
s->cached_temp = temp;
dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
hw_id, __func__, temp);
thermal_zone_device_update(s->tzd,
@ -527,7 +530,9 @@ static irqreturn_t tsens_irq_thread(int irq, void *data)
*/
break;
}
s->cached_temp = INT_MIN;
}
TSENS_DBG_1(priv, "%s: irq[%d] exit", __func__, irq);
return IRQ_HANDLED;
}
@ -570,7 +575,7 @@ static int tsens_set_trips(void *_sensor, int low, int high)
spin_unlock_irqrestore(&priv->ul_lock, flags);
dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
TSENS_DBG_1(priv, "[%u] %s: (%d:%d)->(%d:%d)\n",
hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
return 0;
@ -603,6 +608,11 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
u32 valid;
int ret;
if (s->cached_temp != INT_MIN) {
*temp = s->cached_temp;
goto dump_and_exit;
}
/* VER_0 doesn't have VALID bit */
if (tsens_version(priv) == VER_0)
goto get_temp;
@ -622,6 +632,9 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
/* Valid bit is set, OK to read the temperature */
*temp = tsens_hw_to_mC(s, temp_idx);
dump_and_exit:
TSENS_DBG(priv, "Sensor_id: %d temp: %d", hw_id, *temp);
return 0;
}
@ -648,6 +661,8 @@ int get_temp_common(const struct tsens_sensor *s, int *temp)
*temp = code_to_degc(last_temp, s) * 1000;
TSENS_DBG(priv, "Sensor_id: %d temp: %d", hw_id, *temp);
return 0;
} while (time_before(jiffies, timeout));
@ -705,6 +720,7 @@ static void tsens_debug_init(struct platform_device *pdev)
{
struct tsens_priv *priv = platform_get_drvdata(pdev);
struct dentry *root, *file;
char tsens_name[32];
root = debugfs_lookup("tsens", NULL);
if (!root)
@ -720,6 +736,20 @@ static void tsens_debug_init(struct platform_device *pdev)
/* A directory for each instance of the TSENS IP */
priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
/* Enable TSENS IPC logging context */
snprintf(tsens_name, sizeof(tsens_name), "%s_0", dev_name(&pdev->dev));
priv->ipc_log = ipc_log_context_create(IPC_LOGPAGES, tsens_name, 0);
if (!priv->ipc_log)
dev_err(&pdev->dev, "%s: unable to create IPC Logging for %s\n",
__func__, tsens_name);
snprintf(tsens_name, sizeof(tsens_name), "%s_1", dev_name(&pdev->dev));
priv->ipc_log1 = ipc_log_context_create(IPC_LOGPAGES, tsens_name, 0);
if (!priv->ipc_log1)
dev_err(&pdev->dev, "%s: unable to create IPC Logging for %s\n",
__func__, tsens_name);
}
#else
static inline void tsens_debug_init(struct platform_device *pdev) {}
@ -1056,6 +1086,7 @@ static int tsens_register(struct tsens_priv *priv)
if (devm_thermal_add_hwmon_sysfs(tzd))
dev_warn(priv->dev,
"Failed to add hwmon sysfs attributes\n");
qti_update_tz_ops(tzd, true);
}
/* VER_0 require to set MIN and MAX THRESH
@ -1129,6 +1160,7 @@ static int tsens_probe(struct platform_device *pdev)
priv->sensor[i].hw_id = data->hw_ids[i];
else
priv->sensor[i].hw_id = i;
priv->sensor[i].cached_temp = INT_MIN;
}
priv->feat = data->feat;
priv->fields = data->fields;
@ -1159,12 +1191,16 @@ static int tsens_probe(struct platform_device *pdev)
static int tsens_remove(struct platform_device *pdev)
{
struct tsens_priv *priv = platform_get_drvdata(pdev);
int i;
debugfs_remove_recursive(priv->debug_root);
tsens_disable_irq(priv);
if (priv->ops->disable)
priv->ops->disable(priv);
for (i = 0; i < priv->num_sensors; i++)
if (priv->sensor[i].tzd)
qti_update_tz_ops(priv->sensor[i].tzd, false);
return 0;
}

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __QCOM_TSENS_H__
@ -21,6 +22,7 @@
#include <linux/thermal.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/ipc_logging.h>
struct tsens_priv;
@ -54,6 +56,7 @@ struct tsens_sensor {
unsigned int hw_id;
int slope;
u32 status;
int cached_temp;
};
/**
@ -145,6 +148,25 @@ struct tsens_ops {
[_name##_##14] = REG_FIELD(_offset, 30, 30), \
[_name##_##15] = REG_FIELD(_offset, 31, 31)
#define IPC_LOGPAGES 10
#define TSENS_DBG(dev, msg, args...) do { \
pr_debug("%s:" msg, __func__, args); \
if ((dev) && (dev)->ipc_log) { \
ipc_log_string((dev)->ipc_log, \
"%s: " msg " [%s]\n", \
__func__, args, current->comm); \
} \
} while (0)
#define TSENS_DBG_1(priv, msg, args...) do { \
dev_dbg((priv)->dev, "%s:" msg, __func__, args); \
if ((priv) && (priv)->ipc_log1) { \
ipc_log_string((priv)->ipc_log1, \
"%s: " msg " [%s]\n", \
__func__, args, current->comm); \
} \
} while (0)
/*
* reg_field IDs to use as an index into an array
* If you change the order of the entries, check the devm_regmap_field_alloc()
@ -551,6 +573,8 @@ struct tsens_context {
* @ops: pointer to list of callbacks supported by this device
* @debug_root: pointer to debugfs dentry for all tsens
* @debug: pointer to debugfs dentry for tsens controller
* @ipc_log: pointer for first ipc log context id
* @ipc_log1: pointer for second ipc log context id
* @sensor: list of sensors attached to this device
*/
struct tsens_priv {
@ -571,6 +595,8 @@ struct tsens_priv {
struct dentry *debug_root;
struct dentry *debug;
void *ipc_log;
void *ipc_log1;
struct tsens_sensor sensor[];
};