// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtk_dynamic_loading_throttling.h" #include "mtk_pbm.h" #include "mtk_pbm_common.h" #if IS_ENABLED(CONFIG_MTK_MDPM) #include "mtk_mdpm.h" #endif #if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2) #include #endif #include "mtk_pbm_gpu_cb.h" #define DEFAULT_PBM_WEIGHT 1024 #define MAX_FLASH_POWER 3500 #define MAX_MD1_POWER 4000 #define MIN_CPU_POWER 600 #define BAT_PERCENT_LIMIT (15) #define TIMER_INTERVAL_MS (200) static LIST_HEAD(pbm_policy_list); static bool mt_pbm_debug; #define DLPTCB_MAX_NUM 16 static struct pbm_callback_table pbmcb_tb[DLPTCB_MAX_NUM] = { {0} }; #if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2) static struct pbm_gpu_callback_table pbm_gpu_cb = {0, 0, 0, 0, 0}; #endif struct hpf hpf_ctrl = { .switch_md1 = 1, .switch_gpu = 0, .switch_flash = 0, .loading_dlpt = 0, .loading_md1 = 0, .loading_cpu = 0, .loading_gpu = 0, .loading_flash = MAX_FLASH_POWER, .to_cpu_budget = 0, .to_gpu_budget = 0, }; static struct hpf hpf_ctrl_manual = { .loading_dlpt = 0, .loading_md1 = 0, .loading_cpu = 0, .loading_gpu = 0, .loading_flash = 0, .to_cpu_budget = 0, .to_gpu_budget = 0, }; static struct pbm pbm_ctrl = { /* feature key */ .pbm_stop = 0, .pbm_drv_done = 0, .hpf_en = 63,/* bin: 111111 (Flash, GPU, CPU, MD3, MD1, DLPT) */ .manual_mode = 0, /*normal=0, UT(throttle)=1, UT(NO throttle)=2 */ }; static int g_dlpt_need_do = 1; static int g_start_polling; static DEFINE_MUTEX(pbm_mutex); static struct delayed_work poll_queue; static struct notifier_block pbm_nb; static bool g_pbm_update; static unsigned int gpu_max_pb; static unsigned int gpu_min_pb; static int uisoc = -1; static unsigned int ma_to_mw(unsigned int bat_cur) { union power_supply_propval prop; struct power_supply *psy; int ret; unsigned int bat_vol, ret_val; psy = power_supply_get_by_name("battery"); if (psy == NULL) { pr_notice("%s can't get battery node\n", __func__); return 0; } ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &prop); if (ret || prop.intval < 0) { pr_info("%s: POWER_SUPPLY_PROP_VOLTAGE_NOW fail\n", __func__); return 0; } bat_vol = prop.intval / 1000; ret_val = (bat_vol * bat_cur) / 1000; pr_info("[%s] %d(mV) * %d(mA) = %d(mW)\n", __func__, bat_vol, bat_cur, ret_val); return ret_val; } static unsigned long hpf_get_power_cpu(void) { struct hpf *hpfmgr = &hpf_ctrl; return hpfmgr->loading_cpu; } static unsigned long hpf_get_power_gpu(void) { struct hpf *hpfmgr = &hpf_ctrl; if (hpfmgr->switch_gpu) return hpfmgr->loading_gpu; else return 0; } unsigned long hpf_get_power_flash(void) { struct hpf *hpfmgr = &hpf_ctrl; if (hpfmgr->switch_flash) return hpfmgr->loading_flash; else return 0; } static unsigned long hpf_get_power_dlpt(void) { struct hpf *hpfmgr = &hpf_ctrl; return hpfmgr->loading_dlpt; } static unsigned long hpf_get_power_md1(void) { struct hpf *hpfmgr = &hpf_ctrl; if (hpfmgr->switch_md1) #if IS_ENABLED(CONFIG_MTK_MDPM) hpfmgr->loading_md1 = get_md1_power(MAX_POWER, true); #else hpfmgr->loading_md1 = MAX_MD1_POWER; #endif else hpfmgr->loading_md1 = 0; return hpfmgr->loading_md1; } static u32 cpu_power_to_freq(struct cpu_pbm_policy *pbm_policy, u32 power) { int i; if (!pbm_policy->em) return 0; for (i = pbm_policy->max_perf_state - 1; i > 0; i--) { if (power >= pbm_policy->em->table[i].power) break; } return pbm_policy->em->table[i].frequency; } static u32 cpu_freq_to_power(struct cpu_pbm_policy *pbm_policy, u32 freq) { int i; if (!pbm_policy->em) return 0; for (i = pbm_policy->max_perf_state - 1; i > 0; i--) { if (freq >= pbm_policy->em->table[i].frequency) break; } return pbm_policy->em->table[i].power; } static void mtk_cpu_dlpt_set_limit_by_pbm(unsigned int limit_power) { struct cpu_pbm_policy *pbm_policy; unsigned int total_power_weight, granted_power, num_cpus; u32 frequency; total_power_weight = 0; list_for_each_entry(pbm_policy, &pbm_policy_list, cpu_pbm_list) total_power_weight += pbm_policy->power_weight; list_for_each_entry(pbm_policy, &pbm_policy_list, cpu_pbm_list) { num_cpus = cpumask_weight(pbm_policy->policy->cpus); if (num_cpus == 0 || total_power_weight == 0) { pr_info("warning: num_cpus=%d total_power_weight=%d\n", num_cpus, total_power_weight); continue; } granted_power = limit_power * pbm_policy->power_weight / total_power_weight / num_cpus; frequency = cpu_power_to_freq(pbm_policy, granted_power); if (frequency) freq_qos_update_request(&pbm_policy->qos_req, frequency); } } static void mtk_cpu_dlpt_unlimit_by_pbm(void) { struct cpu_pbm_policy *pbm_policy; list_for_each_entry(pbm_policy, &pbm_policy_list, cpu_pbm_list) { freq_qos_update_request(&pbm_policy->qos_req, FREQ_QOS_MAX_DEFAULT_VALUE); } } static void mtk_gpufreq_set_power_limit_by_pbm(int limit_power) { #if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2) if (pbm_gpu_cb.set_limit != NULL) pbm_gpu_cb.set_limit(TARGET_DEFAULT, LIMIT_PBM, limit_power, GPUPPM_KEEP_IDX); #else return; #endif } static void mtk_gpufreq_get_max_min_pb(void) { #if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2) if (pbm_gpu_cb.get_max_pb != NULL && gpu_max_pb == 0) { gpu_max_pb = pbm_gpu_cb.get_max_pb(TARGET_DEFAULT); } if (pbm_gpu_cb.get_min_pb != NULL && gpu_min_pb == 0) { gpu_min_pb = pbm_gpu_cb.get_min_pb(TARGET_DEFAULT); } #else return; #endif } static void pbm_allocate_budget_manager(void) { int _dlpt, md1, dlpt, cpu, gpu, flash, tocpu, togpu; int multiple; int cpu_lower_bound = MIN_CPU_POWER; static int pre_tocpu, pre_togpu; if (pbm_ctrl.manual_mode == 1 || pbm_ctrl.manual_mode == 2) { dlpt = hpf_ctrl_manual.loading_dlpt; md1 = hpf_ctrl_manual.loading_md1; cpu = hpf_ctrl_manual.loading_cpu; gpu = hpf_ctrl_manual.loading_gpu; flash = hpf_ctrl_manual.loading_flash; } else { md1 = hpf_get_power_md1(); dlpt = hpf_get_power_dlpt(); cpu = hpf_get_power_cpu(); gpu = hpf_get_power_gpu(); flash = hpf_get_power_flash(); } if (dlpt == 0) { if (mt_pbm_debug) pr_info("DLPT=0\n"); return; } _dlpt = dlpt - (md1 + flash); if (_dlpt < 0) _dlpt = 0; if (gpu == 0) { tocpu = _dlpt; togpu = 0; /* check CPU lower bound */ if (tocpu < cpu_lower_bound) tocpu = cpu_lower_bound; if (pbm_ctrl.manual_mode != 2) { mtk_cpu_dlpt_set_limit_by_pbm(tocpu); mtk_gpufreq_set_power_limit_by_pbm(togpu); } } else { multiple = (_dlpt * 1000) / (cpu + gpu); if (multiple > 0) { tocpu = (multiple * cpu) / 1000; togpu = (multiple * gpu) / 1000; } else { tocpu = 1; togpu = 1; } if (togpu > gpu_max_pb) { togpu = gpu_max_pb; tocpu = _dlpt - gpu_max_pb; } if (tocpu < cpu_lower_bound) { tocpu = cpu_lower_bound; togpu = _dlpt - cpu_lower_bound; } if (togpu < gpu_min_pb) togpu = gpu_min_pb; if (tocpu <= 0) tocpu = 1; if (togpu <= 0) togpu = 1; if (pbm_ctrl.manual_mode != 2) { mtk_cpu_dlpt_set_limit_by_pbm(tocpu); mtk_gpufreq_set_power_limit_by_pbm(togpu); } } hpf_ctrl.to_cpu_budget = tocpu; hpf_ctrl.to_gpu_budget = togpu; if (mt_pbm_debug) { pr_info("(C/G)=%d,%d=>(D/M1/F/C/G)=%d,%d,%d,%d,%d(Multi:%d),%d\n", cpu, gpu, dlpt, md1, flash, tocpu, togpu, multiple, cpu_lower_bound); } else { if (((abs(pre_tocpu - tocpu) >= 10) && cpu > tocpu) || ((abs(pre_togpu - togpu) >= 10) && gpu > togpu)) { pr_info("(C/G)=%d,%d=>(D/M1/F/C/G)=%d,%d,%d,%d,%d(Multi:%d),%d\n", cpu, gpu, dlpt, md1, flash, tocpu, togpu, multiple, cpu_lower_bound); pre_tocpu = tocpu; pre_togpu = togpu; } else if ((cpu > tocpu) || (gpu > togpu)) { pr_info_ratelimited("(C/G)=%d,%d => (D/M1/F/C/G)=%d,%d,%d,%d,%d (Multi:%d),%d\n", cpu, gpu, dlpt, md1, flash, tocpu, togpu, multiple, cpu_lower_bound); } else { pre_tocpu = tocpu; pre_togpu = togpu; } } } static bool pbm_func_enable_check(void) { struct pbm *pwrctrl = &pbm_ctrl; if (!pwrctrl->pbm_drv_done) { pr_info("pwrctrl->pbm_drv_done: %d\n", pwrctrl->pbm_drv_done); return false; } return true; } static bool pbm_update_table_info(enum pbm_kicker kicker, struct mrp *mrpmgr) { struct hpf *hpfmgr = &hpf_ctrl; bool is_update = false; switch (kicker) { case KR_DLPT: if (hpfmgr->loading_dlpt != mrpmgr->loading_dlpt) { hpfmgr->loading_dlpt = mrpmgr->loading_dlpt; is_update = true; } break; case KR_MD1: if (hpfmgr->switch_md1 != mrpmgr->switch_md) { hpfmgr->switch_md1 = mrpmgr->switch_md; is_update = true; } break; case KR_MD3: pr_info("should not kicker KR_MD3\n"); break; case KR_CPU: if (hpfmgr->loading_cpu != mrpmgr->loading_cpu) { hpfmgr->loading_cpu = mrpmgr->loading_cpu; is_update = true; } break; case KR_GPU: if (hpfmgr->switch_gpu != mrpmgr->switch_gpu || hpfmgr->loading_gpu != mrpmgr->loading_gpu) { hpfmgr->switch_gpu = mrpmgr->switch_gpu; hpfmgr->loading_gpu = mrpmgr->loading_gpu; is_update = true; } break; case KR_FLASH: if (hpfmgr->switch_flash != mrpmgr->switch_flash) { hpfmgr->switch_flash = mrpmgr->switch_flash; is_update = true; } break; default: pr_info("[%s] ERROR, unknown kicker [%d]\n", __func__, kicker); WARN_ON_ONCE(1); break; } return is_update; } static void mtk_power_budget_manager(enum pbm_kicker kicker, struct mrp *mrpmgr) { bool pbm_enable = false; bool pbm_update = false; pbm_enable = pbm_func_enable_check(); if (!pbm_enable) return; pbm_update = pbm_update_table_info(kicker, mrpmgr); if (!pbm_update) return; else g_pbm_update = pbm_update; } static void pbm_timer_add(int enable) { if (enable) mod_delayed_work(system_freezable_power_efficient_wq, &poll_queue, msecs_to_jiffies(TIMER_INTERVAL_MS)); else { cancel_delayed_work(&poll_queue); //unlimit CG mtk_cpu_dlpt_unlimit_by_pbm(); mtk_gpufreq_set_power_limit_by_pbm(0); } } void pbm_check_and_run_polling(int uisoc, int pbm_stop) { if (((uisoc <= BAT_PERCENT_LIMIT && uisoc >= 0 && pbm_stop == 0) || pbm_stop == 2) && g_start_polling == 0) { g_start_polling = 1; pbm_timer_add(g_start_polling); pr_info("[DLPT] pbm polling, soc=%d polling=%d stop=%d\n", uisoc, g_start_polling, pbm_ctrl.pbm_stop); } else if (((uisoc > BAT_PERCENT_LIMIT && pbm_stop == 0) || pbm_stop == 1) && g_start_polling == 1) { g_start_polling = 0; pr_info("[DLPT] pbm release polling, soc=%d polling=%d stop=%d\n", uisoc, g_start_polling, pbm_ctrl.pbm_stop); } } int pbm_psy_event(struct notifier_block *nb, unsigned long event, void *v) { struct power_supply *psy = v; union power_supply_propval val; int ret; if (strcmp(psy->desc->name, "battery") != 0) return NOTIFY_DONE; ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &val); if (ret) return NOTIFY_DONE; uisoc = val.intval; pbm_check_and_run_polling(uisoc, pbm_ctrl.pbm_stop); return NOTIFY_DONE; } void kicker_pbm_by_dlpt(int i_max) { struct mrp mrpmgr = {0}; mrpmgr.loading_dlpt = ma_to_mw(i_max); mtk_power_budget_manager(KR_DLPT, &mrpmgr); } void kicker_pbm_by_md(enum pbm_kicker kicker, bool status) { struct mrp mrpmgr = {0}; mrpmgr.switch_md = status; mtk_power_budget_manager(kicker, &mrpmgr); } EXPORT_SYMBOL(kicker_pbm_by_md); void kicker_pbm_by_cpu(unsigned int loading) { struct mrp mrpmgr = {0}; mrpmgr.loading_cpu = loading; mtk_power_budget_manager(KR_CPU, &mrpmgr); } void kicker_pbm_by_gpu(bool status, unsigned int loading, int voltage) { struct mrp mrpmgr = {0}; mrpmgr.switch_gpu = status; mrpmgr.loading_gpu = loading; mtk_power_budget_manager(KR_GPU, &mrpmgr); } void kicker_pbm_by_flash(bool status) { struct mrp mrpmgr = {0}; mrpmgr.switch_flash = status; mtk_power_budget_manager(KR_FLASH, &mrpmgr); } EXPORT_SYMBOL(kicker_pbm_by_flash); static void pbm_thread_handle(struct work_struct *work) { int g_dlpt_state_sync = 0; unsigned int gpu_cur_pb = 0, gpu_cur_volt = 0; struct cpu_pbm_policy *pbm_policy; unsigned int req_total_power = 0; mtk_gpufreq_get_max_min_pb(); #if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2) if (pbm_gpu_cb.get_cur_vol != NULL) gpu_cur_volt = pbm_gpu_cb.get_cur_vol(TARGET_DEFAULT); if (pbm_gpu_cb.get_cur_pb != NULL) gpu_cur_pb = pbm_gpu_cb.get_cur_pb(TARGET_DEFAULT); #endif if (gpu_cur_volt) kicker_pbm_by_gpu(true, gpu_cur_pb, gpu_cur_volt); else kicker_pbm_by_gpu(false, gpu_cur_pb, gpu_cur_volt); list_for_each_entry(pbm_policy, &pbm_policy_list, cpu_pbm_list) { req_total_power += pbm_policy->power; } kicker_pbm_by_cpu(req_total_power); mutex_lock(&pbm_mutex); if (g_dlpt_need_do == 1 && g_pbm_update == true) { g_pbm_update = false; pbm_allocate_budget_manager(); g_dlpt_state_sync = 0; } pbm_timer_add(g_start_polling); mutex_unlock(&pbm_mutex); } static int _mt_pbm_pm_callback(struct notifier_block *nb, unsigned long action, void *ptr) { switch (action) { case PM_SUSPEND_PREPARE: if (mt_pbm_debug) pr_info("PM_SUSPEND_PREPARE:start\n"); mutex_lock(&pbm_mutex); g_dlpt_need_do = 0; mutex_unlock(&pbm_mutex); if (mt_pbm_debug) pr_info("PM_SUSPEND_PREPARE:end\n"); break; case PM_HIBERNATION_PREPARE: break; case PM_POST_SUSPEND: if (mt_pbm_debug) pr_info("PM_POST_SUSPEND:start\n"); mutex_lock(&pbm_mutex); g_dlpt_need_do = 1; mutex_unlock(&pbm_mutex); if (mt_pbm_debug) pr_info("PM_POST_SUSPEND:end\n"); break; case PM_POST_HIBERNATION: break; default: return NOTIFY_DONE; } return NOTIFY_OK; } struct tracepoints_table { const char *name; void *func; struct tracepoint *tp; bool registered; }; static void pbm_cpu_frequency_tracer(void *ignore, unsigned int frequency, unsigned int cpu_id) { struct cpufreq_policy *policy = NULL; struct cpu_pbm_policy *pbm_policy; unsigned int cpu, num_cpus, freq; #if IS_ENABLED(CONFIG_MTK_IRQ_MONITOR_DEBUG) u64 ts[2]; ts[0] = sched_clock(); #endif policy = cpufreq_cpu_get(cpu_id); if (!policy) return; if (cpu_id != cpumask_first(policy->related_cpus)) { cpufreq_cpu_put(policy); return; } list_for_each_entry(pbm_policy, &pbm_policy_list, cpu_pbm_list) { cpu = pbm_policy->policy->cpu; if (cpu == cpu_id) freq = frequency; else freq = cpufreq_quick_get(cpu); num_cpus = cpumask_weight(pbm_policy->policy->cpus); pbm_policy->power = cpu_freq_to_power(pbm_policy, freq) * num_cpus; } cpufreq_cpu_put(policy); #if IS_ENABLED(CONFIG_MTK_IRQ_MONITOR_DEBUG) ts[1] = sched_clock(); if ((ts[1] - ts[0] > 200000ULL) && in_hardirq()) { printk_deferred("%s duration %llu, ts[0]=%llu, ts[1]=%llu\n", __func__, ts[1] - ts[0], ts[0], ts[1]); } #endif } struct tracepoints_table pbm_tracepoints[] = { {.name = "cpu_frequency", .func = pbm_cpu_frequency_tracer}, }; #define FOR_EACH_INTEREST(i) \ for (i = 0; i < sizeof(pbm_tracepoints) / sizeof(struct tracepoints_table); i++) static void lookup_tracepoints(struct tracepoint *tp, void *ignore) { int i; FOR_EACH_INTEREST(i) { if (strcmp(pbm_tracepoints[i].name, tp->name) == 0) pbm_tracepoints[i].tp = tp; } } void tracepoint_cleanup(void) { int i; FOR_EACH_INTEREST(i) { if (pbm_tracepoints[i].registered) { tracepoint_probe_unregister( pbm_tracepoints[i].tp, pbm_tracepoints[i].func, NULL); pbm_tracepoints[i].registered = false; } } } void register_pbm_notify(void *oc_cb, enum PBM_PRIO_TAG prio_val) { if (prio_val == PBM_PRIO_GPU) { #if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2) struct pbm_gpu_callback_table *gpu_cb; gpu_cb = (struct pbm_gpu_callback_table *) oc_cb; pbm_gpu_cb.get_max_pb = gpu_cb->get_max_pb; pbm_gpu_cb.get_min_pb = gpu_cb->get_min_pb; pbm_gpu_cb.get_cur_pb = gpu_cb->get_cur_pb; pbm_gpu_cb.get_cur_vol = gpu_cb->get_cur_vol; pbm_gpu_cb.set_limit = gpu_cb->set_limit; #else return; #endif } else { pbmcb_tb[prio_val].pbmcb = oc_cb; } } EXPORT_SYMBOL(register_pbm_notify); void register_pbm_gpu_notify(void *cb) { register_pbm_notify(cb, PBM_PRIO_GPU); } EXPORT_SYMBOL(register_pbm_gpu_notify); static int mt_pbm_debug_proc_show(struct seq_file *m, void *v) { if (mt_pbm_debug) seq_puts(m, "pbm debug enabled\n"); else seq_puts(m, "pbm debug disabled\n"); return 0; } /* * enable debug message */ static ssize_t mt_pbm_debug_proc_write (struct file *file, const char __user *buffer, size_t count, loff_t *data) { char desc[32]; int len = 0; int debug = 0; len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1); if (copy_from_user(desc, buffer, len)) return 0; desc[len] = '\0'; /* if (sscanf(desc, "%d", &debug) == 1) { */ if (kstrtoint(desc, 10, &debug) == 0) { if (debug == 0) mt_pbm_debug = 0; else if (debug == 1) mt_pbm_debug = 1; else pr_notice("should be [0:disable,1:enable]\n"); } else pr_notice("should be [0:disable,1:enable]\n"); return count; } static int mt_pbm_manual_mode_proc_show(struct seq_file *m, void *v) { seq_printf(m, "manual_mode: %d\n", pbm_ctrl.manual_mode); if (pbm_ctrl.manual_mode > 0) { seq_printf(m, "request (C/G)=%lu,%lu=>(D/M1/F)=%lu,%lu,%lu\n", hpf_ctrl_manual.loading_cpu, hpf_ctrl_manual.loading_gpu, hpf_ctrl_manual.loading_dlpt, hpf_ctrl_manual.loading_md1, hpf_ctrl_manual.loading_flash); } else { seq_printf(m, "request (C/G)=%lu,%lu=>(D/M1/F)=%lu,%lu,%lu\n", hpf_ctrl.loading_cpu, hpf_ctrl.loading_gpu, hpf_ctrl.loading_dlpt, hpf_ctrl.loading_md1, hpf_ctrl.loading_flash); } seq_printf(m, "budget (C/G)=%lu,%lu\n", hpf_ctrl.to_cpu_budget, hpf_ctrl.to_gpu_budget); return 0; } static ssize_t mt_pbm_manual_mode_proc_write (struct file *file, const char __user *buffer, size_t count, loff_t *data) { char desc[64], cmd[21]; int len = 0, manual_mode = 0; int loading_dlpt, loading_md1; int loading_cpu, loading_gpu, loading_flash; len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1); if (copy_from_user(desc, buffer, len)) return 0; desc[len] = '\0'; if (sscanf(desc, "%20s %d %d %d %d %d %d", cmd, &manual_mode, &loading_dlpt, &loading_md1, &loading_cpu, &loading_gpu, &loading_flash) != 7) { pr_notice("parameter number not correct\n"); return -EPERM; } if (strncmp(cmd, "manual", 6)) return -EINVAL; if (manual_mode == 1 || manual_mode == 2) { hpf_ctrl_manual.loading_dlpt = loading_dlpt; hpf_ctrl_manual.loading_md1 = loading_md1; hpf_ctrl_manual.loading_cpu = loading_cpu; hpf_ctrl_manual.loading_gpu = loading_gpu; hpf_ctrl_manual.loading_flash = loading_flash; pbm_ctrl.manual_mode = manual_mode; pbm_allocate_budget_manager(); } else if (manual_mode == 0) pbm_ctrl.manual_mode = 0; else pr_notice("pbm manual setting should be 0 or 1 or 2\n"); return count; } static int mt_pbm_stop_proc_show(struct seq_file *m, void *v) { seq_printf(m, "pbm stop: %d\n", pbm_ctrl.pbm_stop); return 0; } static ssize_t mt_pbm_stop_proc_write (struct file *file, const char __user *buffer, size_t count, loff_t *data) { char desc[64], cmd[21]; int len = 0, stop = 0; len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1); if (copy_from_user(desc, buffer, len)) return 0; desc[len] = '\0'; if (sscanf(desc, "%20s %d", cmd, &stop) != 2) { pr_notice("parameter number not correct\n"); return -EPERM; } if (strncmp(cmd, "stop", 4)) return -EINVAL; if (stop == 0 || stop == 1 || stop == 2) { pbm_ctrl.pbm_stop = stop; pbm_check_and_run_polling(uisoc, pbm_ctrl.pbm_stop); } else pr_notice("pbm stop should be 0 or 1 or 2\n"); return count; } #define PROC_FOPS_RW(name) \ static int mt_ ## name ## _proc_open(struct inode *inode, struct file *file)\ { \ return single_open(file, mt_ ## name ## _proc_show, PDE_DATA(inode));\ } \ static const struct proc_ops mt_ ## name ## _proc_fops = { \ .proc_open = mt_ ## name ## _proc_open, \ .proc_read = seq_read, \ .proc_lseek = seq_lseek, \ .proc_release = single_release, \ .proc_write = mt_ ## name ## _proc_write, \ } #define PROC_FOPS_RO(name) \ static int mt_ ## name ## _proc_open(struct inode *inode, struct file *file)\ { \ return single_open(file, mt_ ## name ## _proc_show, PDE_DATA(inode));\ } \ static const struct proc_ops mt_ ## name ## _proc_fops = { \ .proc_open = mt_ ## name ## _proc_open, \ .proc_read = seq_read, \ .proc_lseek = seq_lseek, \ .proc_release = single_release, \ } #define PROC_ENTRY(name) {__stringify(name), &mt_ ## name ## _proc_fops} PROC_FOPS_RW(pbm_debug); PROC_FOPS_RW(pbm_manual_mode); PROC_FOPS_RW(pbm_stop); static int mt_pbm_create_procfs(void) { struct proc_dir_entry *dir = NULL; int i; struct pentry { const char *name; const struct proc_ops *fops; }; const struct pentry entries[] = { PROC_ENTRY(pbm_debug), PROC_ENTRY(pbm_manual_mode), PROC_ENTRY(pbm_stop), }; dir = proc_mkdir("pbm", NULL); if (!dir) { pr_notice("fail to create /proc/pbm @ %s()\n", __func__); return -ENOMEM; } for (i = 0; i < ARRAY_SIZE(entries); i++) { if (!proc_create(entries[i].name, 0660, dir, entries[i].fops)) pr_notice("@%s: create /proc/pbm/%s failed\n", __func__, entries[i].name); } return 0; } static int pbm_probe(struct platform_device *pdev) { struct cpufreq_policy *policy; struct cpu_pbm_policy *pbm_policy; struct device_node *np; unsigned int i; int cpu, ret; np = of_find_compatible_node(NULL, NULL, "mediatek,pbm"); if (!np) { dev_notice(&pdev->dev, "get pbm node fail\n"); return -ENODATA; } mt_pbm_create_procfs(); pm_notifier(_mt_pbm_pm_callback, 0); pbm_nb.notifier_call = pbm_psy_event; ret = power_supply_reg_notifier(&pbm_nb); if (ret) { pr_notice("pbm power_supply_reg_notifier fail\n"); return ret; } for_each_kernel_tracepoint(lookup_tracepoints, NULL); FOR_EACH_INTEREST(i) { if (pbm_tracepoints[i].tp == NULL) { pr_info("pbm Error, %s not found\n", pbm_tracepoints[i].name); tracepoint_cleanup(); return -1; } } ret = tracepoint_probe_register(pbm_tracepoints[0].tp, pbm_tracepoints[0].func, NULL); pbm_tracepoints[0].registered = true; if (!ret) pbm_tracepoints[0].registered = true; else pr_info("cpu_frequency: Couldn't activate tracepoint\n"); #if IS_ENABLED(CONFIG_MTK_DYNAMIC_LOADING_POWER_THROTTLING) register_dlpt_notify(&kicker_pbm_by_dlpt, DLPT_PRIO_PBM); #endif for_each_possible_cpu(cpu) { policy = cpufreq_cpu_get(cpu); if (!policy) continue; if (policy->cpu == cpu) { pbm_policy = kzalloc(sizeof(*pbm_policy), GFP_KERNEL); if (!pbm_policy) return -ENOMEM; i = cpufreq_table_count_valid_entries(policy); if (!i) { pr_info("%s: CPUFreq table not found or has no valid entries\n", __func__); return -ENODEV; } pbm_policy->em = em_cpu_get(policy->cpu); if (pbm_policy->em) { pbm_policy->max_perf_state = pbm_policy->em->nr_perf_states; } else { pr_info("%s: Fail to get em from cpu %d\n", __func__, policy->cpu); } pbm_policy->policy = policy; pbm_policy->cpu = cpu; pbm_policy->power_weight = DEFAULT_PBM_WEIGHT; ret = freq_qos_add_request(&policy->constraints, &pbm_policy->qos_req, FREQ_QOS_MAX, FREQ_QOS_MAX_DEFAULT_VALUE); if (ret < 0) { pr_notice("%s: Fail to add freq constraint (%d)\n", __func__, ret); return ret; } list_add_tail(&pbm_policy->cpu_pbm_list, &pbm_policy_list); } } INIT_DELAYED_WORK(&poll_queue, pbm_thread_handle); pbm_ctrl.pbm_drv_done = 1; return ret; } static int pbm_remove(struct platform_device *pdev) { struct cpu_pbm_policy *pbm_policy, *pbm_policy_t; cancel_delayed_work_sync(&poll_queue); //unlimit CG mtk_cpu_dlpt_unlimit_by_pbm(); mtk_gpufreq_set_power_limit_by_pbm(0); list_for_each_entry_safe(pbm_policy, pbm_policy_t, &pbm_policy_list, cpu_pbm_list) { freq_qos_remove_request(&pbm_policy->qos_req); cpufreq_cpu_put(pbm_policy->policy); list_del(&pbm_policy->cpu_pbm_list); kfree(pbm_policy); } return 0; } static const struct of_device_id pbm_of_match[] = { { .compatible = "mediatek,pbm", }, {}, }; MODULE_DEVICE_TABLE(of, pbm_of_match); static struct platform_driver pbm_driver = { .probe = pbm_probe, .remove = pbm_remove, .driver = { .name = "mtk-power_budget_management", .of_match_table = pbm_of_match, }, }; module_platform_driver(pbm_driver); MODULE_AUTHOR("Samuel Hsieh"); MODULE_DESCRIPTION("MTK power budget management"); MODULE_LICENSE("GPL");