/*
* 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 .
*/
#include
#include
#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);
}