From 76635927ce9e4b972f159016b094515f895e6d9a Mon Sep 17 00:00:00 2001 From: Ilan Aelion Date: Fri, 15 Jul 2011 12:05:01 -0600 Subject: video: tegra: host: 3d clock scaling Adds support for 3d clock scaling based on the 3d module idle time percentage. Original-Change-Id: I4d3a70d372b9a8bd6f999e71e135fcd35673e18f Reviewed-on: http://git-master/r/41250 Tested-by: Ilan Aelion Reviewed-by: Krishna Reddy Reviewed-by: Terje Bergstrom Reviewed-by: Scott Williams Rebase-Id: R64b86250fce24e3b43d70ffd316e9de518eb24ba --- drivers/video/tegra/host/debug.c | 2 + drivers/video/tegra/host/debug.h | 3 + drivers/video/tegra/host/dev.c | 46 +++++ drivers/video/tegra/host/nvhost_acm.c | 263 ++++++++++++++++++++++++++++- drivers/video/tegra/host/nvhost_acm.h | 6 + drivers/video/tegra/host/nvhost_scale.h | 73 ++++++++ drivers/video/tegra/host/t20/channel_t20.c | 5 + 7 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 drivers/video/tegra/host/nvhost_scale.h diff --git a/drivers/video/tegra/host/debug.c b/drivers/video/tegra/host/debug.c index c0cdf65ccd18..a7ff51aed08b 100644 --- a/drivers/video/tegra/host/debug.c +++ b/drivers/video/tegra/host/debug.c @@ -111,6 +111,8 @@ void nvhost_debug_init(struct nvhost_master *master) debugfs_create_u32("null_kickoff_pid", S_IRUGO|S_IWUSR, de, &nvhost_debug_null_kickoff_pid); + + nvhost_debug_scale_init(de); } #else void nvhost_debug_init(struct nvhost_master *master) diff --git a/drivers/video/tegra/host/debug.h b/drivers/video/tegra/host/debug.h index 829c29ecda2a..81017fe8d2a1 100644 --- a/drivers/video/tegra/host/debug.h +++ b/drivers/video/tegra/host/debug.h @@ -22,6 +22,8 @@ #ifndef __NVHOST_DEBUG_H #define __NVHOST_DEBUG_H +#include + struct output { void (*fn)(void *ctx, const char* str, size_t len); void *ctx; @@ -40,4 +42,5 @@ static inline void write_to_printk(void *ctx, const char* str, size_t len) void nvhost_debug_output(struct output *o, const char* fmt, ...); +void nvhost_debug_scale_init(struct dentry *de); #endif /*__NVHOST_DEBUG_H */ diff --git a/drivers/video/tegra/host/dev.c b/drivers/video/tegra/host/dev.c index 41b3493ce59e..16479962127d 100644 --- a/drivers/video/tegra/host/dev.c +++ b/drivers/video/tegra/host/dev.c @@ -41,6 +41,8 @@ #include #include +#include "nvhost_scale.h" + #define DRIVER_NAME "tegra_grhost" #define IFACE_NAME "nvhost" @@ -838,6 +840,46 @@ static int __devinit nvhost_init_chip_support(struct nvhost_master *host) return 0; } + + +static ssize_t enable_3d_scaling_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + ssize_t res; + + res = snprintf(buf, PAGE_SIZE, "%d\n", scale3d_is_enabled()); + + return res; +} + +static ssize_t enable_3d_scaling_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long val = 0; + + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; + + scale3d_enable(val); + + return count; +} + +static DEVICE_ATTR(enable_3d_scaling, S_IRUGO | S_IWUSR, + enable_3d_scaling_show, enable_3d_scaling_store); + +void nvhost_remove_sysfs(struct device *dev) +{ + device_remove_file(dev, &dev_attr_enable_3d_scaling); +} + +void nvhost_create_sysfs(struct device *dev) +{ + int error = device_create_file(dev, &dev_attr_enable_3d_scaling); + if (error) + dev_err(dev, "failed to create sysfs attributes"); +} + static int __devinit nvhost_probe(struct platform_device *pdev) { struct nvhost_master *host; @@ -924,6 +966,8 @@ static int __devinit nvhost_probe(struct platform_device *pdev) nvhost_debug_init(host); + nvhost_create_sysfs(&pdev->dev); + dev_info(&pdev->dev, "initialized\n"); return 0; @@ -940,6 +984,7 @@ static int __exit nvhost_remove(struct platform_device *pdev) { struct nvhost_master *host = platform_get_drvdata(pdev); nvhost_remove_chip_support(host); + nvhost_remove_sysfs(&pdev->dev); /*kfree(host);?*/ return 0; } @@ -963,6 +1008,7 @@ static int nvhost_resume(struct platform_device *pdev) clk_enable(host->mod.clk[0]); nvhost_syncpt_reset(&host->syncpt); clk_disable(host->mod.clk[0]); + scale3d_reset(); dev_info(&pdev->dev, "resumed\n"); return 0; } diff --git a/drivers/video/tegra/host/nvhost_acm.c b/drivers/video/tegra/host/nvhost_acm.c index e477d5a6ad85..fe105c156470 100644 --- a/drivers/video/tegra/host/nvhost_acm.c +++ b/drivers/video/tegra/host/nvhost_acm.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include "nvhost_scale.h" #define ACM_POWERDOWN_HANDLER_DELAY_MSEC 25 #define ACM_SUSPEND_WAIT_FOR_IDLE_TIMEOUT (2 * HZ) @@ -57,6 +59,7 @@ void nvhost_module_busy(struct nvhost_module *mod) static void powerdown_handler(struct work_struct *work) { struct nvhost_module *mod; + mod = container_of(to_delayed_work(work), struct nvhost_module, powerdown); mutex_lock(&mod->lock); if ((atomic_read(&mod->refcount) == 0) && mod->powered) { @@ -78,6 +81,222 @@ static void powerdown_handler(struct work_struct *work) mutex_unlock(&mod->lock); } +/* + * 3d clock scaling + * + * module3d_notify_busy() is called upon submit, module3d_notify_idle() is + * called when all outstanding submits are completed. Idle times are measured + * over a fixed time period (scale3d.p_period). If the 3d module idle time + * percentage goes over the limit (set in scale3d.p_idle_max), 3d clocks are + * scaled down. If the percentage goes under the minimum limit (set in + * scale3d.p_idle_min), 3d clocks are scaled up. An additional test is made + * over the time frame given in scale3d.p_fast_response for clocking up + * quickly in response to sudden load peaks. + */ +static struct scale3d_info_rec scale3d; + +static void scale_3d_clocks(unsigned long percent) +{ + unsigned long hz, curr; + + if (!tegra_is_clk_enabled(scale3d.clk_3d)) + return; + + if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA3) + if (!tegra_is_clk_enabled(scale3d.clk_3d2)) + return; + + curr = clk_get_rate(scale3d.clk_3d); + hz = percent * (curr / 100); + + if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA3) + clk_set_rate(scale3d.clk_3d2, 0); + clk_set_rate(scale3d.clk_3d, hz); +} + +static void scale_3d_clocks_handler(struct work_struct *work) +{ + unsigned int scale; + + spin_lock(&scale3d.lock); + scale = scale3d.scale; + spin_unlock(&scale3d.lock); + + if (scale != 0) { + mutex_lock(&scale3d.set_lock); + scale_3d_clocks(scale); + mutex_unlock(&scale3d.set_lock); + } +} + +static void scale3d_init(struct nvhost_module *mod) +{ + spin_lock_init(&scale3d.lock); + mutex_init(&scale3d.set_lock); + + scale3d.clk_3d = mod->clk[0]; + if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA3) + scale3d.clk_3d2 = mod->clk[1]; + + INIT_WORK(&scale3d.work, scale_3d_clocks_handler); + + /* set scaling parameter defaults */ + scale3d.enable = 1; + scale3d.p_period = 1200000; + scale3d.p_idle_min = 17; + scale3d.p_idle_max = 17; + scale3d.p_fast_response = 16000; + scale3d.p_verbosity = 0; + + scale3d_reset(); + + scale3d.init = 1; +} + +/* set 3d clocks to max */ +static void reset_3d_clocks(void) +{ + unsigned long hz; + + mutex_lock(&scale3d.set_lock); + hz = clk_round_rate(scale3d.clk_3d, UINT_MAX); + clk_set_rate(scale3d.clk_3d, hz); + if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA3) + clk_set_rate(scale3d.clk_3d2, hz); + mutex_unlock(&scale3d.set_lock); +} + +int scale3d_is_enabled(void) +{ + int enable; + + spin_lock(&scale3d.lock); + enable = scale3d.enable; + spin_unlock(&scale3d.lock); + + return enable; +} + +void scale3d_enable(int enable) +{ + int disable = 0; + + spin_lock(&scale3d.lock); + + if (enable) + scale3d.enable = 1; + else { + scale3d.enable = 0; + disable = 1; + } + + spin_unlock(&scale3d.lock); + + if (disable) + reset_3d_clocks(); +} + +static void reset_scaling_counters(ktime_t time) +{ + scale3d.idle_total = 0; + scale3d.last_idle = time; + scale3d.last_busy = time; + scale3d.idle_frame = time; +} + +static void scaling_state_check(ktime_t time) +{ + unsigned long dt; + + /* check for load peaks */ + dt = (unsigned long) ktime_us_delta(time, scale3d.fast_frame); + if (dt > scale3d.p_fast_response) { + unsigned long idleness = (scale3d.idle_total * 100) / dt; + scale3d.fast_frame = time; + /* if too busy, scale up */ + if (idleness < scale3d.p_idle_min) { + if (scale3d.p_verbosity > 5) + pr_info("scale3d: %ld%% busy\n", + 100 - idleness); + + scale3d.scale = 200; + schedule_work(&scale3d.work); + reset_scaling_counters(time); + return; + } + } + + dt = (unsigned long) ktime_us_delta(time, scale3d.idle_frame); + if (dt > scale3d.p_period) { + unsigned long idleness = (scale3d.idle_total * 100) / dt; + + if (scale3d.p_verbosity > 5) + pr_info("scale3d: idle %lu, ~%lu%%\n", + scale3d.idle_total, idleness); + + if (idleness > scale3d.p_idle_max) { + /* if idle time is high, clock down */ + scale3d.scale = 100 - (idleness - scale3d.p_idle_min); + schedule_work(&scale3d.work); + } else if (idleness < scale3d.p_idle_min) { + /* if idle time is low, clock up */ + scale3d.scale = 200; + schedule_work(&scale3d.work); + } + reset_scaling_counters(time); + } +} + +static void module3d_notify_idle(void) +{ + spin_lock(&scale3d.lock); + + if (!scale3d.enable) + goto done; + + scale3d.last_idle = ktime_get(); + scale3d.is_idle = 1; + + scaling_state_check(scale3d.last_idle); + +done: + spin_unlock(&scale3d.lock); +} + +void module3d_notify_busy(void) +{ + unsigned long idle; + ktime_t t; + + spin_lock(&scale3d.lock); + + if (!scale3d.enable) + goto done; + + t = ktime_get(); + + if (scale3d.is_idle) { + scale3d.last_busy = t; + idle = (unsigned long) + ktime_us_delta(scale3d.last_busy, scale3d.last_idle); + scale3d.idle_total += idle; + scale3d.is_idle = 0; + } + + scaling_state_check(t); + +done: + spin_unlock(&scale3d.lock); +} + +void scale3d_reset() +{ + ktime_t t = ktime_get(); + spin_lock(&scale3d.lock); + reset_scaling_counters(t); + spin_unlock(&scale3d.lock); +} + void nvhost_module_idle_mult(struct nvhost_module *mod, int refs) { bool kick = false; @@ -91,8 +310,12 @@ void nvhost_module_idle_mult(struct nvhost_module *mod, int refs) } mutex_unlock(&mod->lock); - if (kick) + if (kick) { wake_up(&mod->idle); + + if (strcmp(mod->name, "gr3d") == 0) + module3d_notify_idle(); + } } static const char *get_module_clk_id(const char *module, int index) @@ -273,6 +496,8 @@ int nvhost_module_init(struct nvhost_module *mod, const char *name, mod->powerdown_delay = ACM_POWERDOWN_HANDLER_DELAY_MSEC; if (strcmp(name, "gr3d") == 0) { + if (!scale3d.init) + scale3d_init(mod); mod->powergate_id = TEGRA_POWERGATE_3D; #ifdef CONFIG_ARCH_TEGRA_3x_SOC mod->powergate_id2 = TEGRA_POWERGATE_3D1; @@ -354,6 +579,8 @@ void nvhost_module_suspend(struct nvhost_module *mod, bool system_suspend) printk("tegra_grhost: entered idle\n"); flush_delayed_work(&mod->powerdown); + cancel_work_sync(&scale3d.work); + if (system_suspend) printk("tegra_grhost: flushed delayed work\n"); BUG_ON(mod->powered); @@ -362,7 +589,41 @@ void nvhost_module_suspend(struct nvhost_module *mod, bool system_suspend) void nvhost_module_deinit(struct nvhost_module *mod) { int i; + nvhost_module_suspend(mod, false); for (i = 0; i < mod->num_clks; i++) clk_put(mod->clk[i]); } + + +/* + * debugfs parameters to control 3d clock scaling + */ + +void nvhost_debug_scale_init(struct dentry *de) +{ + struct dentry *d, *f; + + d = debugfs_create_dir("scaling", de); + if (!d) { + pr_err("scale3d: can\'t create debugfs directory\n"); + return; + } + +#define CREATE_SCALE3D_FILE(fname) \ + do {\ + f = debugfs_create_u32(#fname, S_IRUGO | S_IWUSR, d,\ + &scale3d.p_##fname);\ + if (NULL == f) {\ + pr_err("scale3d: can\'t create file " #fname "\n");\ + return;\ + } \ + } while (0) + + CREATE_SCALE3D_FILE(fast_response); + CREATE_SCALE3D_FILE(idle_min); + CREATE_SCALE3D_FILE(idle_max); + CREATE_SCALE3D_FILE(period); + CREATE_SCALE3D_FILE(verbosity); +#undef CREATE_SCALE3D_FILE +} diff --git a/drivers/video/tegra/host/nvhost_acm.h b/drivers/video/tegra/host/nvhost_acm.h index 2efdadf29723..6f3011e343cd 100644 --- a/drivers/video/tegra/host/nvhost_acm.h +++ b/drivers/video/tegra/host/nvhost_acm.h @@ -87,4 +87,10 @@ static inline void nvhost_module_idle(struct nvhost_module *mod) nvhost_module_idle_mult(mod, 1); } +/* + * call when performing submit to notify scaling mechanism that 3d module is + * in use + */ +void module3d_notify_busy(void); + #endif diff --git a/drivers/video/tegra/host/nvhost_scale.h b/drivers/video/tegra/host/nvhost_scale.h new file mode 100644 index 000000000000..591a5fc04ed9 --- /dev/null +++ b/drivers/video/tegra/host/nvhost_scale.h @@ -0,0 +1,73 @@ +/* + * drivers/video/tegra/host/nvhost_scale.h + * + * Tegra Graphics Host 3D clock scaling + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __NVHOST_SCALE_H +#define __NVHOST_SCALE_H + +#include +#include +#include +#include + +/* + * debugfs parameters to control 3d clock scaling test + * + * period - time period for clock rate evaluation + * fast_response - time period for evaluation of 'busy' spikes + * idle_min - if less than [idle_min] percent idle over [fast_response] + * microseconds, clock up. + * idle_max - if over [idle_max] percent idle over [period] microseconds, + * clock down. + * max_scale - limits rate changes to no less than (100 - max_scale)% or + * (100 + 2 * max_scale)% of current clock rate + * verbosity - set above 5 for debug printouts + */ + +struct scale3d_info_rec { + spinlock_t lock; /* lock for timestamps etc */ + struct mutex set_lock; /* lock for clock setting */ + int enable; + int init; + ktime_t idle_frame; + ktime_t fast_frame; + ktime_t last_idle; + ktime_t last_busy; + int is_idle; + unsigned long idle_total; + struct work_struct work; + unsigned int scale; + unsigned int p_period; + unsigned int p_idle_min; + unsigned int p_idle_max; + unsigned int p_fast_response; + unsigned int p_verbosity; + struct clk *clk_3d; + struct clk *clk_3d2; +}; + +/* reset 3d module load counters, called on resume */ +void scale3d_reset(void); + +int scale3d_is_enabled(void); +void scale3d_enable(int enable); + +#endif /* __NVHOST_SCALE_H */ diff --git a/drivers/video/tegra/host/t20/channel_t20.c b/drivers/video/tegra/host/t20/channel_t20.c index b6a72e177005..480dacf374f6 100644 --- a/drivers/video/tegra/host/t20/channel_t20.c +++ b/drivers/video/tegra/host/t20/channel_t20.c @@ -171,6 +171,8 @@ static int t20_channel_submit(struct nvhost_channel *channel, /* keep module powered */ nvhost_module_busy(&channel->mod); + if (strcmp(channel->mod.name, "gr3d") == 0) + module3d_notify_busy(); /* get submit lock */ err = mutex_lock_interruptible(&channel->submitlock); @@ -320,6 +322,9 @@ static void power_3d(struct nvhost_module *mod, enum nvhost_power_action action) return; } + if (strcmp(mod->name, "gr3d") == 0) + module3d_notify_busy(); + hwctx_to_save->valid = true; ch->ctxhandler.get(hwctx_to_save); ch->cur_ctx = NULL; -- cgit v1.2.3