diff options
Diffstat (limited to 'pvr-source/services4/system/omap4/sgxfreq.c')
-rw-r--r-- | pvr-source/services4/system/omap4/sgxfreq.c | 590 |
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); +} |