summaryrefslogtreecommitdiffstats
path: root/pvr-source/services4/system/omap4/sgxfreq_on3demand.c
diff options
context:
space:
mode:
Diffstat (limited to 'pvr-source/services4/system/omap4/sgxfreq_on3demand.c')
-rw-r--r--pvr-source/services4/system/omap4/sgxfreq_on3demand.c324
1 files changed, 324 insertions, 0 deletions
diff --git a/pvr-source/services4/system/omap4/sgxfreq_on3demand.c b/pvr-source/services4/system/omap4/sgxfreq_on3demand.c
new file mode 100644
index 0000000..c4e4bd9
--- /dev/null
+++ b/pvr-source/services4/system/omap4/sgxfreq_on3demand.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2012 Texas Instruments, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/sysfs.h>
+#include <linux/vmalloc.h>
+#include <asm/io.h>
+#include "sgxfreq.h"
+
+static int on3demand_start(struct sgxfreq_sgx_data *data);
+static void on3demand_stop(void);
+static void on3demand_predict(void);
+static void on3demand_frame_done(void);
+static void on3demand_active(void);
+static void on3demand_timeout(struct work_struct *work);
+
+
+static struct sgxfreq_governor on3demand_gov = {
+ .name = "on3demand",
+ .gov_start = on3demand_start,
+ .gov_stop = on3demand_stop,
+ .sgx_frame_done = on3demand_frame_done,
+ .sgx_active = on3demand_active,
+};
+
+static struct on3demand_data {
+ unsigned int load;
+ unsigned int up_threshold;
+ unsigned int down_threshold;
+ unsigned int history_size;
+ unsigned long prev_total_idle;
+ unsigned long prev_total_active;
+ unsigned int low_load_cnt;
+ unsigned int poll_interval;
+ unsigned long delta_active;
+ unsigned long delta_idle;
+ bool polling_enabled;
+ struct delayed_work work;
+ struct mutex mutex;
+} odd;
+
+#define ON3DEMAND_DEFAULT_UP_THRESHOLD 80
+#define ON3DEMAND_DEFAULT_DOWN_THRESHOLD 30
+#define ON3DEMAND_DEFAULT_HISTORY_SIZE_THRESHOLD 5
+/* For Live wallpaper frame done at interval of ~64ms */
+#define ON3DEMAND_DEFAULT_POLL_INTERVAL 75
+
+/*FIXME: This should be dynamic and queried from platform */
+#define ON3DEMAND_FRAME_DONE_DEADLINE_MS 16
+
+
+/*********************** begin sysfs interface ***********************/
+
+extern struct kobject *sgxfreq_kobj;
+
+static ssize_t show_down_threshold(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", odd.down_threshold);
+}
+
+static ssize_t store_down_threshold(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ unsigned int thres;
+
+ ret = sscanf(buf, "%u", &thres);
+ if (ret != 1)
+ return -EINVAL;
+
+ mutex_lock(&odd.mutex);
+
+ if (thres <= 100) {
+ odd.down_threshold = thres;
+ odd.low_load_cnt = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ mutex_unlock(&odd.mutex);
+
+ return count;
+}
+
+static ssize_t show_up_threshold(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", odd.up_threshold);
+}
+
+static ssize_t store_up_threshold(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ unsigned int thres;
+
+ ret = sscanf(buf, "%u", &thres);
+ if (ret != 1)
+ return -EINVAL;
+
+ mutex_lock(&odd.mutex);
+
+ if (thres <= 100) {
+ odd.up_threshold = thres;
+ odd.low_load_cnt = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ mutex_unlock(&odd.mutex);
+
+ return count;
+}
+
+static ssize_t show_history_size(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", odd.history_size);
+}
+
+static ssize_t store_history_size(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ unsigned int size;
+
+ ret = sscanf(buf, "%u", &size);
+ if (ret != 1)
+ return -EINVAL;
+
+ mutex_lock(&odd.mutex);
+
+ if (size >= 1) {
+ odd.history_size = size;
+ odd.low_load_cnt = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ mutex_unlock(&odd.mutex);
+
+ return count;
+}
+
+static ssize_t show_load(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", odd.load);
+}
+
+static DEVICE_ATTR(down_threshold, 0644,
+ show_down_threshold, store_down_threshold);
+static DEVICE_ATTR(up_threshold, 0644,
+ show_up_threshold, store_up_threshold);
+static DEVICE_ATTR(history_size, 0644,
+ show_history_size, store_history_size);
+static DEVICE_ATTR(load, 0444,
+ show_load, NULL);
+
+static struct attribute *on3demand_attributes[] = {
+ &dev_attr_down_threshold.attr,
+ &dev_attr_up_threshold.attr,
+ &dev_attr_history_size.attr,
+ &dev_attr_load.attr,
+ NULL
+};
+
+static struct attribute_group on3demand_attr_group = {
+ .attrs = on3demand_attributes,
+ .name = "on3demand",
+};
+/************************ end sysfs interface ************************/
+
+int on3demand_init(void)
+{
+ int ret;
+
+ mutex_init(&odd.mutex);
+
+ ret = sgxfreq_register_governor(&on3demand_gov);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int on3demand_deinit(void)
+{
+ return 0;
+}
+
+static int on3demand_start(struct sgxfreq_sgx_data *data)
+{
+ int ret;
+
+ odd.load = 0;
+ odd.up_threshold = ON3DEMAND_DEFAULT_UP_THRESHOLD;
+ odd.down_threshold = ON3DEMAND_DEFAULT_DOWN_THRESHOLD;
+ odd.history_size = ON3DEMAND_DEFAULT_HISTORY_SIZE_THRESHOLD;
+ odd.prev_total_active = 0;
+ odd.prev_total_idle = 0;
+ odd.low_load_cnt = 0;
+ odd.poll_interval = ON3DEMAND_DEFAULT_POLL_INTERVAL;
+ odd.polling_enabled = false;
+
+ INIT_DELAYED_WORK(&odd.work, on3demand_timeout);
+
+ ret = sysfs_create_group(sgxfreq_kobj, &on3demand_attr_group);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void on3demand_stop(void)
+{
+ cancel_delayed_work_sync(&odd.work);
+ sysfs_remove_group(sgxfreq_kobj, &on3demand_attr_group);
+}
+
+static void on3demand_predict(void)
+{
+ static unsigned short first_sample = 1;
+ unsigned long total_active, total_idle;
+ unsigned long freq;
+
+ if (first_sample == 1) {
+ first_sample = 0;
+ odd.prev_total_active = sgxfreq_get_total_active_time();
+ odd.prev_total_idle = sgxfreq_get_total_idle_time();
+ return;
+ }
+
+ /* Sample new active and idle times */
+ total_active = sgxfreq_get_total_active_time();
+ total_idle = sgxfreq_get_total_idle_time();
+
+ /* Compute load */
+ odd.delta_active = __delta32(total_active, odd.prev_total_active);
+ odd.delta_idle = __delta32(total_idle, odd.prev_total_idle);
+
+ /*
+ * If SGX was active for longer than frame display time (1/fps),
+ * scale to highest possible frequency.
+ */
+ if (odd.delta_active > ON3DEMAND_FRAME_DONE_DEADLINE_MS) {
+ odd.low_load_cnt = 0;
+ sgxfreq_set_freq_request(sgxfreq_get_freq_max());
+ }
+
+ if ((odd.delta_active + odd.delta_idle))
+ odd.load = (100 * odd.delta_active / (odd.delta_active + odd.delta_idle));
+
+ odd.prev_total_active = total_active;
+ odd.prev_total_idle = total_idle;
+
+ /* Scale GPU frequency on purpose */
+ if (odd.load >= odd.up_threshold) {
+ odd.low_load_cnt = 0;
+ sgxfreq_set_freq_request(sgxfreq_get_freq_max());
+ } else if (odd.load <= odd.down_threshold) {
+ if (odd.low_load_cnt == odd.history_size) {
+ /* Convert load to frequency */
+ freq = (sgxfreq_get_freq() * odd.load) / 100;
+ sgxfreq_set_freq_request(freq);
+ odd.low_load_cnt = 0;
+ } else {
+ odd.low_load_cnt++;
+ }
+ } else {
+ odd.low_load_cnt = 0;
+ }
+}
+
+
+static void on3demand_active(void)
+{
+ if (!odd.polling_enabled) {
+ sgxfreq_set_freq_request(sgxfreq_get_freq_max());
+ odd.low_load_cnt = 0;
+ odd.polling_enabled = true;
+ schedule_delayed_work(&odd.work, odd.poll_interval * HZ/1000);
+ }
+
+}
+
+static void on3demand_frame_done(void)
+{
+ if (odd.polling_enabled) {
+ cancel_delayed_work_sync(&odd.work);
+ schedule_delayed_work(&odd.work, odd.poll_interval * HZ/1000);
+ }
+ on3demand_predict();
+}
+
+static void on3demand_timeout(struct work_struct *work)
+{
+ /*
+ * If sgx was idle all throughout timer disable polling and
+ * enable it on next sgx active event
+ */
+ if (!odd.delta_active) {
+ sgxfreq_set_freq_request(sgxfreq_get_freq_min());
+ odd.low_load_cnt = 0;
+ odd.polling_enabled = false;
+ } else {
+ on3demand_predict();
+ odd.polling_enabled = true;
+ schedule_delayed_work(&odd.work, odd.poll_interval * HZ/1000);
+ }
+}