/*
* 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 .
*/
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
#include
#include
#else
#include
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0))
#include
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
#include
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
#include
#endif
#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;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0))
struct gpu_platform_data *pdata;
#else
struct clk *core_clk;
struct clk *gpu_clk;
struct clk *per_clk;
struct clk *gpu_core_clk;
struct clk *gpu_hyd_clk;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
struct regulator *gpu_reg;
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
struct notifier_block *clk_nb;
#endif
#endif
} 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) || 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 ************************/
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
static int set_volt_for_freq(unsigned long freq)
{
struct opp *opp;
unsigned long volt = 0;
int ret;
if (sfd.gpu_reg) {
opp = opp_find_freq_exact(sfd.dev, freq, true);
if(IS_ERR(opp))
{
int r = PTR_ERR(opp);
pr_err("sgxfreq: Couldn't find opp matching freq: %lu. Err: %d",
freq, r);
return -1;
}
volt = opp_get_voltage(opp);
if (!volt)
{
pr_err("sgxfreq: Could find volt corresponding to freq: %lu\n",
freq);
return -1;
}
ret = regulator_set_voltage_tol(sfd.gpu_reg, volt , 6000);
if (ret) {
pr_err("sgxfreq: Error(%d) setting volt: %lu for freq:%lu\n",
ret, volt, freq);
return ret;
}
}
return 0;
}
#endif
static int __set_freq(void)
{
unsigned long freq;
int ret = 0;
freq = min(sfd.freq_request, sfd.freq_limit);
if (freq != sfd.freq) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
if (freq > sfd.freq) {
/* Going up - must scale voltage before clocks */
if (set_volt_for_freq(freq) != 0) {
pr_err("sgxfreq: Error setting voltage for freq: %lu\n",
freq);
goto err1;
}
}
#endif
ret = clk_set_rate(sfd.gpu_core_clk, freq);
if (ret) {
pr_err("sgxfreq: Error(%d) setting gpu core clock rate: %lu\n",
ret, freq);
goto err2;
}
ret = clk_set_rate(sfd.gpu_hyd_clk, freq);
if (ret) {
pr_err("sgxfreq: Error(%d) setting gpu hyd clock rate: %lu\n",
ret, freq);
goto err3;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
if (freq < sfd.freq) {
/* Going down - must scale voltage after clocks */
if(set_volt_for_freq(freq) != 0) {
pr_err("sgxfreq: Error setting voltage for freq: %lu\n",
freq);
goto err4;
}
}
#endif
#elif (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;
goto noerr;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
err4:
#endif
ret |= clk_set_rate(sfd.gpu_hyd_clk, sfd.freq);
err3:
ret |= clk_set_rate(sfd.gpu_core_clk, sfd.freq);
err2:
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
if(freq > sfd.freq)
ret |= set_volt_for_freq(sfd.freq);
err1:
#endif
#endif
noerr:
return ret;
}
return ret;
}
static struct sgxfreq_governor *__find_governor(const char *name)
{
struct sgxfreq_governor *t;
list_for_each_entry(t, &sfd.gov_list, governor_list)
if (!strncasecmp(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;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
struct dev_pm_opp *opp;
struct device_node *np;
unsigned int voltage_latency;
#else
struct opp *opp;
#endif
struct timeval tv;
sfd.dev = dev;
if (!sfd.dev)
return -EINVAL;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0))
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;
#endif
rcu_read_lock();
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0))
sfd.freq_cnt = sfd.pdata->opp_get_opp_count(dev);
#else
ret = of_init_opp_table(dev);
if (ret) {
pr_err("sgxfreq: failed to init OPP table: %d\n", ret);
return -EINVAL;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
sfd.freq_cnt = opp_get_opp_count(dev);
#else
sfd.freq_cnt = dev_pm_opp_get_opp_count(dev);
#endif
#endif
if (sfd.freq_cnt < 1) {
pr_err("sgxfreq: failed to get operating frequencies\n");
rcu_read_unlock();
return -ENODEV;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
np = of_node_get(dev->of_node);
sfd.clk_nb = of_pm_voltdm_notifier_register(dev, np, sfd.gpu_core_clk, "gpu",
&voltage_latency);
if (IS_ERR(sfd.clk_nb)) {
ret = PTR_ERR(sfd.clk_nb);
/* defer probe if regulator is not yet registered */
if (ret == -EPROBE_DEFER) {
dev_err(dev,
"gpu clock notifier not ready, retry\n");
} else {
dev_err(dev,
"Failed to register gpu clock notifier: %d\n",
ret);
}
return ret;
}
#endif
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++) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0))
opp = sfd.pdata->opp_find_freq_ceil(dev, &freq);
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
opp = opp_find_freq_ceil(dev, &freq);
#else
/* 3.14 and later kernels */
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
#endif
if (IS_ERR_OR_NULL(opp)) {
rcu_read_unlock();
kfree(sfd.freq_list);
return -ENODEV;
}
sfd.freq_list[i] = freq;
freq++;
}
rcu_read_unlock();
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
sfd.core_clk = devm_clk_get(dev, "dpll_core_h14x2_ck");
if (IS_ERR(sfd.core_clk)) {
ret = PTR_ERR(sfd.core_clk);
pr_err("sgxfreq: failed to get core clock: %d\n", ret);
return ret;
}
sfd.gpu_clk = devm_clk_get(dev, "dpll_gpu_m2_ck");
if (IS_ERR(sfd.gpu_clk)) {
ret = PTR_ERR(sfd.gpu_clk);
pr_err("sgxfreq: failed to get gpu clock: %d\n", ret);
return ret;
}
sfd.per_clk = devm_clk_get(dev, "dpll_per_h14x2_ck");
if (IS_ERR(sfd.per_clk)) {
ret = PTR_ERR(sfd.per_clk);
pr_err("sgxfreq: failed to get per clock: %d\n", ret);
return ret;
}
sfd.gpu_core_clk = devm_clk_get(dev, "gpu_core_gclk_mux");
if (IS_ERR(sfd.gpu_core_clk)) {
ret = PTR_ERR(sfd.gpu_core_clk);
pr_err("sgxfreq: failed to get gpu core clock: %d\n", ret);
return ret;
}
sfd.gpu_hyd_clk = devm_clk_get(dev, "gpu_core_gclk_mux");
if (IS_ERR(sfd.gpu_hyd_clk)) {
ret = PTR_ERR(sfd.gpu_hyd_clk);
pr_err("sgxfreq: failed to get gpu hyd clock: %d\n", ret);
return ret;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
sfd.gpu_reg = devm_regulator_get(dev, "gpu");
if (IS_ERR(sfd.gpu_reg)) {
if (PTR_ERR(sfd.gpu_reg) == -EPROBE_DEFER) {
dev_err(dev, "gpu regulator not ready, retry\n");
return -EPROBE_DEFER;
}
pr_err("sgxfreq: failed to get gpu regulator: %ld\n", PTR_ERR(sfd.gpu_reg));
sfd.gpu_reg = NULL;
}
#endif
ret = clk_set_parent(sfd.gpu_hyd_clk, sfd.core_clk);
if (ret != 0) {
pr_err("sgxfreq: failed to set gpu_hyd_clk parent: %d\n", ret);
}
ret = clk_set_parent(sfd.gpu_core_clk, sfd.core_clk);
if (ret != 0) {
pr_err("sgxfreq: failed to set gpu_core_clk parent: %d\n", ret);
}
#endif
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) || 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) || 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);
}