/*
* 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
#include "sgxfreq.h"
static int on3demand_start(struct sgxfreq_sgx_data *data);
static void on3demand_stop(void);
static void on3demand_predict(void);
static struct sgxfreq_governor on3demand_gov = {
.name = "on3demand",
.gov_start = on3demand_start,
.gov_stop = on3demand_stop,
.sgx_frame_done = on3demand_predict
};
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;
struct mutex mutex;
} odd;
#define ON3DEMAND_DEFAULT_UP_THRESHOLD 80
#define ON3DEMAND_DEFAULT_DOWN_THRESHOLD 30
#define ON3DEMAND_DEFAULT_HISTORY_SIZE_THRESHOLD 5
/*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;
ret = sysfs_create_group(sgxfreq_kobj, &on3demand_attr_group);
if (ret)
return ret;
return 0;
}
static void on3demand_stop(void)
{
sysfs_remove_group(sgxfreq_kobj, &on3demand_attr_group);
}
static void on3demand_predict(void)
{
static unsigned short first_sample = 1;
unsigned long total_active, delta_active;
unsigned long total_idle, delta_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 */
delta_active = __delta32(total_active, odd.prev_total_active);
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 (delta_active > ON3DEMAND_FRAME_DONE_DEADLINE_MS) {
odd.low_load_cnt = 0;
sgxfreq_set_freq_request(sgxfreq_get_freq_max());
}
if ((delta_active + delta_idle))
odd.load = (100 * delta_active / (delta_active + 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;
}
}