diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a412e77fdbf8..ea9fbf657ff7 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -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. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index c4df3c9694ce..51714d52a030 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -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 diff --git a/drivers/thermal/qcom/bcl_pmic5.c b/drivers/thermal/qcom/bcl_pmic5.c index da2cf3478d48..2efe7dd7c1fa 100644 --- a/drivers/thermal/qcom/bcl_pmic5.c +++ b/drivers/thermal/qcom/bcl_pmic5.c @@ -22,6 +22,7 @@ #include #include #include +#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; diff --git a/drivers/thermal/qcom/bcl_soc.c b/drivers/thermal/qcom/bcl_soc.c new file mode 100644 index 000000000000..eeab5b29776e --- /dev/null +++ b/drivers/thermal/qcom/bcl_soc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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"); diff --git a/drivers/thermal/qcom/cpu_hotplug.c b/drivers/thermal/qcom/cpu_hotplug.c new file mode 100644 index 000000000000..3dbeea7903b4 --- /dev/null +++ b/drivers/thermal/qcom/cpu_hotplug.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/cpu_voltage_cooling.c b/drivers/thermal/qcom/cpu_voltage_cooling.c new file mode 100644 index 000000000000..2d5cbd3a7b4e --- /dev/null +++ b/drivers/thermal/qcom/cpu_voltage_cooling.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/ddr_cdev.c b/drivers/thermal/qcom/ddr_cdev.c new file mode 100644 index 000000000000..6a4a79f66465 --- /dev/null +++ b/drivers/thermal/qcom/ddr_cdev.c @@ -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 +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/policy_engine.c b/drivers/thermal/qcom/policy_engine.c new file mode 100644 index 000000000000..bb2b0f7fdbfa --- /dev/null +++ b/drivers/thermal/qcom/policy_engine.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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"); diff --git a/drivers/thermal/qcom/qmi_cooling.c b/drivers/thermal/qcom/qmi_cooling.c new file mode 100644 index 000000000000..2ab23347a753 --- /dev/null +++ b/drivers/thermal/qcom/qmi_cooling.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/qmi_sensors.h b/drivers/thermal/qcom/qmi_sensors.h new file mode 100644 index 000000000000..eed7a55e2c70 --- /dev/null +++ b/drivers/thermal/qcom/qmi_sensors.h @@ -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__ */ diff --git a/drivers/thermal/qcom/qmi_sensors_v2.c b/drivers/thermal/qcom/qmi_sensors_v2.c new file mode 100644 index 000000000000..e5918f721332 --- /dev/null +++ b/drivers/thermal/qcom/qmi_sensors_v2.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/qti_userspace_cdev.c b/drivers/thermal/qcom/qti_userspace_cdev.c new file mode 100644 index 000000000000..e1b583155ad6 --- /dev/null +++ b/drivers/thermal/qcom/qti_userspace_cdev.c @@ -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 +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/thermal_mitigation_device_service_v01.c b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.c new file mode 100644 index 000000000000..96fd8bac3ebb --- /dev/null +++ b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.c @@ -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 + +#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, + }, +}; + diff --git a/drivers/thermal/qcom/thermal_mitigation_device_service_v01.h b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.h new file mode 100644 index 000000000000..9e9a3dcc45cf --- /dev/null +++ b/drivers/thermal/qcom/thermal_mitigation_device_service_v01.h @@ -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 diff --git a/drivers/thermal/qcom/thermal_sensor_service_v02.c b/drivers/thermal/qcom/thermal_sensor_service_v02.c new file mode 100644 index 000000000000..2d341b2fe773 --- /dev/null +++ b/drivers/thermal/qcom/thermal_sensor_service_v02.c @@ -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 +#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, + }, +}; + diff --git a/drivers/thermal/qcom/thermal_sensor_service_v02.h b/drivers/thermal/qcom/thermal_sensor_service_v02.h new file mode 100644 index 000000000000..b5cf7b6fcc8a --- /dev/null +++ b/drivers/thermal/qcom/thermal_sensor_service_v02.h @@ -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 diff --git a/drivers/thermal/qcom/thermal_zone_internal.h b/drivers/thermal/qcom/thermal_zone_internal.h new file mode 100644 index 000000000000..a037c2a167b0 --- /dev/null +++ b/drivers/thermal/qcom/thermal_zone_internal.h @@ -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 +#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 diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index e49f58e83513..70cfad1ecfe5 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -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 @@ -20,6 +21,7 @@ #include #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; } diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index ba05c8233356..f47b80adf572 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -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 #include #include +#include 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[]; };