From 94c63777762ee9a40e48c8e1fa3abd529d44ce7f Mon Sep 17 00:00:00 2001 From: Ram Chandrasekar Date: Wed, 18 Mar 2020 13:59:36 -0700 Subject: [PATCH 1/2] drivers: thermal: cpu_voltage: Add CPU voltage cooling device support A single CPU cluster can have CPUs with different frequency plans. Mitigating individual CPU may or may not bring down the CPU cluster voltage depending on the operating level of the other CPUs. CPU voltage cooling device will provide support to apply CPU frequency mitigation on different CPUs in a cluster to achieve a reduction in cluster voltage. This is achieved by building a mitigation table mapping the frequency of different CPUs based on voltage. Change-Id: Ie09375c6c15a9f81e663fca96c5af87e0b6789c2 Signed-off-by: Ram Chandrasekar --- drivers/thermal/qcom/Kconfig | 9 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/cpu_voltage_cooling.c | 379 +++++++++++++++++++++ 3 files changed, 389 insertions(+) create mode 100644 drivers/thermal/qcom/cpu_voltage_cooling.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 025e9a60086f..abeddfad23ab 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -91,3 +91,12 @@ config QTI_THERMAL_LIMITS_DCVS tracking temperatures of the CPUs and taking thermal action in the hardware without s/w intervention. +config QTI_CPU_VOLTAGE_COOLING_DEVICE + tristate "QTI CPU VOLTAGE cooling devices" + depends on CPU_FREQ && THERMAL_OF && QTI_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. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 3e3487994c73..25c36312c2ed 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE) += cpu_isolate.o obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o +obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o diff --git a/drivers/thermal/qcom/cpu_voltage_cooling.c b/drivers/thermal/qcom/cpu_voltage_cooling.c new file mode 100644 index 000000000000..21048aa544d4 --- /dev/null +++ b/drivers/thermal/qcom/cpu_voltage_cooling.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. 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(-ENODEV); +} + +static int cc_init(struct device *dev, int *cpus) +{ + struct cc_limits_data *cc_cdev; + int idx = 0, ret = 0; + struct cpufreq_policy *policy; + struct device_node *np = dev->of_node; + + 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\n", cpus[0]); + mutex_unlock(&cc_list_lock); + return -ENODEV; + } + 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", cc_cdev->cpu_map[0], + cc_cdev->cpu_map[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(dev, 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, + }, +}; + +static void cc_cooling_exit(void) +{ + platform_driver_unregister(&cc_cooling_driver); +} + +static int cc_cooling_init(void) +{ + return platform_driver_register(&cc_cooling_driver); +} + +module_init(cc_cooling_init); +module_exit(cc_cooling_exit); +MODULE_DESCRIPTION("CPU Voltage cooling device driver"); +MODULE_LICENSE("GPL v2"); From b541a79e7dc4de1c7ef7084d64f3667b41d2ca9f Mon Sep 17 00:00:00 2001 From: Ram Chandrasekar Date: Mon, 23 Mar 2020 13:30:14 -0700 Subject: [PATCH 2/2] defconfig: Enable voltage cooling device for lahaina Enable voltage cooling device for lahaina. This will enable to build voltage cooling device using CPUs sharing the same rails. Change-Id: Ic4df305280006c60bfe3f0287138477a679237c1 Signed-off-by: Ram Chandrasekar --- arch/arm64/configs/vendor/lahaina_QGKI.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/vendor/lahaina_QGKI.config b/arch/arm64/configs/vendor/lahaina_QGKI.config index af9be218da7e..8eebaaaa33ef 100644 --- a/arch/arm64/configs/vendor/lahaina_QGKI.config +++ b/arch/arm64/configs/vendor/lahaina_QGKI.config @@ -116,6 +116,7 @@ CONFIG_QTI_QMI_COOLING_DEVICE=y CONFIG_QTI_ADC_TM=y CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE=y CONFIG_QTI_THERMAL_LIMITS_DCVS=y +CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE=y CONFIG_SPS=y # CONFIG_SPS_SUPPORT_BAMDMA is not set CONFIG_SPS_SUPPORT_NDP_BAM=y