blob: 9c0160e98df30ac5f55b803212ad781c8340ee11 [file] [log] [blame]
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/cpu.h>
#include <linux/debugfs.h>
#include <linux/edp.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_qos.h>
#include <linux/workqueue.h>
#include <linux/platform_data/tegra_edp.h>
#include <linux/debugfs.h>
#include <trace/events/sysedp.h>
#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);