summaryrefslogtreecommitdiffstats
path: root/pvr-source/services4/system/omap4/sgxfreq.c
diff options
context:
space:
mode:
Diffstat (limited to 'pvr-source/services4/system/omap4/sgxfreq.c')
-rw-r--r--pvr-source/services4/system/omap4/sgxfreq.c590
1 files changed, 590 insertions, 0 deletions
diff --git a/pvr-source/services4/system/omap4/sgxfreq.c b/pvr-source/services4/system/omap4/sgxfreq.c
new file mode 100644
index 0000000..7e8e8fd
--- /dev/null
+++ b/pvr-source/services4/system/omap4/sgxfreq.c
@@ -0,0 +1,590 @@
+/*
+ * 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/opp.h>
+#include <plat/gpu.h>
+#include "sgxfreq.h"
+
+static struct sgxfreq_data {
+ int freq_cnt;
+ unsigned long *freq_list;
+ unsigned long freq;
+ unsigned long freq_request;
+ unsigned long freq_limit;
+ unsigned long total_idle_time;
+ unsigned long total_active_time;
+ struct mutex freq_mutex;
+ struct list_head gov_list;
+ struct sgxfreq_governor *gov;
+ struct mutex gov_mutex;
+ struct sgxfreq_sgx_data sgx_data;
+ struct device *dev;
+ struct gpu_platform_data *pdata;
+} sfd;
+
+/* Governor init/deinit functions */
+int onoff_init(void);
+int onoff_deinit(void);
+int activeidle_init(void);
+int activeidle_deinit(void);
+int on3demand_init(void);
+int on3demand_deinit(void);
+int userspace_init(void);
+int userspace_deinit(void);
+
+
+typedef int sgxfreq_gov_init_t(void);
+sgxfreq_gov_init_t *sgxfreq_gov_init[] = {
+ onoff_init,
+ activeidle_init,
+ on3demand_init,
+ userspace_init,
+ NULL,
+};
+
+typedef int sgxfreq_gov_deinit_t(void);
+sgxfreq_gov_deinit_t *sgxfreq_gov_deinit[] = {
+ onoff_deinit,
+ activeidle_deinit,
+ on3demand_deinit,
+ userspace_deinit,
+ NULL,
+};
+
+#define SGXFREQ_DEFAULT_GOV_NAME "on3demand"
+static unsigned long _idle_curr_time;
+static unsigned long _idle_prev_time;
+static unsigned long _active_curr_time;
+static unsigned long _active_prev_time;
+
+#if defined(CONFIG_THERMAL_FRAMEWORK)
+int cool_init(void);
+void cool_deinit(void);
+#endif
+
+/*********************** begin sysfs interface ***********************/
+
+struct kobject *sgxfreq_kobj;
+
+static ssize_t show_frequency_list(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+ ssize_t count = 0;
+
+ for (i = 0; i < sfd.freq_cnt; i++)
+ count += sprintf(&buf[count], "%lu ", sfd.freq_list[i]);
+ count += sprintf(&buf[count], "\n");
+
+ return count;
+}
+
+static ssize_t show_frequency_request(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%lu\n", sfd.freq_request);
+}
+
+static ssize_t show_frequency_limit(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%lu\n", sfd.freq_limit);
+}
+
+static ssize_t show_frequency(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%lu\n", sfd.freq);
+}
+
+static ssize_t show_stat(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "gpu %lu %lu\n",
+ sfd.total_active_time, sfd.total_idle_time);
+}
+
+static ssize_t show_governor_list(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t i = 0;
+ struct sgxfreq_governor *t;
+
+ list_for_each_entry(t, &sfd.gov_list, governor_list) {
+ if (i >= (ssize_t) ((PAGE_SIZE / sizeof(char))
+ - (SGXFREQ_NAME_LEN + 2)))
+ goto out;
+ i += scnprintf(&buf[i], SGXFREQ_NAME_LEN, "%s ", t->name);
+ }
+out:
+ i += sprintf(&buf[i], "\n");
+ return i;
+}
+
+static ssize_t show_governor(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (sfd.gov)
+ return scnprintf(buf, SGXFREQ_NAME_LEN, "%s\n", sfd.gov->name);
+
+ return sprintf(buf, "\n");
+}
+
+static ssize_t store_governor(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+ char name[16];
+
+ ret = sscanf(buf, "%15s", name);
+ if (ret != 1)
+ return -EINVAL;
+
+ ret = sgxfreq_set_governor(name);
+ if (ret)
+ return ret;
+ else
+ return count;
+}
+
+static DEVICE_ATTR(frequency_list, 0444, show_frequency_list, NULL);
+static DEVICE_ATTR(frequency_request, 0444, show_frequency_request, NULL);
+static DEVICE_ATTR(frequency_limit, 0444, show_frequency_limit, NULL);
+static DEVICE_ATTR(frequency, 0444, show_frequency, NULL);
+static DEVICE_ATTR(governor_list, 0444, show_governor_list, NULL);
+static DEVICE_ATTR(governor, 0644, show_governor, store_governor);
+static DEVICE_ATTR(stat, 0444, show_stat, NULL);
+
+static const struct attribute *sgxfreq_attributes[] = {
+ &dev_attr_frequency_list.attr,
+ &dev_attr_frequency_request.attr,
+ &dev_attr_frequency_limit.attr,
+ &dev_attr_frequency.attr,
+ &dev_attr_governor_list.attr,
+ &dev_attr_governor.attr,
+ &dev_attr_stat.attr,
+ NULL
+};
+
+/************************ end sysfs interface ************************/
+
+static void __set_freq(void)
+{
+ unsigned long freq;
+
+ freq = min(sfd.freq_request, sfd.freq_limit);
+ if (freq != sfd.freq) {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,0))
+ sfd.pdata->device_scale(sfd.dev, sfd.dev, freq);
+#else
+ sfd.pdata->device_scale(sfd.dev, freq);
+#endif
+ sfd.freq = freq;
+ }
+}
+
+static struct sgxfreq_governor *__find_governor(const char *name)
+{
+ struct sgxfreq_governor *t;
+
+ list_for_each_entry(t, &sfd.gov_list, governor_list)
+ if (!strnicmp(name, t->name, SGXFREQ_NAME_LEN))
+ return t;
+
+ return NULL;
+}
+
+static void __update_timing_info(bool active)
+{
+ struct timeval tv;
+ do_gettimeofday(&tv);
+ if(active)
+ {
+ if(sfd.sgx_data.active == true) {
+ _active_curr_time = __tv2msec(tv);
+ sfd.total_active_time += __delta32(
+ _active_curr_time, _active_prev_time);
+ SGXFREQ_TRACE("A->A TA:= %lums \tdA: %lums \tTI: %lums \tdI: %lums\n",
+ sfd.total_active_time,
+ __delta32(_active_curr_time, _active_prev_time),
+ sfd.total_active_time,
+ (unsigned long)0);
+ _active_prev_time = _active_curr_time;
+ } else {
+ _idle_curr_time = __tv2msec(tv);
+ _active_prev_time = _idle_curr_time;
+ sfd.total_idle_time +=
+ __delta32(_idle_curr_time, _idle_prev_time);
+ SGXFREQ_TRACE("I->A TA:= %lums \tdA: %lums \tTI: %lums \tdI: %lums\n",
+ sfd.total_active_time,
+ (unsigned long)0,
+ sfd.total_idle_time,
+ __delta32(_idle_curr_time, _idle_prev_time));
+ }
+ } else {
+ if(sfd.sgx_data.active == true)
+ {
+ _idle_prev_time = _active_curr_time = __tv2msec(tv);
+ sfd.total_active_time +=
+ __delta32(_active_curr_time, _active_prev_time);
+ SGXFREQ_TRACE("A->I TA:= %lums \tdA: %lums \tTI: %lums \tdI: %lums\n",
+ sfd.total_active_time,
+ __delta32(_active_curr_time, _active_prev_time),
+ sfd.total_active_time,
+ (unsigned long)0);
+ }
+ else
+ {
+ _idle_curr_time = __tv2msec(tv);
+ sfd.total_idle_time += __delta32(
+ _idle_curr_time, _idle_prev_time);
+ SGXFREQ_TRACE("I->I TA:= %lums \tdA: %lums \tTI: %lums \tdI: %lums\n",
+ sfd.total_active_time,
+ (unsigned long)0,
+ sfd.total_idle_time,
+ __delta32(_idle_curr_time, _idle_prev_time));
+ _idle_prev_time = _idle_curr_time;
+ }
+ }
+}
+
+int sgxfreq_init(struct device *dev)
+{
+ int i, ret;
+ unsigned long freq;
+ struct opp *opp;
+ struct timeval tv;
+
+ sfd.dev = dev;
+ if (!sfd.dev)
+ return -EINVAL;
+
+ sfd.pdata = (struct gpu_platform_data *)dev->platform_data;
+ if (!sfd.pdata ||
+ !sfd.pdata->opp_get_opp_count ||
+ !sfd.pdata->opp_find_freq_ceil ||
+ !sfd.pdata->device_scale)
+ return -EINVAL;
+
+ rcu_read_lock();
+
+ sfd.freq_cnt = sfd.pdata->opp_get_opp_count(dev);
+ if (sfd.freq_cnt < 1) {
+ rcu_read_unlock();
+ return -ENODEV;
+ }
+
+ sfd.freq_list = kmalloc(sfd.freq_cnt * sizeof(unsigned long), GFP_ATOMIC);
+ if (!sfd.freq_list) {
+ rcu_read_unlock();
+ return -ENOMEM;
+ }
+
+ freq = 0;
+ for (i = 0; i < sfd.freq_cnt; i++) {
+ opp = sfd.pdata->opp_find_freq_ceil(dev, &freq);
+ if (IS_ERR_OR_NULL(opp)) {
+ rcu_read_unlock();
+ kfree(sfd.freq_list);
+ return -ENODEV;
+ }
+ sfd.freq_list[i] = freq;
+ freq++;
+ }
+ rcu_read_unlock();
+
+ mutex_init(&sfd.freq_mutex);
+ sfd.freq_limit = sfd.freq_list[sfd.freq_cnt - 1];
+ sgxfreq_set_freq_request(sfd.freq_list[sfd.freq_cnt - 1]);
+ sfd.sgx_data.clk_on = false;
+ sfd.sgx_data.active = false;
+
+ mutex_init(&sfd.gov_mutex);
+ INIT_LIST_HEAD(&sfd.gov_list);
+
+ sgxfreq_kobj = kobject_create_and_add("sgxfreq", &sfd.dev->kobj);
+ ret = sysfs_create_files(sgxfreq_kobj, sgxfreq_attributes);
+ if (ret) {
+ kfree(sfd.freq_list);
+ return ret;
+ }
+
+#if defined(CONFIG_THERMAL_FRAMEWORK)
+ cool_init();
+#endif
+
+ for (i = 0; sgxfreq_gov_init[i] != NULL; i++)
+ sgxfreq_gov_init[i]();
+
+ if (sgxfreq_set_governor(SGXFREQ_DEFAULT_GOV_NAME)) {
+ kfree(sfd.freq_list);
+ return -ENODEV;
+ }
+ do_gettimeofday(&tv);
+ _idle_prev_time = _active_curr_time = _idle_curr_time =
+ _active_prev_time = __tv2msec(tv);
+
+ return 0;
+}
+
+int sgxfreq_deinit(void)
+{
+ int i;
+
+ sgxfreq_set_governor(NULL);
+
+ sgxfreq_set_freq_request(sfd.freq_list[0]);
+
+#if defined(CONFIG_THERMAL_FRAMEWORK)
+ cool_deinit();
+#endif
+
+ for (i = 0; sgxfreq_gov_deinit[i] != NULL; i++)
+ sgxfreq_gov_deinit[i]();
+
+ sysfs_remove_files(sgxfreq_kobj, sgxfreq_attributes);
+ kobject_put(sgxfreq_kobj);
+
+ kfree(sfd.freq_list);
+
+ return 0;
+}
+
+int sgxfreq_register_governor(struct sgxfreq_governor *governor)
+{
+ if (!governor)
+ return -EINVAL;
+
+ list_add(&governor->governor_list, &sfd.gov_list);
+
+ return 0;
+}
+
+void sgxfreq_unregister_governor(struct sgxfreq_governor *governor)
+{
+ if (!governor)
+ return;
+
+ list_del(&governor->governor_list);
+}
+
+int sgxfreq_set_governor(const char *name)
+{
+ int ret = 0;
+ struct sgxfreq_governor *new_gov = 0;
+
+ if (name) {
+ new_gov = __find_governor(name);
+ if (!new_gov)
+ return -EINVAL;
+ }
+
+ mutex_lock(&sfd.gov_mutex);
+
+ if (sfd.gov && sfd.gov->gov_stop)
+ sfd.gov->gov_stop();
+
+ if (new_gov && new_gov->gov_start)
+ ret = new_gov->gov_start(&sfd.sgx_data);
+
+ if (ret) {
+ if (sfd.gov && sfd.gov->gov_start)
+ sfd.gov->gov_start(&sfd.sgx_data);
+ return -ENODEV;
+ }
+ sfd.gov = new_gov;
+
+ mutex_unlock(&sfd.gov_mutex);
+
+ return 0;
+}
+
+int sgxfreq_get_freq_list(unsigned long **pfreq_list)
+{
+ *pfreq_list = sfd.freq_list;
+
+ return sfd.freq_cnt;
+}
+
+unsigned long sgxfreq_get_freq_min(void)
+{
+ return sfd.freq_list[0];
+}
+
+unsigned long sgxfreq_get_freq_max(void)
+{
+ return sfd.freq_list[sfd.freq_cnt - 1];
+}
+
+unsigned long sgxfreq_get_freq_floor(unsigned long freq)
+{
+ int i;
+ unsigned long f = 0;
+
+ for (i = sfd.freq_cnt - 1; i >= 0; i--) {
+ f = sfd.freq_list[i];
+ if (f <= freq)
+ return f;
+ }
+
+ return f;
+}
+
+unsigned long sgxfreq_get_freq_ceil(unsigned long freq)
+{
+ int i;
+ unsigned long f = 0;
+
+ for (i = 0; i < sfd.freq_cnt; i++) {
+ f = sfd.freq_list[i];
+ if (f >= freq)
+ return f;
+ }
+
+ return f;
+}
+
+unsigned long sgxfreq_get_freq(void)
+{
+ return sfd.freq;
+}
+
+unsigned long sgxfreq_get_freq_request(void)
+{
+ return sfd.freq_request;
+}
+
+unsigned long sgxfreq_get_freq_limit(void)
+{
+ return sfd.freq_limit;
+}
+
+unsigned long sgxfreq_set_freq_request(unsigned long freq_request)
+{
+ freq_request = sgxfreq_get_freq_ceil(freq_request);
+
+ mutex_lock(&sfd.freq_mutex);
+
+ sfd.freq_request = freq_request;
+ __set_freq();
+
+ mutex_unlock(&sfd.freq_mutex);
+
+ return freq_request;
+}
+
+unsigned long sgxfreq_set_freq_limit(unsigned long freq_limit)
+{
+ freq_limit = sgxfreq_get_freq_ceil(freq_limit);
+
+ mutex_lock(&sfd.freq_mutex);
+
+ sfd.freq_limit = freq_limit;
+ __set_freq();
+
+ mutex_unlock(&sfd.freq_mutex);
+
+ return freq_limit;
+}
+
+unsigned long sgxfreq_get_total_active_time(void)
+{
+ __update_timing_info(sfd.sgx_data.active);
+ return sfd.total_active_time;
+}
+
+unsigned long sgxfreq_get_total_idle_time(void)
+{
+ __update_timing_info(sfd.sgx_data.active);
+ return sfd.total_idle_time;
+}
+
+/*
+ * sgx_clk_on, sgx_clk_off, sgx_active, and sgx_idle notifications are
+ * serialized by power lock. governor notif calls need sync with governor
+ * setting.
+ */
+void sgxfreq_notif_sgx_clk_on(void)
+{
+ sfd.sgx_data.clk_on = true;
+
+ mutex_lock(&sfd.gov_mutex);
+
+ if (sfd.gov && sfd.gov->sgx_clk_on)
+ sfd.gov->sgx_clk_on();
+
+ mutex_unlock(&sfd.gov_mutex);
+}
+
+void sgxfreq_notif_sgx_clk_off(void)
+{
+ sfd.sgx_data.clk_on = false;
+
+ mutex_lock(&sfd.gov_mutex);
+
+ if (sfd.gov && sfd.gov->sgx_clk_off)
+ sfd.gov->sgx_clk_off();
+
+ mutex_unlock(&sfd.gov_mutex);
+}
+
+
+void sgxfreq_notif_sgx_active(void)
+{
+ __update_timing_info(true);
+
+ sfd.sgx_data.active = true;
+
+ mutex_lock(&sfd.gov_mutex);
+
+ if (sfd.gov && sfd.gov->sgx_active)
+ sfd.gov->sgx_active();
+
+ mutex_unlock(&sfd.gov_mutex);
+
+}
+
+void sgxfreq_notif_sgx_idle(void)
+{
+
+ __update_timing_info(false);
+
+ sfd.sgx_data.active = false;
+
+ mutex_lock(&sfd.gov_mutex);
+
+ if (sfd.gov && sfd.gov->sgx_idle)
+ sfd.gov->sgx_idle();
+
+ mutex_unlock(&sfd.gov_mutex);
+}
+
+void sgxfreq_notif_sgx_frame_done(void)
+{
+ mutex_lock(&sfd.gov_mutex);
+
+ if (sfd.gov && sfd.gov->sgx_frame_done)
+ sfd.gov->sgx_frame_done();
+
+ mutex_unlock(&sfd.gov_mutex);
+}