summaryrefslogtreecommitdiff
path: root/drivers/video/tegra/host/t30/scale3d.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/tegra/host/t30/scale3d.c')
-rw-r--r--drivers/video/tegra/host/t30/scale3d.c354
1 files changed, 354 insertions, 0 deletions
diff --git a/drivers/video/tegra/host/t30/scale3d.c b/drivers/video/tegra/host/t30/scale3d.c
new file mode 100644
index 000000000000..63d52abf2bdf
--- /dev/null
+++ b/drivers/video/tegra/host/t30/scale3d.c
@@ -0,0 +1,354 @@
+/*
+ * drivers/video/tegra/host/t20/scale3d.c
+ *
+ * Tegra Graphics Host 3D clock scaling
+ *
+ * Copyright (c) 2010-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.
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/types.h>
+#include <linux/clk.h>
+#include <mach/clk.h>
+#include <mach/hardware.h>
+#include "scale3d.h"
+#include "../dev.h"
+
+static int scale3d_is_enabled(void);
+static void scale3d_enable(int enable);
+
+/*
+ * 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 {
+ struct mutex lock; /* lock for timestamps etc */
+ 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;
+};
+
+static struct scale3d_info_rec scale3d;
+
+static void scale3d_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 scale3d_clocks_handler(struct work_struct *work)
+{
+ unsigned int scale;
+
+ mutex_lock(&scale3d.lock);
+ scale = scale3d.scale;
+ mutex_unlock(&scale3d.lock);
+
+ if (scale != 0)
+ scale3d_clocks(scale);
+}
+
+void nvhost_scale3d_suspend(struct nvhost_module *mod)
+{
+ cancel_work_sync(&scale3d.work);
+}
+
+/* set 3d clocks to max */
+static void reset_3d_clocks(void)
+{
+ unsigned long hz;
+
+ 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);
+}
+
+static int scale3d_is_enabled(void)
+{
+ int enable;
+
+ mutex_lock(&scale3d.lock);
+ enable = scale3d.enable;
+ mutex_unlock(&scale3d.lock);
+
+ return enable;
+}
+
+static void scale3d_enable(int enable)
+{
+ int disable = 0;
+
+ mutex_lock(&scale3d.lock);
+
+ if (enable)
+ scale3d.enable = 1;
+ else {
+ scale3d.enable = 0;
+ disable = 1;
+ }
+
+ mutex_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);
+ }
+}
+
+void nvhost_scale3d_notify_idle(struct nvhost_module *mod)
+{
+ mutex_lock(&scale3d.lock);
+
+ if (!scale3d.enable)
+ goto done;
+
+ scale3d.last_idle = ktime_get();
+ scale3d.is_idle = 1;
+
+ scaling_state_check(scale3d.last_idle);
+
+done:
+ mutex_unlock(&scale3d.lock);
+}
+
+void nvhost_scale3d_notify_busy(struct nvhost_module *mod)
+{
+ unsigned long idle;
+ ktime_t t;
+
+ mutex_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:
+ mutex_unlock(&scale3d.lock);
+}
+
+void nvhost_scale3d_reset()
+{
+ ktime_t t = ktime_get();
+ mutex_lock(&scale3d.lock);
+ reset_scaling_counters(t);
+ mutex_unlock(&scale3d.lock);
+}
+
+/*
+ * debugfs parameters to control 3d clock scaling
+ */
+
+void nvhost_scale3d_debug_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
+}
+
+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_scale3d_init(struct device *d, struct nvhost_module *mod)
+{
+ if (!scale3d.init) {
+ int error;
+ mutex_init(&scale3d.lock);
+
+ scale3d.clk_3d = mod->clk[0];
+ if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA3)
+ scale3d.clk_3d2 = mod->clk[1];
+
+ INIT_WORK(&scale3d.work, scale3d_clocks_handler);
+
+ /* set scaling parameter defaults */
+ scale3d.enable = 0;
+ scale3d.p_period = 1200000;
+ scale3d.p_idle_min = 17;
+ scale3d.p_idle_max = 17;
+ scale3d.p_fast_response = 16000;
+ scale3d.p_verbosity = 0;
+
+ error = device_create_file(d, &dev_attr_enable_3d_scaling);
+ if (error)
+ dev_err(d, "failed to create sysfs attributes");
+
+ scale3d.init = 1;
+ }
+
+ nvhost_scale3d_reset();
+}
+
+void nvhost_scale3d_deinit(struct device *dev, struct nvhost_module *mod)
+{
+ device_remove_file(dev, &dev_attr_enable_3d_scaling);
+ scale3d.init = 0;
+}