kernel_samsung_a53x/drivers/soc/samsung/exynos-gpu-profiler.c
2024-06-15 16:02:09 -03:00

485 lines
13 KiB
C
Executable file

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <soc/samsung/cal-if.h>
#include <soc/samsung/exynos-profiler.h>
#include <soc/samsung/exynos-migov.h>
#include <soc/samsung/exynos-gpu-profiler.h>
enum hwevent {
Q0_EMPTY,
Q1_EMPTY,
NUM_OF_Q,
};
/* Result during profile time */
struct profile_result {
struct freq_cstate_result fc_result;
s32 cur_temp;
s32 avg_temp;
/* private data */
ktime_t queued_time_snap[NUM_OF_Q];
ktime_t queued_last_updated;
ktime_t queued_time_ratio[NUM_OF_Q];
/* Times Info. */
u64 rtimes[NUM_OF_TIMEINFO];
};
static struct profiler {
struct device_node *root;
int enabled;
s32 migov_id;
u32 cal_id;
struct freq_table *table;
u32 table_cnt;
u32 dyn_pwr_coeff;
u32 st_pwr_coeff;
const char *tz_name;
struct thermal_zone_device *tz;
struct freq_cstate fc;
struct freq_cstate_snapshot fc_snap[NUM_OF_USER];
u32 cur_freq_idx; /* current freq_idx */
u32 max_freq_idx; /* current max_freq_idx */
u32 min_freq_idx; /* current min_freq_idx */
struct profile_result result[NUM_OF_USER];
struct kobject kobj;
u64 gpu_hw_status;
} profiler;
/************************************************************************
* HELPER *
************************************************************************/
u64 get_gpu_hw_status(void)
{
#if defined(CONFIG_SOC_S5E9925)
u32 addr = 0x1000a004;
unsigned long tmp;
if (exynos_smc_readsfr(addr, &tmp) == 0) {
int shift = 7;
unsigned long mask = 0x1fe;
return (u64)((tmp>>shift)&mask);
}
#endif
return 0;
}
/************************************************************************
* SUPPORT-MIGOV *
************************************************************************/
u32 gpupro_get_table_cnt(s32 id)
{
return profiler.table_cnt;
}
u32 gpupro_get_freq_table(s32 id, u32 *table)
{
int idx;
for (idx = 0; idx < profiler.table_cnt; idx++)
table[idx] = profiler.table[idx].freq;
return idx;
}
u32 gpupro_get_max_freq(s32 id)
{
return profiler.table[profiler.max_freq_idx].freq;
}
u32 gpupro_get_min_freq(s32 id)
{
return profiler.table[profiler.min_freq_idx].freq;
}
u32 gpupro_get_freq(s32 id)
{
return profiler.result[MIGOV].fc_result.freq[ACTIVE];
}
void gpupro_get_power(s32 id, u64 *dyn_power, u64 *st_power)
{
*dyn_power = profiler.result[MIGOV].fc_result.dyn_power;
*st_power = profiler.result[MIGOV].fc_result.st_power;
}
void gpupro_get_power_change(s32 id, s32 freq_delta_ratio,
u32 *freq, u64 *dyn_power, u64 *st_power)
{
struct profile_result *result = &profiler.result[MIGOV];
struct freq_cstate_result *fc_result = &result->fc_result;
int flag = (STATE_SCALE_WO_SPARE | STATE_SCALE_TIME | STATE_SCALE_WITH_ORG_CAP);
get_power_change(profiler.table, profiler.table_cnt,
profiler.cur_freq_idx, profiler.min_freq_idx, profiler.max_freq_idx,
fc_result->time[ACTIVE], fc_result->time[CLK_OFF], freq_delta_ratio,
fc_result->profile_time, result->avg_temp, flag, dyn_power, st_power, freq);
}
u32 gpupro_get_active_pct(s32 id)
{
return profiler.result[MIGOV].fc_result.ratio[ACTIVE];
}
s32 gpupro_get_temp(s32 id)
{
return profiler.result[MIGOV].avg_temp;
}
void gpupro_set_margin(s32 id, s32 margin)
{
gpu_dvfs_set_freq_margin(margin);
return;
}
static void gpupro_reset_profiler(int user)
{
struct freq_cstate *fc = &profiler.fc;
struct profile_result *result = &profiler.result[user];
struct freq_cstate_snapshot *fc_snap = &profiler.fc_snap[user];
uint64_t* cur_queued_time;
int idx;
profiler.fc.time[ACTIVE] = gpu_dvfs_get_time_in_state();
sync_fcsnap_with_cur(fc, fc_snap, profiler.table_cnt);
fc_snap->last_snap_time = gpu_dvfs_get_tis_last_update();
result->queued_last_updated = gpu_dvfs_get_job_queue_last_updated();
cur_queued_time = gpu_dvfs_get_job_queue_count();
if (cur_queued_time) {
for (idx = 0; idx < NUM_OF_Q; idx++)
result->queued_time_snap[idx] = cur_queued_time[idx];
}
}
static u32 gpupro_update_profile(int user);
u32 gpupro_update_mode(s32 id, int mode)
{
if (profiler.enabled != mode) {
/* reset profiler struct at start time */
if (mode)
gpupro_reset_profiler(MIGOV);
profiler.enabled = mode;
return 0;
}
gpupro_update_profile(MIGOV);
return 0;
}
u64 gpupro_get_q_empty_pct(s32 type)
{
return profiler.result[MIGOV].queued_time_ratio[type];
}
u64 gpupro_get_input_nr_avg_cnt(void)
{
return 0;
}
u64 gpupro_get_timeinfo(enum timeinfo idx)
{
return profiler.result[MIGOV].rtimes[idx];
}
void gpupro_set_targetframetime(int us)
{
exynos_migov_set_targetframetime(us);
return;
}
void gpupro_set_targettime_margin(int us)
{
exynos_migov_set_targettime_margin(us);
return;
}
void gpupro_set_util_margin(int percentage)
{
exynos_migov_set_util_margin(percentage);
return;
}
void gpupro_set_decon_time(int us)
{
exynos_migov_set_decon_time(us);
return;
}
void gpupro_set_comb_ctrl(int enable)
{
exynos_migov_set_comb_ctrl(enable);
return;
}
u64 gpupro_get_gpu_hw_status(void)
{
return profiler.gpu_hw_status;
}
struct private_fn_gpu gpu_pd_fn = {
.get_q_empty_pct = &gpupro_get_q_empty_pct,
.get_input_nr_avg_cnt = &gpupro_get_input_nr_avg_cnt,
.get_timeinfo = &gpupro_get_timeinfo,
.set_targetframetime = &gpupro_set_targetframetime,
.set_targettime_margin = &gpupro_set_targettime_margin,
.set_util_margin = &gpupro_set_util_margin,
.set_decon_time = &gpupro_set_decon_time,
.set_comb_ctrl = &gpupro_set_comb_ctrl,
.get_gpu_hw_status = &gpupro_get_gpu_hw_status,
};
struct domain_fn gpu_fn = {
.get_table_cnt = &gpupro_get_table_cnt,
.get_freq_table = &gpupro_get_freq_table,
.get_max_freq = &gpupro_get_max_freq,
.get_min_freq = &gpupro_get_min_freq,
.get_freq = &gpupro_get_freq,
.get_power = &gpupro_get_power,
.get_power_change = &gpupro_get_power_change,
.get_active_pct = &gpupro_get_active_pct,
.get_temp = &gpupro_get_temp,
.set_margin = &gpupro_set_margin,
.update_mode = &gpupro_update_mode,
};
/************************************************************************
* Gathering GPU Freq Information *
************************************************************************/
static u32 gpupro_update_profile(int user)
{
struct profile_result *result = &profiler.result[user];
struct freq_cstate *fc = &profiler.fc;
struct freq_cstate_snapshot *fc_snap = &profiler.fc_snap[user];
struct freq_cstate_result *fc_result = &result->fc_result;
uint64_t* cur_gpu_job_queue_count;
ktime_t cur_queued_last_updated;
ktime_t queue_updated_delta;
ktime_t tis_last_update;
ktime_t last_snap_time;
int idx;
/* Common Data */
if (profiler.tz) {
int temp = get_temp(profiler.tz);
profiler.result[user].avg_temp = (temp + profiler.result[user].cur_temp) >> 1;
profiler.result[user].cur_temp = temp;
}
profiler.cur_freq_idx = get_idx_from_freq(profiler.table,
profiler.table_cnt, gpu_dvfs_get_cur_clock(), RELATION_LOW);
profiler.max_freq_idx = get_idx_from_freq(profiler.table,
profiler.table_cnt, gpu_dvfs_get_max_freq(), RELATION_LOW);
profiler.min_freq_idx = get_idx_from_freq(profiler.table,
profiler.table_cnt, gpu_dvfs_get_min_freq(), RELATION_HIGH);
profiler.fc.time[ACTIVE] = gpu_dvfs_get_time_in_state();
tis_last_update = gpu_dvfs_get_tis_last_update();
last_snap_time = fc_snap->last_snap_time;
make_snapshot_and_time_delta(fc, fc_snap, fc_result, profiler.table_cnt);
#if IS_ENABLED(CONFIG_DRM_SGPU)
fc_result->profile_time = tis_last_update - last_snap_time;
fc_snap->last_snap_time = tis_last_update;
#endif
compute_freq_cstate_result(profiler.table, fc_result, profiler.table_cnt,
profiler.cur_freq_idx, profiler.result[user].avg_temp);
/* Private Data */
cur_gpu_job_queue_count = gpu_dvfs_get_job_queue_count();
cur_queued_last_updated = gpu_dvfs_get_job_queue_last_updated();
queue_updated_delta = cur_queued_last_updated - result->queued_last_updated;
if (cur_gpu_job_queue_count) {
for (idx = 0; idx < NUM_OF_Q; idx++) {
ktime_t queued_time_delta = cur_gpu_job_queue_count[idx] - result->queued_time_snap[idx];
if (queue_updated_delta)
result->queued_time_ratio[idx] = (queued_time_delta * RATIO_UNIT) / queue_updated_delta;
else
result->queued_time_ratio[idx] = 0;
result->queued_time_ratio[idx] = (result->queued_time_ratio[idx] < (ktime_t) RATIO_UNIT)
? result->queued_time_ratio[idx] : (ktime_t) RATIO_UNIT;
result->queued_time_snap[idx] = cur_gpu_job_queue_count[idx];
}
}
result->queued_last_updated = cur_queued_last_updated;
exynos_stats_get_run_times(&result->rtimes[0]);
return 0;
}
/************************************************************************
* INITIALIZATON *
************************************************************************/
static int register_export_fn(u32 *max_freq, u32 *min_freq, u32 *cur_freq)
{
*max_freq = gpu_dvfs_get_max_freq();
*min_freq = gpu_dvfs_get_min_freq();
*cur_freq = gpu_dvfs_get_cur_clock();
profiler.table_cnt = gpu_dvfs_get_step();
profiler.fc.time[ACTIVE] = gpu_dvfs_get_time_in_state();
return 0;
}
static int parse_dt(struct device_node *dn)
{
int ret;
/* necessary data */
ret = of_property_read_u32(dn, "cal-id", &profiler.cal_id);
if (ret)
return -2;
/* un-necessary data */
ret = of_property_read_s32(dn, "migov-id", &profiler.migov_id);
if (ret)
profiler.migov_id = -1; /* Don't support migov */
of_property_read_u32(dn, "power-coefficient", &profiler.dyn_pwr_coeff);
of_property_read_u32(dn, "static-power-coefficient", &profiler.st_pwr_coeff);
of_property_read_string(dn, "tz-name", &profiler.tz_name);
return 0;
}
static int init_profile_result(struct profile_result *result, int size)
{
if (init_freq_cstate_result(&result->fc_result, NUM_OF_CSTATE, size))
return -ENOMEM;
return 0;
}
static void show_profiler_info(void)
{
int idx;
pr_info("================ gpu domain ================\n");
pr_info("min= %dKHz, max= %dKHz\n",
profiler.table[profiler.table_cnt - 1].freq, profiler.table[0].freq);
for (idx = 0; idx < profiler.table_cnt; idx++)
pr_info("lv=%3d freq=%8d volt=%8d dyn_cost=%5d st_cost=%5d\n",
idx, profiler.table[idx].freq, profiler.table[idx].volt,
profiler.table[idx].dyn_cost,
profiler.table[idx].st_cost);
if (profiler.tz_name)
pr_info("support temperature (tz_name=%s)\n", profiler.tz_name);
if (profiler.migov_id != -1)
pr_info("support migov domain(id=%d)\n", profiler.migov_id);
}
void exynos_sdp_set_powertable(int id, int cnt, struct freq_table *table);
static int exynos_gpu_profiler_probe(struct platform_device *pdev)
{
unsigned int org_max_freq, org_min_freq, cur_freq;
int ret, idx;
/* get node of device tree */
if (!pdev->dev.of_node) {
pr_err("gpupro: failed to get device treee\n");
return -EINVAL;
}
profiler.root = pdev->dev.of_node;
/* Parse data from Device Tree to init domain */
ret = parse_dt(profiler.root);
if (ret) {
pr_err("gpupro: failed to parse dt(ret: %d)\n", ret);
return -EINVAL;
}
register_export_fn(&org_max_freq, &org_min_freq, &cur_freq);
/* init freq table */
profiler.table = init_freq_table(
(u32 *)gpu_dvfs_get_freq_table(), profiler.table_cnt,
profiler.cal_id, org_max_freq, org_min_freq,
profiler.dyn_pwr_coeff, profiler.st_pwr_coeff,
PWR_COST_CFVV, PWR_COST_CFVV);
if (!profiler.table) {
pr_err("gpupro: failed to init freq_table\n");
return -EINVAL;
}
exynos_sdp_set_powertable(profiler.migov_id, profiler.table_cnt, profiler.table);
profiler.max_freq_idx = 0;
profiler.min_freq_idx = profiler.table_cnt - 1;
profiler.cur_freq_idx = get_idx_from_freq(profiler.table,
profiler.table_cnt, cur_freq, RELATION_HIGH);
if (init_freq_cstate(&profiler.fc, NUM_OF_CSTATE, profiler.table_cnt))
return -ENOMEM;
/* init snapshot & result table */
for (idx = 0; idx < NUM_OF_USER; idx++) {
if (init_freq_cstate_snapshot(&profiler.fc_snap[idx],
NUM_OF_CSTATE, profiler.table_cnt))
return -ENOMEM;
if (init_profile_result(&profiler.result[idx], profiler.table_cnt))
return -EINVAL;
}
/* get thermal-zone to get temperature */
if (profiler.tz_name)
profiler.tz = init_temp(profiler.tz_name);
if (profiler.tz)
init_static_cost(profiler.table, profiler.table_cnt,
1, profiler.root, profiler.tz);
ret = exynos_migov_register_domain(MIGOV_GPU, &gpu_fn, &gpu_pd_fn);
/* get GPU HW Status */
profiler.gpu_hw_status = get_gpu_hw_status();
show_profiler_info();
return ret;
}
static const struct of_device_id exynos_gpu_profiler_match[] = {
{
.compatible = "samsung,exynos-gpu-profiler",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_gpu_profiler_match);
static struct platform_driver exynos_gpu_profiler_driver = {
.probe = exynos_gpu_profiler_probe,
.driver = {
.name = "exynos-gpu-profiler",
.owner = THIS_MODULE,
.of_match_table = exynos_gpu_profiler_match,
},
};
static int exynos_gpu_profiler_init(void)
{
return platform_driver_register(&exynos_gpu_profiler_driver);
}
late_initcall(exynos_gpu_profiler_init);
MODULE_SOFTDEP("pre: exynos-migov");
MODULE_DESCRIPTION("Exynos GPU Profiler");
MODULE_LICENSE("GPL");