unplugged-kernel/drivers/misc/mediatek/base/power/swpm_v1/mtk_swpm.c

653 lines
15 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/ktime.h>
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
#include <sspm_reservedmem_define.h>
#endif
#ifdef CONFIG_MTK_DRAMC
#include <mtk_dramc.h>
#endif
#ifdef CONFIG_MTK_GPU_SWPM_SUPPORT
#include <mtk_gpu_power_sspm_ipi.h>
#endif
#include <mtk_swpm_common.h>
#include <mtk_swpm_platform.h>
#include <mtk_swpm_interface.h>
/****************************************************************************
* Macro Definitions
****************************************************************************/
#define DEFAULT_AVG_WINDOW (50)
#define IDD_TBL_DBG
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
#define MIN(a, b) ((a) >= (b) ? (b) : (a))
#define SWPM_OPS (swpm_m.plat_ops)
/****************************************************************************
* Type Definitions
****************************************************************************/
struct swpm_manager {
bool initialize;
bool plat_ready;
struct swpm_mem_ref_tbl *mem_ref_tbl;
unsigned int ref_tbl_size;
struct swpm_core_internal_ops *plat_ops;
};
/****************************************************************************
* Local Variables
****************************************************************************/
static struct swpm_manager swpm_m = {
.initialize = 0,
.plat_ready = 0,
.mem_ref_tbl = NULL,
.ref_tbl_size = 0,
};
static struct proc_dir_entry *swpm_dir;
static unsigned char avg_window = DEFAULT_AVG_WINDOW;
static unsigned int log_interval_ms = DEFAULT_LOG_INTERVAL_MS;
/****************************************************************************
* Global Variables
****************************************************************************/
/* swpm periodic timer for ftrace output */
unsigned int swpm_log_mask = DEFAULT_LOG_MASK;
struct timer_list swpm_timer;
struct swpm_rec_data *swpm_info_ref;
unsigned int swpm_status;
bool swpm_debug;
#ifdef CONFIG_MTK_GPU_SWPM_SUPPORT
bool swpm_gpu_debug;
#endif
DEFINE_MUTEX(swpm_mutex);
/****************************************************************************
* Static Function
****************************************************************************/
static char *_copy_from_user_for_proc(const char __user *buffer, size_t count)
{
static char buf[64];
unsigned int len = 0;
len = (count < (sizeof(buf) - 1)) ? count : (sizeof(buf) - 1);
if (copy_from_user(buf, buffer, len))
return NULL;
buf[len] = '\0';
return buf;
}
static int dump_power_proc_show(struct seq_file *m, void *v)
{
char buf[256];
char *ptr = buf;
int i;
for (i = 0; i < NR_POWER_RAIL; i++) {
ptr += snprintf(ptr, 256, "%s",
swpm_power_rail_to_string((enum power_rail)i));
if (i != NR_POWER_RAIL - 1)
ptr += sprintf(ptr, "/");
else
ptr += sprintf(ptr, " = ");
}
for (i = 0; i < NR_POWER_RAIL; i++) {
ptr += snprintf(ptr, 256, "%d",
swpm_get_avg_power((enum power_rail)i, avg_window));
if (i != NR_POWER_RAIL - 1)
ptr += sprintf(ptr, "/");
else
ptr += sprintf(ptr, " uA");
}
seq_printf(m, "%s\n", buf);
return 0;
}
static int dump_lkg_power_proc_show(struct seq_file *m, void *v)
{
return 0;
}
#ifdef CONFIG_MTK_GPU_SWPM_SUPPORT
static int gpu_debug_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "\nSWPM gpu_debug is %s\n",
(swpm_gpu_debug == true) ? "enabled" : "disabled");
if (swpm_gpu_debug == true) {
seq_printf(m, "gpu freq urate : %u\n",
swpm_info_ref->gpu_reserved[gfreq + 1]);
seq_printf(m, "gpu volt : %u\n",
swpm_info_ref->gpu_reserved[gvolt + 1]);
seq_printf(m, "gpu loading : %u\n",
swpm_info_ref->gpu_reserved[gloading + 1]);
seq_printf(m, "alu fma urate : %u\n",
swpm_info_ref->gpu_reserved[galu_fma_urate + 1]);
seq_printf(m, "tex urate : %u\n",
swpm_info_ref->gpu_reserved[gtex_urate + 1]);
seq_printf(m, "lsc urate : %u\n",
swpm_info_ref->gpu_reserved[glsc_urate + 1]);
seq_printf(m, "l2c urate : %u\n",
swpm_info_ref->gpu_reserved[gl2c_urate + 1]);
seq_printf(m, "vary urate : %u\n",
swpm_info_ref->gpu_reserved[gvary_urate + 1]);
seq_printf(m, "tiler urate : %u\n",
swpm_info_ref->gpu_reserved[gtiler_urate + 1]);
}
return 0;
}
static ssize_t gpu_debug_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
int enable_time = 0;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (!kstrtouint(buf, 10, &enable_time)) {
swpm_gpu_debug = (enable_time) ? true : false;
if (swpm_gpu_debug) {
if (enable_time < 1000000) {
if (enable_time == 1)
MTKGPUPower_model_start_swpm(1000000);
else if (enable_time == 2)
MTKGPUPower_model_sspm_enable();
} else
MTKGPUPower_model_start_swpm(enable_time);
} else
MTKGPUPower_model_stop();
} else {
swpm_err("echo 1/0 > /proc/swpm/debug\n");
}
return count;
}
#endif
static int debug_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "\nSWPM debug is %s\n",
(swpm_debug == true) ? "enabled" : "disabled");
return 0;
}
static ssize_t debug_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
int enable = 0;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (!kstrtouint(buf, 10, &enable))
swpm_debug = (enable) ? true : false;
else
swpm_err("echo 1/0 > /proc/swpm/debug\n");
return count;
}
static int enable_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "\nSWPM status = 0x%x\n", swpm_status);
return 0;
}
static ssize_t enable_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
int type, enable;
#endif
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
if (sscanf(buf, "%d %d", &type, &enable) == 2) {
swpm_lock(&swpm_mutex);
swpm_set_enable(type, enable);
if (swpm_status) {
unsigned long expires;
if (swpm_timer.function != NULL) {
expires = jiffies +
msecs_to_jiffies(log_interval_ms);
mod_timer(&swpm_timer, expires);
}
} else {
if (swpm_timer.function != NULL)
del_timer(&swpm_timer);
}
swpm_unlock(&swpm_mutex);
} else {
swpm_err("echo <type or 65535> <0 or 1> > /proc/swpm/enable\n");
}
#endif
return count;
}
static int update_cnt_proc_show(struct seq_file *m, void *v)
{
return 0;
}
static ssize_t update_cnt_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
int type, cnt;
#endif
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
if (sscanf(buf, "%d %d", &type, &cnt) == 2)
swpm_set_update_cnt(type, cnt);
else
swpm_err("echo <type or 65535> <cnt> > /proc/swpm/update_cnt\n");
#endif
return count;
}
static int profile_proc_show(struct seq_file *m, void *v)
{
if (!swpm_info_ref)
return 0;
seq_printf(m, "monitor time avg/max = %llu/%llu ns, cnt = %llu\n",
swpm_info_ref->avg_latency[MON_TIME],
swpm_info_ref->max_latency[MON_TIME],
swpm_info_ref->prof_cnt[MON_TIME]);
seq_printf(m, "calculate time avg/max = %llu/%llu ns, cnt = %llu\n",
swpm_info_ref->avg_latency[CALC_TIME],
swpm_info_ref->max_latency[CALC_TIME],
swpm_info_ref->prof_cnt[CALC_TIME]);
seq_printf(m, "proc record time avg/max = %llu/%llu ns, cnt = %llu\n",
swpm_info_ref->avg_latency[REC_TIME],
swpm_info_ref->max_latency[REC_TIME],
swpm_info_ref->prof_cnt[REC_TIME]);
seq_printf(m, "total time avg/max = %llu/%llu ns, cnt = %llu\n",
swpm_info_ref->avg_latency[TOTAL_TIME],
swpm_info_ref->max_latency[TOTAL_TIME],
swpm_info_ref->prof_cnt[TOTAL_TIME]);
seq_printf(m, "\nSWPM profile is %s\n",
(swpm_info_ref->profile_enable) ? "enabled" : "disabled");
return 0;
}
static ssize_t profile_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
int enable = 0;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (!swpm_info_ref)
goto end;
if (!kstrtouint(buf, 10, &enable))
swpm_info_ref->profile_enable = enable;
else
swpm_err("echo <1/0> > /proc/swpm/profile\n");
end:
return count;
}
static int avg_window_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Current Avg Window is %d\n",
MIN(MAX_RECORD_CNT, avg_window));
return 0;
}
static ssize_t avg_window_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
int window = 0;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (!kstrtouint(buf, 10, &window))
avg_window = MIN(MAX_RECORD_CNT, window);
else
swpm_err("echo <window> > /proc/swpm/avg_window\n");
return count;
}
static int log_interval_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Current log interval is %d ms\n", log_interval_ms);
return 0;
}
static ssize_t log_interval_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
unsigned int interval = 0;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (!kstrtouint(buf, 10, &interval))
log_interval_ms = interval;
else
swpm_err("echo <interval_ms> > /proc/swpm/log_interval\n");
return count;
}
static int log_mask_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Current log mask is 0x%x\n", swpm_log_mask);
return 0;
}
static ssize_t log_mask_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
unsigned int mask = 0;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (!kstrtouint(buf, 10, &mask))
swpm_log_mask = mask;
else
swpm_err("echo <mask> > /proc/swpm/log_mask\n");
return count;
}
PROC_FOPS_RO(dump_power);
PROC_FOPS_RO(dump_lkg_power);
#ifdef CONFIG_MTK_GPU_SWPM_SUPPORT
PROC_FOPS_RW(gpu_debug);
#endif
PROC_FOPS_RW(debug);
PROC_FOPS_RW(enable);
PROC_FOPS_RW(update_cnt);
PROC_FOPS_RW(profile);
PROC_FOPS_RW(avg_window);
PROC_FOPS_RW(log_interval);
PROC_FOPS_RW(log_mask);
static int swpm_core_ops_ready_chk(void)
{
bool func_ready = false;
struct swpm_core_internal_ops *ops_chk = swpm_m.plat_ops;
if (ops_chk &&
ops_chk->cmd)
func_ready = true;
return func_ready;
}
/***************************************************************************
* API
***************************************************************************/
int swpm_core_ops_register(struct swpm_core_internal_ops *ops)
{
if (!swpm_m.plat_ops && ops) {
swpm_m.plat_ops = ops;
swpm_m.plat_ready = swpm_core_ops_ready_chk();
} else
return -1;
return 0;
}
#undef swpm_pmu_enable
int swpm_pmu_enable(enum swpm_pmu_user id,
unsigned int enable)
{
unsigned int cmd_code;
if (!swpm_m.plat_ready)
return SWPM_INIT_ERR;
else if (id >= NR_SWPM_PMU_USER)
return SWPM_ARGS_ERR;
cmd_code = (!!enable) | (id << SWPM_CODE_USER_BIT);
SWPM_OPS->cmd(SET_PMU, cmd_code);
return SWPM_SUCCESS;
}
int swpm_append_procfs(struct swpm_entry *p)
{
if (!swpm_dir) {
swpm_err("[%s] /proc/swpm failed creation\n", __func__);
return -1;
}
if (!p) {
swpm_err("[%s] append failure, fp null\n", __func__);
return -1;
}
if (!proc_create(p->name, 0664, swpm_dir, p->fops)) {
swpm_err("[%s]: append /proc/swpm/%s failed\n",
__func__, p->name);
return -1;
}
return 0;
}
int swpm_create_procfs(void)
{
int i = 0;
struct swpm_entry swpm_entries[] = {
PROC_ENTRY(dump_power),
PROC_ENTRY(dump_lkg_power),
PROC_ENTRY(debug),
PROC_ENTRY(enable),
PROC_ENTRY(update_cnt),
PROC_ENTRY(profile),
PROC_ENTRY(avg_window),
PROC_ENTRY(log_interval),
PROC_ENTRY(log_mask),
#ifdef CONFIG_MTK_GPU_SWPM_SUPPORT
PROC_ENTRY(gpu_debug),
#endif
};
swpm_dir = proc_mkdir("swpm", NULL);
if (!swpm_dir) {
swpm_err("[%s] mkdir /proc/swpm failed\n", __func__);
return -1;
}
for (i = 0; i < ARRAY_SIZE(swpm_entries); i++) {
if (!proc_create(swpm_entries[i].name,
0664,
swpm_dir, swpm_entries[i].fops)) {
swpm_err("[%s]: create /proc/swpm/%s failed\n",
__func__, swpm_entries[i].name);
return -1;
}
}
return 0;
}
void swpm_get_rec_addr(phys_addr_t *phys,
phys_addr_t *virt,
unsigned long long *size)
{
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
/* get sspm reserved mem */
*phys = sspm_reserve_mem_get_phys(SWPM_MEM_ID);
*virt = sspm_reserve_mem_get_virt(SWPM_MEM_ID);
*size = sspm_reserve_mem_get_size(SWPM_MEM_ID);
swpm_info("phy_addr = 0x%llx, virt_addr=0x%llx, size = %llu\n",
(unsigned long long) *phys,
(unsigned long long) *virt,
*size);
#endif
}
int swpm_reserve_mem_init(phys_addr_t *virt,
unsigned long long *size)
{
int i;
unsigned char *ptr;
if (!virt)
return -1;
/* clear reserve mem */
ptr = (unsigned char *)(uintptr_t)*virt;
for (i = 0; i < *size; i++)
ptr[i] = 0x0;
return 0;
}
int swpm_interface_manager_init(struct swpm_mem_ref_tbl *ref_tbl,
unsigned int tbl_size)
{
if (!ref_tbl)
return -1;
swpm_lock(&swpm_mutex);
swpm_m.initialize = true;
swpm_m.mem_ref_tbl = ref_tbl;
swpm_m.ref_tbl_size = tbl_size;
swpm_unlock(&swpm_mutex);
return 0;
}
void swpm_update_periodic_timer(void)
{
mod_timer(&swpm_timer, jiffies + msecs_to_jiffies(log_interval_ms));
}
int swpm_set_periodic_timer(void (*func)(struct timer_list *))
{
swpm_lock(&swpm_mutex);
if (func != NULL) {
swpm_timer.function = func;
//init_timer_deferrable(&swpm_timer);
timer_setup(&swpm_timer,func,0);
}
swpm_unlock(&swpm_mutex);
return 0;
}
int swpm_mem_addr_request(enum swpm_type id, phys_addr_t **ptr)
{
int ret = 0;
if (!swpm_m.initialize || !swpm_m.mem_ref_tbl) {
swpm_err("swpm not initialize\n");
ret = -1;
goto end;
} else if (id >= swpm_m.ref_tbl_size) {
swpm_err("swpm_type invalid\n");
ret = -2;
goto end;
} else if (!(swpm_m.mem_ref_tbl[id].valid)
|| !(swpm_m.mem_ref_tbl[id].virt)) {
ret = -3;
swpm_err("swpm_mem_ref id not initialize\n");
goto end;
}
swpm_lock(&swpm_mutex);
*ptr = (swpm_m.mem_ref_tbl[id].virt);
swpm_unlock(&swpm_mutex);
end:
return ret;
}
unsigned int swpm_get_avg_power(enum power_rail type, unsigned int avg_window)
{
unsigned int *ptr;
unsigned int cnt, idx, sum = 0, pwr = 0;
if (type >= NR_POWER_RAIL) {
swpm_err("Invalid SWPM type = %d\n", type);
return 0;
}
/* window should be 1 to MAX_RECORD_CNT */
avg_window = MAX(avg_window, 1);
avg_window = MIN(avg_window, MAX_RECORD_CNT);
/* get ptr of the target meter record */
ptr = &swpm_info_ref->pwr[type][0];
/* calculate avg */
for (idx = swpm_info_ref->cur_idx, cnt = 0; cnt < avg_window; cnt++) {
sum += ptr[idx];
if (!idx)
idx = MAX_RECORD_CNT - 1;
else
idx--;
}
pwr = sum / avg_window;
swpm_dbg("avg pwr of meter %d = %d uA\n", type, pwr);
return pwr;
}
EXPORT_SYMBOL(swpm_get_avg_power);