kernel-brax3-ubuntu-touch/drivers/thermal/mediatek/thermal_jatm.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

635 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_qos.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/thermal.h>
#include <linux/time64.h>
#include <linux/timekeeping.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include "thermal_interface.h"
#include "thermal_jatm.h"
#include "thermal_trace.h"
static LIST_HEAD(jatm_policy_list);
struct jatm_info {
bool turn_on;
bool activated;
int max_budget;
int budget;
int interval;
int stop_deadline;
int mode;
bool fix_opp;
bool delay_start;
struct timespec64 last_jatm_enable;
struct timespec64 last_frame_start;
};
static struct jatm_info j_info;
DEFINE_MUTEX(jatm_mutex);
static void jatm_stop_work_fn(struct work_struct *work);
static void jatm_start_work_fn(struct work_struct *work);
static DECLARE_DELAYED_WORK(jatm_stop_work, jatm_stop_work_fn);
static DECLARE_DELAYED_WORK(jatm_start_work, jatm_start_work_fn);
LIST_HEAD(jatm_record_list);
static void jatm_set_deadline(void)
{
schedule_delayed_work(&jatm_stop_work,
msecs_to_jiffies(j_info.stop_deadline));
}
static unsigned long calculate_timeval_diff(struct timespec64 *start, struct timespec64 *end)
{
unsigned long ms;
ms = (end->tv_sec - start->tv_sec) * 1000;
ms += ((end->tv_nsec - start->tv_nsec) / 1000000);
return ms;
}
static bool jatm_is_enabled(void)
{
return j_info.activated;
}
static void cpu_force_max_freq(int enable)
{
struct jatm_policy *j_policy;
s32 freq_limit;
if (j_info.fix_opp == false)
return;
if (enable == 1)
freq_limit = FREQ_QOS_MAX_DEFAULT_VALUE;
else
freq_limit = FREQ_QOS_MIN_DEFAULT_VALUE;
list_for_each_entry(j_policy, &jatm_policy_list, jatm_list)
freq_qos_update_request(&j_policy->qos_req, freq_limit);
}
static void schedule_jatm_work(void)
{
schedule_delayed_work(&jatm_stop_work, msecs_to_jiffies(j_info.budget));
}
static void enable_jatm(void)
{
j_info.activated = true;
/* 1. */
set_ttj(JATM_ON);
/* 2. */
cpu_force_max_freq(1);
/* 3. */
pr_info("Enabled and remaining budget = %d\n", j_info.budget);
trace_jatm_enable(j_info.budget);
if (j_info.mode == BUDGET)
schedule_jatm_work();
else if (j_info.mode == STOP_DEADLINE)
jatm_set_deadline();
ktime_get_real_ts64(&(j_info.last_jatm_enable));
}
static void remove_all_records(void)
{
struct jatm_record *record, *tmp;
list_for_each_entry_safe(record, tmp, &jatm_record_list, list) {
list_del(&record->list);
kfree(record);
}
}
static void recycle_jatm_budget(void)
{
struct jatm_record *record, *tmp;
struct timespec64 now_tv;
unsigned long ms;
if (likely(list_empty(&jatm_record_list)))
return;
ktime_get_real_ts64(&now_tv);
record = list_last_entry(&jatm_record_list, struct jatm_record, list);
ms = calculate_timeval_diff(&record->end_tv, &now_tv);
/* elapsed more than jatm_interval since end time of last trigger,
* jatm budget is fully recovered, recycle all entries
*/
pr_debug("%s jatm_budget=%d elapsed=%d jatm_interval=%d\n", __func__,
j_info.budget, ms, j_info.interval);
if (ms >= j_info.interval) {
pr_debug("Budget is fully recoveried\n");
remove_all_records();
j_info.budget = j_info.max_budget;
} else {
/* only recovery record ends more than jatm_interval */
list_for_each_entry_safe(record, tmp, &jatm_record_list, list) {
ms = calculate_timeval_diff(&record->end_tv, &now_tv);
if (ms >= j_info.interval) {
pr_debug("recycle jatm_budget=%d\n", record->usage);
j_info.budget += record->usage;
list_del(&record->list);
kfree(record);
} else {
/* sort after current entry must newer than
* current entry, no need to traverse them
*/
break;
}
}
if (j_info.budget > j_info.max_budget) {
pr_notice("JATM budget(%d) > max_budget(%d)\n",
j_info.budget, j_info.max_budget);
j_info.budget = j_info.max_budget;
}
}
}
void jatm_start_work_fn(struct work_struct *work)
{
struct timespec64 now_tv;
int target_tj;
enum jatm_not_start_reason reason;
reason = ENABLE;
ktime_get_real_ts64(&now_tv);
mutex_lock(&jatm_mutex);
if (unlikely(jatm_is_enabled())) {
/*In case reentant happens, don't reset jatm timer, since CPU*/
/*is already being heat up because of opp0*/
pr_notice("JATM cannot enable: already enabled\n");
reason = ALREADY_ENABLED;
goto done_unlock;
}
if (j_info.mode == STOP_DEADLINE) {
target_tj = get_catm_ttj();
if (unlikely(target_tj <= get_catm_min_ttj())) {
pr_notice("JATM cannot enable: min TTJ\n");
reason = MIN_TTJ;
goto done_unlock;
}
if (get_jatm_suspend()) {
pr_notice("JATM cannot enable: being suspended\n");
reason = SUSPENDED;
goto done_unlock;
}
}
/*recycle_jatm_budget just before enabling instead of*/
/*try_enable_jatm() to have maximum budget*/
recycle_jatm_budget();
if ((j_info.mode == STOP_DEADLINE) || (j_info.budget > 0))
enable_jatm();
else {
pr_notice("JATM cannot enable: no budget\n");
reason = NO_BUDGET;
}
done_unlock:
mutex_unlock(&jatm_mutex);
trace_not_start_reason(reason);
}
static void try_enable_jatm(void)
{
int delay, elapsed;
struct timespec64 now_tv;
mutex_lock(&jatm_mutex);
ktime_get_real_ts64(&now_tv);
/* delay based on jatm usage in the past one period */
if (j_info.delay_start) {
/* recycle here to get accurate jatm usage */
recycle_jatm_budget();
if (j_info.budget < 0)
delay = j_info.interval;
else
delay = j_info.max_budget - j_info.budget;
} else { /* delay based on jatm usage in the past one period */
delay = 0;
}
elapsed = calculate_timeval_diff(&j_info.last_frame_start, &now_tv);
pr_notice("Try to enable JATM after %d ms and delay start %d ms\n", elapsed, delay);
trace_try_enable_jatm(elapsed, delay);
if (delay > 0) {
if (delayed_work_pending(&jatm_start_work))
mod_delayed_work(system_wq, &jatm_start_work,
msecs_to_jiffies(delay));
else
schedule_delayed_work(&jatm_start_work,
msecs_to_jiffies(delay));
mutex_unlock(&jatm_mutex);
} else {
if (delayed_work_pending(&jatm_start_work))
cancel_delayed_work(&jatm_start_work);
/* jatm_start_work_fn will hold the lock, release here */
mutex_unlock(&jatm_mutex);
jatm_start_work_fn(NULL);
}
}
static void record_jatm_usage(struct timespec64 now, int elapsed_ms)
{
struct jatm_record *record = kmalloc(sizeof(struct jatm_record), GFP_KERNEL);
if (!record)
return;
record->usage = elapsed_ms;
record->end_tv = now;
list_add_tail(&(record->list), &jatm_record_list);
}
static void disable_jatm(enum jatm_stop_reason reason)
{
struct timespec64 now_tv;
unsigned long jatm_usage, real_usage;
unsigned long frame_length;
mutex_lock(&jatm_mutex);
ktime_get_real_ts64(&now_tv);
frame_length = calculate_timeval_diff(&j_info.last_frame_start, &now_tv);
/* the hook point in the fpsgo now is almost the start of the next frame */
j_info.last_frame_start = now_tv;
cancel_delayed_work(&jatm_start_work);
/* cancel max freq if we are during JATM, false means JATM not enabled */
if (unlikely(!jatm_is_enabled())) {
mutex_unlock(&jatm_mutex);
return;
}
j_info.activated = false;
write_jatm_suspend(1);
real_usage = calculate_timeval_diff(&j_info.last_jatm_enable, &now_tv);
if (real_usage < 5) {
pr_debug("JATM usage %d < min usage 5\n", real_usage);
jatm_usage = 5;
} else
jatm_usage = real_usage;
if (j_info.budget < jatm_usage)
j_info.budget = 0;
else
j_info.budget -= jatm_usage;
record_jatm_usage(now_tv, jatm_usage);
cancel_delayed_work(&jatm_stop_work);
set_ttj(JATM_OFF);
cpu_force_max_freq(0);
mutex_unlock(&jatm_mutex);
pr_info("stop reason=%s, frame_length=%d, real_usage=%d, remaining=%d\n",
jatm_stop_reason_string[reason], frame_length, real_usage, j_info.budget);
trace_jatm_disable(reason, frame_length, real_usage, j_info.budget);
}
static void jatm_stop_work_fn(struct work_struct *work)
{
disable_jatm(BUDGET_RUNNING_OUT);
}
void jatm_notify_fp_cb(int enable)
{
if (j_info.turn_on != true)
return;
if (enable == 1)
try_enable_jatm();
else
disable_jatm(FRAME_COMPLETE);
}
static ssize_t jatm_info_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%d, %d, %d, %d, %d, %d, %d %d\n",
j_info.max_budget,
j_info.budget,
j_info.interval,
j_info.fix_opp,
jatm_is_enabled(),
j_info.stop_deadline,
j_info.delay_start,
j_info.mode
);
return len;
}
static ssize_t jatm_info_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
char cmd[10];
int max_budget, budget, interval, fix_opp;
int jatm_stop_deadline, jatm_delay_start;
int mode;
if (sscanf(buf, "%5s %u %u %u %u %u %u %u", cmd,
&max_budget, &budget, &interval, &fix_opp,
&jatm_stop_deadline, &jatm_delay_start, &mode) == 8) {
if (strncmp(cmd, "JATM", 4) != 0) {
pr_info("[jatm_info] invalid input\n");
return -EINVAL;
}
if ((budget < 0) || (budget > 50000)) {
pr_info("[jatm_info] budget should be 0 ~ 50000\n");
return -EINVAL;
}
if ((max_budget < 0) || (max_budget > 50000)) {
pr_info("[jatm_info] max_budget should be 0 ~ 50000\n");
return -EINVAL;
}
if (max_budget < budget) {
pr_info("[jatm_info] max_budget should be >= budget\n");
return -EINVAL;
}
if ((interval < 0) || (interval > 10000)) {
pr_info("[jatm_info] interval should be 0 ~ 10000\n");
return -EINVAL;
}
if ((jatm_stop_deadline < 0) || (jatm_stop_deadline > 500)) {
pr_info("[jatm_info] jatm_stop_deadline should be 0 ~ 500\n");
return -EINVAL;
}
j_info.max_budget = max_budget;
j_info.budget = budget;
j_info.interval = interval;
if (fix_opp == 1)
j_info.fix_opp = true;
else
j_info.fix_opp = false;
j_info.stop_deadline = jatm_stop_deadline;
if (jatm_delay_start == 1)
j_info.delay_start = true;
else
j_info.delay_start = false;
j_info.mode = mode;
return count;
}
pr_info("[jatm_info] invalid input\n");
return -EINVAL;
}
static ssize_t turn_on_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%d\n",
j_info.turn_on);
return len;
}
static ssize_t turn_on_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
char cmd[10];
int activate;
int turn_on;
if (sscanf(buf, "%8s %d", cmd, &activate) == 2) {
if (strncmp(cmd, "JATM_UT", 7) == 0) {
if (activate == 1)
try_enable_jatm();
else if (activate == 0)
disable_jatm(FRAME_COMPLETE);
return count;
}
}
if (kstrtoint(buf, 10, &turn_on) == 0) {
if (turn_on == 1)
j_info.turn_on = true;
else if (turn_on == 0)
j_info.turn_on = false;
pr_info("[jatm_enable] turn_on %d\n", j_info.turn_on);
return count;
}
pr_info("[jatm_enable] invalid input\n");
return -EINVAL;
}
static ssize_t max_budget_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int max_budget;
if (kstrtoint(buf, 10, &max_budget) == 0) {
if (max_budget >= 0 && max_budget <= 50000) {
j_info.max_budget = max_budget;
j_info.budget = max_budget;
} else {
pr_info("[max_budget] should be 0 ~ 50000\n");
}
return count;
}
pr_info("[max_budget] invalid input\n");
return -EINVAL;
}
static ssize_t interval_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int interval;
if (kstrtoint(buf, 10, &interval) == 0) {
if (interval >= 0 && interval <= 10000)
j_info.interval = interval;
else
pr_info("[interval] should be 0 ~ 10000\n");
return count;
}
pr_info("[interval] invalid input\n");
return -EINVAL;
}
static ssize_t mode_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int mode;
if (kstrtoint(buf, 10, &mode) == 0) {
if (mode >= 0 && mode <= 1)
j_info.mode = mode;
else
pr_info("[mode] should be 0 ~ 1\n");
return count;
}
pr_info("[mode] invalid input\n");
return -EINVAL;
}
static struct kobj_attribute jatm_info_attr = __ATTR_RW(jatm_info);
static struct kobj_attribute turn_on_attr = __ATTR_RW(turn_on);
static struct kobj_attribute max_budget_attr = __ATTR_WO(max_budget);
static struct kobj_attribute interval_attr = __ATTR_WO(interval);
static struct kobj_attribute mode_attr = __ATTR_WO(mode);
static struct attribute *jatm_attrs[] = {
&jatm_info_attr.attr,
&turn_on_attr.attr,
&max_budget_attr.attr,
&interval_attr.attr,
&mode_attr.attr,
NULL
};
static struct attribute_group jatm_attr_group = {
.name = "jatm",
.attrs = jatm_attrs,
};
static const struct of_device_id therm_jatm_of_match[] = {
{ .compatible = "mediatek,therm_jatm", },
{},
};
MODULE_DEVICE_TABLE(of, therm_jatm_of_match);
static int therm_jatm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cpufreq_policy *policy;
struct jatm_policy *j_policy;
int cpu, ret;
if (!pdev->dev.of_node) {
dev_err(&pdev->dev, "Only DT based supported\n");
return -ENODEV;
}
for_each_possible_cpu(cpu) {
policy = cpufreq_cpu_get(cpu);
if (!policy)
continue;
if (policy->cpu == cpu) {
j_policy = devm_kzalloc(dev, sizeof(*j_policy), GFP_KERNEL);
if (!j_policy)
return -ENOMEM;
j_policy->policy = policy;
j_policy->cpu = cpu;
ret = freq_qos_add_request(&policy->constraints,
&j_policy->qos_req, FREQ_QOS_MIN,
FREQ_QOS_MIN_DEFAULT_VALUE);
if (ret < 0) {
dev_err(&pdev->dev, "%s: Fail to add freq constraint (%d)\n",
__func__, ret);
return ret;
}
list_add_tail(&j_policy->jatm_list, &jatm_policy_list);
}
}
ret = sysfs_create_group(kernel_kobj, &jatm_attr_group);
if (ret) {
dev_info(&pdev->dev, "failed to create thermal sysfs, ret=%d!\n", ret);
return ret;
}
j_info.max_budget = DEFAULT_JATM_INFINITE_BUDGET;
j_info.budget = DEFAULT_JATM_INFINITE_BUDGET;
j_info.interval = DEFAULT_JATM_INTERVAL;
j_info.activated = false;
j_info.fix_opp = false;
/* jatm stop time out */
j_info.stop_deadline = DEFAULT_JATM_STOP_DEADLINE;
/* jatm_delay_start*/
/* true : delay jatm enable after certain delay (allocate for heavier frame)*/
/* false : enable just after */
j_info.delay_start = false;
j_info.mode = STOP_DEADLINE;
jatm_notify_fp = jatm_notify_fp_cb;
return 0;
}
static int therm_jatm_remove(struct platform_device *pdev)
{
sysfs_remove_group(kernel_kobj, &jatm_attr_group);
return 0;
}
static struct platform_driver therm_jatm_driver = {
.probe = therm_jatm_probe,
.remove = therm_jatm_remove,
.driver = {
.name = "mtk-thermal-jatm",
.of_match_table = therm_jatm_of_match,
},
};
module_platform_driver(therm_jatm_driver);
MODULE_AUTHOR("Henry Huang <henry.huang@mediatek.com>");
MODULE_DESCRIPTION("Mediatek jank aware thermal management driver");
MODULE_LICENSE("GPL v2");