/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT #include #endif #ifdef CONFIG_MTK_DRAMC #include #endif #ifdef CONFIG_MTK_GPU_SWPM_SUPPORT #include #endif #include #include #include /**************************************************************************** * 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 <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 > /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 > /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 > /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 > /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);