summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlan Aelion <iaelion@nvidia.com>2011-07-15 12:05:01 -0600
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:48:07 -0800
commit76635927ce9e4b972f159016b094515f895e6d9a (patch)
treee9921a3200964511fa946f23996f2f6d3433f2e3
parentff11e42b48e8743546ca3ceecb7865e748f729db (diff)
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 <iaelion@nvidia.com> Reviewed-by: Krishna Reddy <vdumpa@nvidia.com> Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com> Rebase-Id: R64b86250fce24e3b43d70ffd316e9de518eb24ba
-rw-r--r--drivers/video/tegra/host/debug.c2
-rw-r--r--drivers/video/tegra/host/debug.h3
-rw-r--r--drivers/video/tegra/host/dev.c46
-rw-r--r--drivers/video/tegra/host/nvhost_acm.c263
-rw-r--r--drivers/video/tegra/host/nvhost_acm.h6
-rw-r--r--drivers/video/tegra/host/nvhost_scale.h73
-rw-r--r--drivers/video/tegra/host/t20/channel_t20.c5
7 files changed, 397 insertions, 1 deletions
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 <linux/debugfs.h>
+
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 <mach/nvmap.h>
#include <mach/gpufuse.h>
+#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 <mach/powergate.h>
#include <mach/clk.h>
#include <mach/hardware.h>
+#include <linux/debugfs.h>
+#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 <mach/clk.h>
+#include <linux/hrtimer.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+
+/*
+ * 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;