/* * Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sysedp_internal.h" struct freqcap { unsigned int cpu; unsigned int gpu; unsigned int emc; }; static unsigned int gpu_high_threshold = 500; static unsigned int gpu_window = 80; static unsigned int gpu_high_hist; static unsigned int gpu_high_count = 2; static unsigned int priority_bias = 75; static unsigned int online_cpu_count; static bool gpu_busy; static unsigned int fgpu; static unsigned int avail_power; static unsigned int avail_oc_relax; static unsigned int cap_method; static struct tegra_sysedp_corecap *cur_corecap; static struct clk *emc_cap_clk; static struct clk *gpu_cap_clk; static struct pm_qos_request cpufreq_qos; static unsigned int cpu_power_balance; static unsigned int force_gpu_pri; static struct delayed_work capping_work; static struct tegra_sysedp_platform_data *capping_device_platdata; static struct freqcap core_policy; static struct freqcap forced_caps; static struct freqcap cur_caps; static DEFINE_MUTEX(core_lock); static int init_done; /* To save some cycles from a linear search */ static unsigned int cpu_lut_match(unsigned int power, struct tegra_system_edp_entry *lut, unsigned int lutlen) { unsigned int fv; unsigned int lv; unsigned int step; unsigned int i; if (lutlen == 1) return 0; fv = lut[0].power_limit_100mW * 100; lv = lut[lutlen - 1].power_limit_100mW * 100; step = (lv - fv) / (lutlen - 1); i = (power - fv + step - 1) / step; i = min_t(unsigned int, i, lutlen - 1); if (lut[i].power_limit_100mW * 100 >= power) return i; /* Didn't work, search back from the end */ return lutlen - 1; } static unsigned int get_cpufreq_lim(unsigned int power) { struct tegra_system_edp_entry *p; int i; i = cpu_lut_match(power, capping_device_platdata->cpufreq_lim, capping_device_platdata->cpufreq_lim_size); p = capping_device_platdata->cpufreq_lim + i; for (; i > 0; i--, p--) { if (p->power_limit_100mW * 100 <= power) break; } WARN_ON(p->power_limit_100mW > power); return p->freq_limits[online_cpu_count - 1]; } static void pr_caps(struct freqcap *old, struct freqcap *new, unsigned int cpu_power) { if (!IS_ENABLED(CONFIG_DEBUG_KERNEL)) return; if (new->cpu == old->cpu && new->gpu == old->gpu && new->emc == old->emc) return; pr_debug("sysedp: ncpus %u, gpupri %d, core %5u mW, " "cpu %5u mW %u kHz, gpu %u kHz, emc %u kHz\n", online_cpu_count, gpu_busy, cur_corecap->power, cpu_power, new->cpu, new->gpu, new->emc); } static void apply_caps(struct tegra_sysedp_devcap *devcap) { struct freqcap new; int r; int do_trace = 0; core_policy.cpu = get_cpufreq_lim(devcap->cpu_power + cpu_power_balance); core_policy.gpu = devcap->gpufreq; core_policy.emc = devcap->emcfreq; new.cpu = forced_caps.cpu ?: core_policy.cpu; new.gpu = forced_caps.gpu ?: core_policy.gpu; new.emc = forced_caps.emc ?: core_policy.emc; if (new.cpu != cur_caps.cpu) { pm_qos_update_request(&cpufreq_qos, new.cpu); do_trace = 1; } if (new.emc != cur_caps.emc) { r = clk_set_rate(emc_cap_clk, new.emc * 1000); WARN_ON(r); do_trace = 1; } if (new.gpu != cur_caps.gpu) { r = clk_set_rate(gpu_cap_clk, new.gpu * 1000); WARN_ON(r && (r != -ENOENT)); do_trace = 1; } if (do_trace) trace_sysedp_dynamic_capping(new.cpu, new.gpu, new.emc, gpu_busy); pr_caps(&cur_caps, &new, devcap->cpu_power); cur_caps = new; } static inline bool gpu_priority(void) { return (force_gpu_pri || (gpu_busy && (fgpu > cur_corecap->cpupri.gpufreq * priority_bias / 100))); } static inline struct tegra_sysedp_devcap *get_devcap(void) { return gpu_priority() ? &cur_corecap->gpupri : &cur_corecap->cpupri; } static void __do_cap_control(void) { struct tegra_sysedp_devcap *cap; if (!cur_corecap) return; cap = get_devcap(); apply_caps(cap); } static void do_cap_control(void) { mutex_lock(&core_lock); __do_cap_control(); mutex_unlock(&core_lock); } static void update_cur_corecap(void) { struct tegra_sysedp_corecap *cap; unsigned int power; unsigned int relaxed_power; int i; if (!capping_device_platdata) return; power = avail_power * capping_device_platdata->core_gain / 100; i = capping_device_platdata->corecap_size - 1; cap = capping_device_platdata->corecap + i; for (; i >= 0; i--, cap--) { switch (cap_method) { default: pr_warn("%s: Unknown cap_method, %x! Assuming direct.\n", __func__, cap_method); cap_method = TEGRA_SYSEDP_CAP_METHOD_DIRECT; /* Intentional fall-through*/ case TEGRA_SYSEDP_CAP_METHOD_DIRECT: relaxed_power = 0; break; case TEGRA_SYSEDP_CAP_METHOD_SIGNAL: relaxed_power = min(avail_oc_relax, cap->pthrot); break; case TEGRA_SYSEDP_CAP_METHOD_RELAX: relaxed_power = cap->pthrot; break; } if (cap->power <= power + relaxed_power) { cur_corecap = cap; cpu_power_balance = power + relaxed_power - cap->power; return; } } cur_corecap = capping_device_platdata->corecap; cpu_power_balance = 0; } /* set the available power budget for cpu/gpu/emc (in mW) */ void sysedp_set_dynamic_cap(unsigned int power, unsigned int oc_relax) { if (!init_done) return; mutex_lock(&core_lock); avail_power = power; avail_oc_relax = oc_relax; update_cur_corecap(); __do_cap_control(); mutex_unlock(&core_lock); } static void capping_worker(struct work_struct *work) { if (!gpu_busy) do_cap_control(); } /* * Return true if load was above threshold for at least * gpu_high_count number of notifications */ static bool calc_gpu_busy(unsigned int load) { unsigned int mask; mask = (1 << gpu_high_count) - 1; gpu_high_hist <<= 1; if (load >= gpu_high_threshold) gpu_high_hist |= 1; return (gpu_high_hist & mask) == mask; } void tegra_edp_notify_gpu_load(unsigned int load, unsigned int freq_in_hz) { bool old; old = gpu_busy; gpu_busy = calc_gpu_busy(load); fgpu = freq_in_hz / 1000; if (gpu_busy == old || force_gpu_pri || !capping_device_platdata) return; cancel_delayed_work(&capping_work); if (gpu_busy) do_cap_control(); else schedule_delayed_work(&capping_work, msecs_to_jiffies(gpu_window)); } EXPORT_SYMBOL(tegra_edp_notify_gpu_load); static int tegra_edp_cpu_notify(struct notifier_block *nb, unsigned long action, void *data) { switch (action) { case CPU_UP_PREPARE: online_cpu_count = num_online_cpus() + 1; break; case CPU_DEAD: online_cpu_count = num_online_cpus(); break; default: return NOTIFY_OK; } do_cap_control(); return NOTIFY_OK; } static struct notifier_block tegra_edp_cpu_nb = { .notifier_call = tegra_edp_cpu_notify }; #ifdef CONFIG_DEBUG_FS static struct dentry *capping_debugfs_dir; static int core_set(void *data, u64 val) { unsigned int *pdata = data; unsigned int old; old = *pdata; *pdata = val; if (old != *pdata) { /* Changes to core_gain and cap_method require corecap update */ if ((pdata == &capping_device_platdata->core_gain) || (pdata == &cap_method)) update_cur_corecap(); do_cap_control(); } return 0; } static int core_get(void *data, u64 *val) { unsigned int *pdata = data; *val = *pdata; return 0; } DEFINE_SIMPLE_ATTRIBUTE(core_fops, core_get, core_set, "%lld\n"); static void create_attr(const char *name, unsigned int *data) { struct dentry *d; d = debugfs_create_file(name, S_IRUGO | S_IWUSR, capping_debugfs_dir, data, &core_fops); WARN_ON(IS_ERR_OR_NULL(d)); } static inline void edp_show_2core_cpucaps(struct seq_file *file) { int i; struct tegra_system_edp_entry *p = capping_device_platdata->cpufreq_lim; seq_printf(file, "%5s %10s %10s\n", "Power", "1-core", "2-cores"); for (i = 0; i < capping_device_platdata->cpufreq_lim_size; i++, p++) { seq_printf(file, "%5d %10u %10u\n", p->power_limit_100mW * 100, p->freq_limits[0], p->freq_limits[1]); } } static inline void edp_show_4core_cpucaps(struct seq_file *file) { int i; struct tegra_system_edp_entry *p = capping_device_platdata->cpufreq_lim; seq_printf(file, "%5s %10s %10s %10s %10s\n", "Power", "1-core", "2-cores", "3-cores", "4-cores"); for (i = 0; i < capping_device_platdata->cpufreq_lim_size; i++, p++) { seq_printf(file, "%5d %10u %10u %10u %10u\n", p->power_limit_100mW * 100, p->freq_limits[0], p->freq_limits[1], p->freq_limits[2], p->freq_limits[3]); } } static int cpucaps_show(struct seq_file *file, void *data) { unsigned int max_nr_cpus = num_possible_cpus(); if (!capping_device_platdata || !capping_device_platdata->cpufreq_lim) return -ENODEV; if (max_nr_cpus == 2) edp_show_2core_cpucaps(file); else if (max_nr_cpus == 4) edp_show_4core_cpucaps(file); return 0; } static int corecaps_show(struct seq_file *file, void *data) { int i; struct tegra_sysedp_corecap *p; struct tegra_sysedp_devcap *c; struct tegra_sysedp_devcap *g; if (!capping_device_platdata || !capping_device_platdata->corecap) return -ENODEV; p = capping_device_platdata->corecap; seq_printf(file, "%s %s { %s %9s %9s } %s { %s %9s %9s } %7s\n", "E-state", "CPU-pri", "CPU-mW", "GPU-kHz", "EMC-kHz", "GPU-pri", "CPU-mW", "GPU-kHz", "EMC-kHz", "Pthrot"); for (i = 0; i < capping_device_platdata->corecap_size; i++, p++) { c = &p->cpupri; g = &p->gpupri; seq_printf(file, "%7u %16u %9u %9u %18u %9u %9u %7u\n", p->power, c->cpu_power, c->gpufreq, c->emcfreq, g->cpu_power, g->gpufreq, g->emcfreq, p->pthrot); } return 0; } static int status_show(struct seq_file *file, void *data) { mutex_lock(&core_lock); seq_printf(file, "cpus online : %u\n", online_cpu_count); seq_printf(file, "gpu priority: %u\n", gpu_priority()); seq_printf(file, "gain : %u\n", capping_device_platdata->core_gain); seq_printf(file, "core cap : %u\n", cur_corecap->power); seq_printf(file, "max throttle: %u\n", cur_corecap->pthrot); seq_printf(file, "cpu balance : %u\n", cpu_power_balance); seq_printf(file, "cpu power : %u\n", get_devcap()->cpu_power + cpu_power_balance); seq_printf(file, "cpu cap : %u kHz\n", cur_caps.cpu); seq_printf(file, "gpu cap : %u kHz\n", cur_caps.gpu); seq_printf(file, "emc cap : %u kHz\n", cur_caps.emc); seq_printf(file, "cc method : %u\n", cap_method); mutex_unlock(&core_lock); return 0; } static int longattr_open(struct inode *inode, struct file *file) { return single_open(file, inode->i_private, NULL); } static const struct file_operations longattr_fops = { .open = longattr_open, .read = seq_read, }; static void create_longattr(const char *name, int (*show)(struct seq_file *, void *)) { struct dentry *d; d = debugfs_create_file(name, S_IRUGO, capping_debugfs_dir, show, &longattr_fops); WARN_ON(IS_ERR_OR_NULL(d)); } static void init_debug(void) { struct dentry *d; if (!sysedp_debugfs_dir) return; d = debugfs_create_dir("capping", sysedp_debugfs_dir); if (IS_ERR_OR_NULL(d)) { WARN_ON(1); return; } capping_debugfs_dir = d; create_attr("favor_gpu", &force_gpu_pri); create_attr("gpu_threshold", &gpu_high_threshold); create_attr("force_cpu", &forced_caps.cpu); create_attr("force_gpu", &forced_caps.gpu); create_attr("force_emc", &forced_caps.emc); create_attr("gpu_window", &gpu_window); create_attr("gain", &capping_device_platdata->core_gain); create_attr("gpu_high_count", &gpu_high_count); create_attr("cap_method", &cap_method); create_attr("priority_bias", &priority_bias); create_longattr("corecaps", corecaps_show); create_longattr("cpucaps", cpucaps_show); create_longattr("status", status_show); } #else static inline void init_debug(void) {} #endif static int init_clks(void) { emc_cap_clk = clk_get_sys("battery_edp", "emc"); if (IS_ERR(emc_cap_clk)) return -ENODEV; gpu_cap_clk = clk_get_sys("battery_edp", "gpu"); if (IS_ERR(gpu_cap_clk)) { clk_put(emc_cap_clk); return -ENODEV; } return 0; } static int sysedp_dynamic_capping_probe(struct platform_device *pdev) { int r; struct tegra_sysedp_corecap *cap; int i; if (!pdev->dev.platform_data) return -EINVAL; online_cpu_count = num_online_cpus(); INIT_DELAYED_WORK(&capping_work, capping_worker); pm_qos_add_request(&cpufreq_qos, PM_QOS_CPU_FREQ_MAX, PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE); r = register_cpu_notifier(&tegra_edp_cpu_nb); if (r) return r; r = init_clks(); if (r) return r; mutex_lock(&core_lock); capping_device_platdata = pdev->dev.platform_data; avail_power = capping_device_platdata->init_req_watts; cap_method = capping_device_platdata->cap_method; switch (cap_method) { case TEGRA_SYSEDP_CAP_METHOD_DEFAULT: cap_method = TEGRA_SYSEDP_CAP_METHOD_SIGNAL; break; case TEGRA_SYSEDP_CAP_METHOD_DIRECT: case TEGRA_SYSEDP_CAP_METHOD_SIGNAL: case TEGRA_SYSEDP_CAP_METHOD_RELAX: break; default: pr_warn("%s: Unknown cap_method, %x! Assuming direct.\n", __func__, cap_method); cap_method = TEGRA_SYSEDP_CAP_METHOD_DIRECT; break; } /* scale pthrot value in capping table */ i = capping_device_platdata->corecap_size - 1; cap = capping_device_platdata->corecap + i; for (; i >= 0; i--, cap--) { cap->pthrot *= capping_device_platdata->pthrot_ratio; cap->pthrot /= 100; } update_cur_corecap(); __do_cap_control(); mutex_unlock(&core_lock); init_debug(); init_done = 1; return 0; } static struct platform_driver sysedp_dynamic_capping_driver = { .probe = sysedp_dynamic_capping_probe, .driver = { .owner = THIS_MODULE, .name = "sysedp_dynamic_capping" } }; static __init int sysedp_dynamic_capping_init(void) { return platform_driver_register(&sysedp_dynamic_capping_driver); } late_initcall(sysedp_dynamic_capping_init);