kernel-brax3-ubuntu-touch/drivers/thermal/mediatek/thermal_trace.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

495 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/cpumask.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/sysfs.h>
#include <linux/timer.h>
#include <linux/types.h>
#define CREATE_TRACE_POINTS
#include "thermal_trace.h"
/*==================================================
* trace point API export
*==================================================
*/
#if IS_ENABLED(CONFIG_MTK_MD_THERMAL)
EXPORT_TRACEPOINT_SYMBOL_GPL(md_mutt_limit);
EXPORT_TRACEPOINT_SYMBOL_GPL(md_tx_pwr_limit);
EXPORT_TRACEPOINT_SYMBOL_GPL(md_scg_off);
#endif
EXPORT_TRACEPOINT_SYMBOL_GPL(network_tput);
#if IS_ENABLED(CONFIG_MTK_THERMAL_INTERFACE)
EXPORT_TRACEPOINT_SYMBOL_GPL(cpu_hr_info_0);
EXPORT_TRACEPOINT_SYMBOL_GPL(cpu_hr_info_1);
#endif
#if IS_ENABLED(CONFIG_MTK_THERMAL_JATM)
EXPORT_TRACEPOINT_SYMBOL_GPL(jatm_enable);
EXPORT_TRACEPOINT_SYMBOL_GPL(jatm_disable);
EXPORT_TRACEPOINT_SYMBOL_GPL(not_start_reason);
EXPORT_TRACEPOINT_SYMBOL_GPL(try_enable_jatm);
#endif
/*==================================================
* Throughput calculator data
*==================================================
*/
#define TIMER_INTERVAL_MS (1000)
#define for_each_tput_instance(i) \
for (i = 0; i < NR_TPUT_CALC_INSTANCE; i++)
enum tput_calc_instance {
TPUT_MD,
TPUT_WIFI,
NR_TPUT_CALC_INSTANCE,
};
struct tput_stats {
unsigned long pre_tx_bytes;
unsigned long cur_tx_bytes;
unsigned long cur_tput;
};
struct thermal_info {
struct mutex lock;
struct tput_stats stats[NR_TPUT_CALC_INSTANCE];
struct delayed_work poll_queue;
unsigned long last_update_time;
};
static struct thermal_info thermal_info_data;
struct thermal_trace {
int enable;
int hr_enable;
unsigned long hr_period;
struct mutex lock;
struct hrtimer trace_timer;
};
static struct thermal_trace thermal_trace_data;
static struct thermal_cpu_info cpu_info;
static struct thermal_gpu_info gpu_info;
static struct thermal_apu_info apu_info;
/*==================================================
* Throughput calculator local function
*==================================================
*/
static void thermal_info_timer_add(int enable)
{
mutex_lock(&thermal_info_data.lock);
if (enable)
mod_delayed_work(system_freezable_power_efficient_wq,
&thermal_info_data.poll_queue,
msecs_to_jiffies(TIMER_INTERVAL_MS));
else
cancel_delayed_work(&thermal_info_data.poll_queue);
mutex_unlock(&thermal_info_data.lock);
}
static void get_tx_bytes(struct thermal_info *data)
{
struct net_device *dev;
struct net *net;
int i;
for_each_tput_instance(i)
data->stats[i].cur_tx_bytes = 0;
read_lock(&dev_base_lock);
for_each_net(net) {
for_each_netdev(net, dev) {
struct rtnl_link_stats64 temp;
struct rtnl_link_stats64 *stats;
struct tput_stats *tput_stat;
if (!strncmp(dev->name, "ccmni", 5)) {
tput_stat = &data->stats[TPUT_MD];
stats = dev_get_stats(dev, &temp);
if (tput_stat && stats) {
tput_stat->cur_tx_bytes =
tput_stat->cur_tx_bytes +
stats->tx_bytes;
}
} else if (!strncmp(dev->name, "wlan", 4)
|| !strncmp(dev->name, "ap", 2)
|| !strncmp(dev->name, "p2p", 3)) {
tput_stat = &data->stats[TPUT_WIFI];
stats = dev_get_stats(dev, &temp);
if (tput_stat && stats) {
tput_stat->cur_tx_bytes =
tput_stat->cur_tx_bytes +
stats->tx_bytes;
}
}
}
}
read_unlock(&dev_base_lock);
}
static void thermal_trace_timer_cancel(void)
{
hrtimer_try_to_cancel(&thermal_trace_data.trace_timer);
}
static void thermal_trace_timer_add(void)
{
ktime_t ktime;
ktime = ktime_set(0, thermal_trace_data.hr_period);
hrtimer_start(&thermal_trace_data.trace_timer, ktime, HRTIMER_MODE_REL);
}
static void thermal_info_work(struct work_struct *work)
{
struct thermal_info *trace_data =
container_of(work, struct thermal_info, poll_queue.work);
struct timespec64 time_spec64;
struct timespec64 cur_time;
long pre_time = trace_data->last_update_time;
int i;
ktime_get_ts64(&time_spec64);
cur_time.tv_sec = time_spec64.tv_sec;
cur_time.tv_nsec = time_spec64.tv_nsec;
if (pre_time != 0 && cur_time.tv_sec > pre_time) {
unsigned long diff;
struct tput_stats *stat;
get_tx_bytes(trace_data);
for_each_tput_instance(i) {
stat = &trace_data->stats[i];
diff = (stat->cur_tx_bytes >= stat->pre_tx_bytes)
? (stat->cur_tx_bytes - stat->pre_tx_bytes)
: (0xffffffff - stat->pre_tx_bytes
+ stat->cur_tx_bytes);
stat->cur_tput =
(diff / (cur_time.tv_sec - pre_time)) >> 7;
pr_debug("[%s] %d:time/tx/tput=%lu/%lu/%luKb/s\n",
__func__, i, cur_time.tv_sec,
stat->cur_tx_bytes, stat->cur_tput);
stat->pre_tx_bytes = stat->cur_tx_bytes;
}
}
trace_data->last_update_time = cur_time.tv_sec;
pr_debug("[%s] pre_time=%lu, tv_sec=%lu\n", __func__,
pre_time, cur_time.tv_sec);
trace_network_tput(trace_data->stats[0].cur_tput,
trace_data->stats[1].cur_tput);
trace_frs(&frs_data);
thermal_info_timer_add(thermal_trace_data.enable);
}
static void get_cpu_info(void)
{
cpu_info.ttj = sign_extend32(
readl(thermal_csram_base + CPU_TTJ_OFFSET), 31);
cpu_info.limit_powerbudget = sign_extend32(
readl(thermal_csram_base + CPU_POWERBUDGET_OFFSET), 31);
cpu_info.LL_min_opp_hint = sign_extend32(
readl(thermal_csram_base + CPU_LL_MIN_OPP_HINT_OFFSET), 31);
cpu_info.BL_min_opp_hint = sign_extend32(
readl(thermal_csram_base + CPU_BL_MIN_OPP_HINT_OFFSET), 31);
cpu_info.B_min_opp_hint = sign_extend32(
readl(thermal_csram_base + CPU_B_MIN_OPP_HINT_OFFSET), 31);
cpu_info.LL_limit_opp = sign_extend32(
readl(thermal_csram_base + CPU_LL_LIMIT_OPP_OFFSET), 31);
cpu_info.BL_limit_opp = sign_extend32(
readl(thermal_csram_base + CPU_BL_LIMIT_OPP_OFFSET), 31);
cpu_info.B_limit_opp = sign_extend32(
readl(thermal_csram_base + CPU_B_LIMIT_OPP_OFFSET), 31);
cpu_info.LL_limit_freq = readl(thermal_csram_base + CPU_LL_LIMIT_FREQ_OFFSET);
cpu_info.BL_limit_freq = readl(thermal_csram_base + CPU_BL_LIMIT_FREQ_OFFSET);
cpu_info.B_limit_freq = readl(thermal_csram_base + CPU_B_LIMIT_FREQ_OFFSET);
cpu_info.LL_cur_freq = readl(thermal_csram_base + CPU_LL_CUR_FREQ_OFFSET);
cpu_info.BL_cur_freq = readl(thermal_csram_base + CPU_BL_CUR_FREQ_OFFSET);
cpu_info.B_cur_freq = readl(thermal_csram_base + CPU_B_CUR_FREQ_OFFSET);
cpu_info.LL_max_temp = sign_extend32(
readl(thermal_csram_base + CPU_LL_MAX_TEMP_OFFSET), 31);
cpu_info.BL_max_temp = sign_extend32(
readl(thermal_csram_base + CPU_BL_MAX_TEMP_OFFSET), 31);
cpu_info.B_max_temp = sign_extend32(
readl(thermal_csram_base + CPU_B_MAX_TEMP_OFFSET), 31);
}
static void get_gpu_info(void)
{
gpu_info.ttj = sign_extend32(
readl(thermal_csram_base + GPU_TTJ_OFFSET), 31);
gpu_info.limit_powerbudget = sign_extend32(
readl(thermal_csram_base + GPU_POWERBUDGET_OFFSET), 31);
gpu_info.limit_freq = readl(thermal_csram_base + GPU_LIMIT_FREQ_OFFSET);
gpu_info.cur_freq = readl(thermal_csram_base + GPU_CUR_FREQ_OFFSET);
gpu_info.temp = sign_extend32(
readl(thermal_csram_base + GPU_TEMP_OFFSET), 31);
if (gpu_info.temp == THERMAL_TEMP_INVALID)
gpu_info.temp_noinvalid = gpu_info.pre_temp;
else
gpu_info.temp_noinvalid = gpu_info.temp;
gpu_info.pre_temp = gpu_info.temp_noinvalid;
gpu_info.vtskin = sign_extend32(
readl(thermal_csram_base + GPU_VTSKIN_OFFSET), 31);
gpu_info.ppm_limiter = readl(thermal_csram_base + GPU_PPM_LIMITER_OFFSET);
gpu_info.ppm_limit_freq = readl(thermal_csram_base + GPU_PPM_LIMIT_OFFSET);
}
static void get_apu_info(void)
{
if (thermal_apu_mbox_base) {
apu_info.ttj = sign_extend32(
readl(thermal_apu_mbox_base + APUMBOX_TTJ_OFFSET), 31);
apu_info.limit_powerbudget = sign_extend32(
readl(thermal_apu_mbox_base + APUMBOX_POWERBUDGET_OFFSET), 31);
apu_info.temp = sign_extend32(
readl(thermal_apu_mbox_base + APUMBOX_TEMP_OFFSET), 31);
apu_info.limit_opp = sign_extend32(
readl(thermal_apu_mbox_base + APUMBOX_LIMIT_OPP_OFFSET), 31);
apu_info.cur_opp = sign_extend32(
readl(thermal_apu_mbox_base + APUMBOX_CUR_OPP_OFFSET), 31);
} else {
apu_info.ttj = sign_extend32(
readl(thermal_csram_base + APU_TTJ_OFFSET), 31);
apu_info.limit_powerbudget = sign_extend32(
readl(thermal_csram_base + APU_POWERBUDGET_OFFSET), 31);
apu_info.temp = sign_extend32(
readl(thermal_csram_base + APU_TEMP_OFFSET), 31);
apu_info.limit_opp = sign_extend32(
readl(thermal_csram_base + APU_LIMIT_OPP_OFFSET), 31);
apu_info.cur_opp = sign_extend32(
readl(thermal_csram_base + APU_CUR_OPP_OFFSET), 31);
}
}
static enum hrtimer_restart thermal_trace_work(struct hrtimer *timer)
{
ktime_t ktime;
int i;
struct headroom_info hr_info[NR_CPUS];
if (!thermal_csram_base)
goto skip;
/* parse hr info */
for_each_possible_cpu(i) {
hr_info[i].temp = sign_extend32(
readl(thermal_csram_base + CPU_TEMP_OFFSET + 4 * i), 31);
hr_info[i].predict_temp = sign_extend32(
readl(thermal_csram_base + CPU_PREDICT_TEMP_OFFSET + 4 * i), 31);
hr_info[i].headroom = sign_extend32(
readl(thermal_csram_base + CPU_HEADROOM_OFFSET + 4 * i), 31);
hr_info[i].ratio = readl(thermal_csram_base + CPU_HEADROOM_RATIO_OFFSET + 4 * i);
pr_debug("[%d] temp=%d, predict_temp=%d, headroom=%d, ratio=%d\n", i,
hr_info[i].temp, hr_info[i].predict_temp, hr_info[i].headroom,
hr_info[i].ratio);
}
get_cpu_info();
get_gpu_info();
get_apu_info();
trace_cpu_hr_info_0(&hr_info[0], &hr_info[1], &hr_info[2], &hr_info[3]);
trace_cpu_hr_info_1(&hr_info[4], &hr_info[5], &hr_info[6], &hr_info[7]);
trace_thermal_cpu(&cpu_info);
trace_thermal_gpu(&gpu_info);
trace_thermal_apu(&apu_info);
skip:
ktime = ktime_set(0, thermal_trace_data.hr_period);
hrtimer_forward_now(timer, ktime);
return HRTIMER_RESTART;
}
static ssize_t enable_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%d\n",
thermal_trace_data.enable);
return len;
}
static ssize_t enable_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int enable;
if ((kstrtouint(buf, 10, &enable)))
return -EINVAL;
enable = (enable > 0) ? 1 : 0;
if (enable == thermal_trace_data.enable)
return count;
mutex_lock(&thermal_trace_data.lock);
thermal_trace_data.enable = enable;
thermal_info_timer_add(thermal_trace_data.enable);
mutex_unlock(&thermal_trace_data.lock);
return count;
}
static ssize_t hr_enable_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%d\n",
thermal_trace_data.hr_enable);
return len;
}
static ssize_t hr_enable_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int hr_enable;
if ((kstrtouint(buf, 10, &hr_enable)))
return -EINVAL;
hr_enable = (hr_enable > 0) ? 1 : 0;
if (hr_enable == thermal_trace_data.hr_enable)
return count;
mutex_lock(&thermal_trace_data.lock);
thermal_trace_data.hr_enable = hr_enable;
if (thermal_trace_data.hr_enable)
thermal_trace_timer_add();
else
thermal_trace_timer_cancel();
mutex_unlock(&thermal_trace_data.lock);
return count;
}
static ssize_t hr_period_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%lu\n",
thermal_trace_data.hr_period);
return len;
}
static ssize_t hr_period_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned long period;
if ((kstrtoul(buf, 10, &period)))
return -EINVAL;
if (period < 1000000)
return -EINVAL;
if (period == thermal_trace_data.hr_period)
return count;
mutex_lock(&thermal_trace_data.lock);
thermal_trace_data.hr_period = period;
mutex_unlock(&thermal_trace_data.lock);
return count;
}
static ssize_t mdtput_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%lu\n",
thermal_info_data.stats[TPUT_MD].cur_tput);
return len;
}
static ssize_t wifitput_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%lu\n",
thermal_info_data.stats[TPUT_WIFI].cur_tput);
return len;
}
static struct kobj_attribute enable_attr = __ATTR_RW(enable);
static struct kobj_attribute hr_enable_attr = __ATTR_RW(hr_enable);
static struct kobj_attribute hr_period_attr = __ATTR_RW(hr_period);
static struct kobj_attribute mdtput_attr = __ATTR_RO(mdtput);
static struct kobj_attribute wifitput_attr = __ATTR_RO(wifitput);
static struct attribute *thermal_trace_attrs[] = {
&enable_attr.attr,
&hr_enable_attr.attr,
&hr_period_attr.attr,
&mdtput_attr.attr,
&wifitput_attr.attr,
NULL
};
static struct attribute_group thermal_trace_attr_group = {
.name = "thermal_trace",
.attrs = thermal_trace_attrs,
};
static int __init thermal_trace_init(void)
{
int i, ret;
thermal_info_data.last_update_time = 0;
for_each_tput_instance(i) {
thermal_info_data.stats[i].pre_tx_bytes = 0;
thermal_info_data.stats[i].cur_tput = 0;
}
mutex_init(&thermal_trace_data.lock);
mutex_init(&thermal_info_data.lock);
INIT_DELAYED_WORK(&thermal_info_data.poll_queue, thermal_info_work);
hrtimer_init(&thermal_trace_data.trace_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
thermal_trace_data.trace_timer.function = thermal_trace_work;
thermal_trace_data.hr_period = 1000000; /* 1ms */
ret = sysfs_create_group(kernel_kobj, &thermal_trace_attr_group);
if (ret)
pr_info("failed to create thermal_trace sysfs, ret=%d!\n", ret);
return ret;
}
module_init(thermal_trace_init)
static void __exit thermal_trace_exit(void)
{
mutex_destroy(&thermal_trace_data.lock);
mutex_destroy(&thermal_info_data.lock);
sysfs_remove_group(kernel_kobj, &thermal_trace_attr_group);
cancel_delayed_work_sync(&thermal_info_data.poll_queue);
hrtimer_cancel(&thermal_trace_data.trace_timer);
}
module_exit(thermal_trace_exit)
MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
MODULE_DESCRIPTION("Mediatek thermal trace driver");
MODULE_LICENSE("GPL v2");