blob: 881cb97b1c5c621a7b8eff77d7fa01974605388e [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* cdev_uclamp.c Cooling device to place thermal uclamp vote.
*
* Copyright (c) 2024, Google LLC. All rights reserved.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cpufreq.h>
#include <linux/cpumask.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/thermal.h>
#include <linux/workqueue.h>
#include "thermal_core.h"
#include "../../soc/google/vh/include/sched.h"
struct thermal_uclamp_cdev {
unsigned int cpu;
unsigned int cur_state;
unsigned int max_state;
unsigned int related_cpu_cnt;
struct thermal_cooling_device *cdev;
struct em_perf_domain *em;
struct list_head cdev_list;
};
static LIST_HEAD(therm_uclamp_cdev_list);
static DEFINE_MUTEX(therm_cdev_list_lock);
static int thermal_uclamp_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
*state = uclamp_cdev->max_state;
return 0;
}
static int thermal_uclamp_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
*state = uclamp_cdev->cur_state;
return 0;
}
static int thermal_uclamp_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
int idx = 0;
if (state > uclamp_cdev->max_state)
return -EINVAL;
mutex_lock(&therm_cdev_list_lock);
if (state != uclamp_cdev->cur_state) {
idx = uclamp_cdev->max_state - state;
pr_debug("cdev:[%s] new state request:[%lu] frequeny:[%lu]\n",
cdev->type, state,
uclamp_cdev->em->table[idx].frequency);
uclamp_cdev->cur_state = state;
sched_thermal_freq_cap(uclamp_cdev->cpu,
uclamp_cdev->em->table[idx].frequency);
}
mutex_unlock(&therm_cdev_list_lock);
return 0;
}
static unsigned long thermal_uclamp_cpupower_to_state(struct thermal_uclamp_cdev *uclamp_cdev,
u32 power)
{
unsigned long idx = 0;
for (idx = 0; idx < uclamp_cdev->max_state; idx++) {
if (uclamp_cdev->em->table[idx].power >= power)
break;
}
return idx;
}
static unsigned long thermal_uclamp_cpufreq_to_state(struct thermal_uclamp_cdev *uclamp_cdev,
unsigned long freq)
{
unsigned long idx = 0;
for (idx = 0; idx < uclamp_cdev->max_state; idx++) {
if (uclamp_cdev->em->table[idx].frequency >= freq)
break;
}
return idx;
}
static int thermal_uclamp_state2power(struct thermal_cooling_device *cdev,
unsigned long state, u32 *power)
{
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
unsigned int idx = 0;
if (state > uclamp_cdev->max_state)
return -EINVAL;
idx = uclamp_cdev->max_state - state;
*power = uclamp_cdev->em->table[idx].power * uclamp_cdev->related_cpu_cnt;
return 0;
}
static int thermal_uclamp_get_requested_power(struct thermal_cooling_device *cdev,
u32 *power)
{
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
unsigned long freq = 0, state = 0;
unsigned int idx = 0;
freq = cpufreq_quick_get(uclamp_cdev->cpu);
state = thermal_uclamp_cpufreq_to_state(uclamp_cdev, freq);
idx = uclamp_cdev->max_state - state;
*power = uclamp_cdev->em->table[idx].power * uclamp_cdev->related_cpu_cnt;
return 0;
}
static int thermal_uclamp_power2state(struct thermal_cooling_device *cdev,
u32 power, unsigned long *state)
{
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
*state = thermal_uclamp_cpupower_to_state(uclamp_cdev, power);
return 0;
}
static struct thermal_cooling_device_ops thermal_uclamp_cdev_ops = {
.get_max_state = thermal_uclamp_get_max_state,
.get_cur_state = thermal_uclamp_get_cur_state,
.set_cur_state = thermal_uclamp_set_cur_state,
.get_requested_power = thermal_uclamp_get_requested_power,
.state2power = thermal_uclamp_state2power,
.power2state = thermal_uclamp_power2state,
};
ssize_t
state2power_table_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_cooling_device *cdev = to_cooling_device(dev);
struct thermal_uclamp_cdev *uclamp_cdev = cdev->devdata;
int i, count = 0;
u32 power;
if (!uclamp_cdev)
return -ENODEV;
for (i = 0; i <= uclamp_cdev->max_state; i++) {
thermal_uclamp_state2power(cdev, i, &power);
count += sysfs_emit_at(buf, count, "%u ", DIV_ROUND_UP(power, 1000));
}
count += sysfs_emit_at(buf, count, "\n");
return count;
}
static DEVICE_ATTR_RO(state2power_table);
static void thermal_uclamp_cleanup(void)
{
struct thermal_uclamp_cdev *uclamp_cdev = NULL, *n;
mutex_lock(&therm_cdev_list_lock);
list_for_each_entry_safe(uclamp_cdev, n, &therm_uclamp_cdev_list, cdev_list) {
list_del(&uclamp_cdev->cdev_list);
device_remove_file(&uclamp_cdev->cdev->device,
&dev_attr_state2power_table);
if (uclamp_cdev->cdev) {
pr_info("Un-registered cdev:%s\n",
uclamp_cdev->cdev->type);
thermal_cooling_device_unregister(uclamp_cdev->cdev);
uclamp_cdev->cdev = NULL;
}
sched_thermal_freq_cap(
uclamp_cdev->cpu,
uclamp_cdev->em->table[uclamp_cdev->max_state].frequency);
kfree(uclamp_cdev);
}
mutex_unlock(&therm_cdev_list_lock);
}
static int __init thermal_uclamp_init(void)
{
int ret = 0;
unsigned int cpu = 0;
struct cpufreq_policy *policy = 0;
struct thermal_uclamp_cdev *uclamp_cdev = NULL;
char *name = NULL;
if (!list_empty(&therm_uclamp_cdev_list)) {
pr_err("Thermal uclamp cdev already initialized\n");
return -EALREADY;
}
mutex_lock(&therm_cdev_list_lock);
for (; cpu < num_possible_cpus(); )
{
policy = cpufreq_cpu_get(cpu);
if (!policy) {
pr_err("cpufreq policy %d fetch error. Trying again...\n", cpu);
ret = -EPROBE_DEFER;
goto cleanup_exit;
}
uclamp_cdev = kzalloc(sizeof(*uclamp_cdev), GFP_KERNEL);
if (!uclamp_cdev) {
ret = -ENOMEM;
goto cleanup_exit;
}
uclamp_cdev->cpu = policy->cpu;
uclamp_cdev->related_cpu_cnt = cpumask_weight(policy->related_cpus);
cpu += uclamp_cdev->related_cpu_cnt;
uclamp_cdev->max_state = cpufreq_table_count_valid_entries(policy) - 1;
if (!uclamp_cdev->max_state) {
pr_err("CPU:%d frequency table not available.\n", policy->cpu);
ret = -EPROBE_DEFER;
goto cleanup_exit;
}
uclamp_cdev->em = em_cpu_get(policy->cpu);
if (!uclamp_cdev->em) {
pr_err("CPU:%d energy model not available.\n", policy->cpu);
goto cleanup_exit;
}
cpufreq_cpu_put(policy);
policy = NULL;
list_add_tail(&uclamp_cdev->cdev_list, &therm_uclamp_cdev_list);
sched_thermal_freq_cap(
uclamp_cdev->cpu,
uclamp_cdev->em->table[uclamp_cdev->max_state].frequency);
name = kasprintf(GFP_KERNEL, "thermal-uclamp-%d", uclamp_cdev->cpu);
uclamp_cdev->cdev = thermal_cooling_device_register(name, uclamp_cdev,
&thermal_uclamp_cdev_ops);
if (IS_ERR(uclamp_cdev->cdev)) {
ret = PTR_ERR(uclamp_cdev->cdev);
pr_err("uclamp cdev:[%s] register error. err:%d\n", name, ret);
uclamp_cdev->cdev = NULL;
goto cleanup_exit;
}
ret = device_create_file(&uclamp_cdev->cdev->device,
&dev_attr_state2power_table);
if (ret) {
pr_err("cdev:[%s] state2power attr failed. err:%d\n",
name, ret);
goto cleanup_exit;
}
pr_info("Registered cdev:%s\n", name);
}
mutex_unlock(&therm_cdev_list_lock);
return ret;
cleanup_exit:
mutex_unlock(&therm_cdev_list_lock);
if (policy) {
cpufreq_cpu_put(policy);
policy = NULL;
}
thermal_uclamp_cleanup();
return ret;
}
module_init(thermal_uclamp_init);
static void __exit thermal_uclamp_exit(void)
{
thermal_uclamp_cleanup();
}
module_exit(thermal_uclamp_exit);
MODULE_DESCRIPTION("Cooling device for placing uclamp max for CPU clusters");
MODULE_AUTHOR("Ram Chandrasekar <[email protected]>");
MODULE_LICENSE("GPL");