msm: kgsl: convert pwrscale framework to use devfreq

Convert the clock frequency scaling infrastructure to
be based on devfreq.

Change-Id: I1a60ba339db5715a8836b835bd1b29b46e151af6
Signed-off-by: Jeremy Gebben <jgebben@codeaurora.org>
Signed-off-by: Vladimir Razgulin <vrazguli@codeaurora.org>
This commit is contained in:
Jeremy Gebben
2013-11-22 22:00:32 -07:00
committed by Lucille Sylvester
parent dfd2ca740d
commit f84c799de3
16 changed files with 542 additions and 901 deletions
+13
View File
@@ -4,6 +4,9 @@ config MSM_KGSL
depends on ARCH_MSM && !ARCH_MSM7X00A && !ARCH_MSM7X25
select GENERIC_ALLOCATOR
select FW_LOADER
select PM_DEVFREQ
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select DEVFREQ_GOV_PERFORMANCE
---help---
3D graphics driver. Required to use hardware accelerated
OpenGL ES 2.0 and 1.1.
@@ -60,6 +63,16 @@ config MSM_KGSL_2D
default y
depends on MSM_KGSL && !ARCH_MSM7X27 && !ARCH_MSM7X27A && !(ARCH_QSD8X50 && !MSM_SOC_REV_A)
config MSM_ADRENO_DEFAULT_GOVERNOR
string "devfreq governor for the adreno core"
default "simple_ondemand"
depends on MSM_KGSL
config MSM_Z180_DEFAULT_GOVERNOR
string "devfreq governor for the z180 core(s)"
default "performance"
depends on MSM_KGSL_2D
config MSM_KGSL_DRM
bool "Build a DRM interface for the MSM_KGSL driver"
depends on MSM_KGSL && DRM
-2
View File
@@ -15,8 +15,6 @@ msm_kgsl_core-y = \
msm_kgsl_core-$(CONFIG_DEBUG_FS) += kgsl_debugfs.o
msm_kgsl_core-$(CONFIG_MSM_KGSL_CFF_DUMP) += kgsl_cffdump.o
msm_kgsl_core-$(CONFIG_MSM_KGSL_DRM) += kgsl_drm.o
msm_kgsl_core-$(CONFIG_MSM_SCM) += kgsl_pwrscale_trustzone.o
msm_kgsl_core-$(CONFIG_MSM_SLEEP_STATS_DEVICE) += kgsl_pwrscale_idlestats.o
msm_kgsl_core-$(CONFIG_SYNC) += kgsl_sync.o
msm_adreno-y += \
+20 -19
View File
@@ -77,11 +77,27 @@
#define KGSL_LOG_LEVEL_DEFAULT 3
/*
* The default values for the simpleondemand governor are 90 and 5,
* we use different values here.
* They have to be tuned and compare with the tz governor anyway.
*/
static struct devfreq_simple_ondemand_data adreno_ondemand_data = {
.upthreshold = 80,
.downdifferential = 20,
};
static const struct devfreq_governor_data adreno_governors[] = {
{ .name = "simple_ondemand", .data = &adreno_ondemand_data },
};
static const struct kgsl_functable adreno_functable;
static struct adreno_device device_3d0 = {
.dev = {
KGSL_DEVICE_COMMON_INIT(device_3d0.dev),
.pwrscale = KGSL_PWRSCALE_INIT(adreno_governors,
ARRAY_SIZE(adreno_governors)),
.name = DEVICE_3D0_NAME,
.id = KGSL_DEVICE_3D0,
.mh = {
@@ -1635,8 +1651,7 @@ adreno_probe(struct platform_device *pdev)
adreno_ft_init_sysfs(device);
kgsl_pwrscale_init(device);
kgsl_pwrscale_attach_policy(device, ADRENO_DEFAULT_PWRSCALE_POLICY);
kgsl_pwrscale_init(&pdev->dev, CONFIG_MSM_ADRENO_DEFAULT_GOVERNOR);
device->flags &= ~KGSL_FLAGS_SOFT_RESET;
pdata = kgsl_device_get_drvdata(device);
@@ -1666,7 +1681,6 @@ static int __devexit adreno_remove(struct platform_device *pdev)
adreno_coresight_remove(pdev);
adreno_profile_close(device);
kgsl_pwrscale_detach_policy(device);
kgsl_pwrscale_close(device);
adreno_dispatcher_close(adreno_dev);
@@ -2855,6 +2869,7 @@ static void adreno_power_stats(struct kgsl_device *device,
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
unsigned int cycles = 0;
memset(stats, 0, sizeof(*stats));
/*
* Get the busy cycles counted since the counter was last reset.
* If we're not currently active, there shouldn't have been
@@ -2863,22 +2878,8 @@ static void adreno_power_stats(struct kgsl_device *device,
if (device->state == KGSL_STATE_ACTIVE)
cycles = adreno_dev->gpudev->busy_cycles(adreno_dev);
/*
* In order to calculate idle you have to have run the algorithm
* at least once to get a start time.
*/
if (pwr->time != 0) {
s64 tmp = ktime_to_us(ktime_get());
stats->total_time = tmp - pwr->time;
pwr->time = tmp;
stats->busy_time = adreno_ticks_to_us(cycles, device->pwrctrl.
pwrlevels[device->pwrctrl.active_pwrlevel].
gpu_freq);
} else {
stats->total_time = 0;
stats->busy_time = 0;
pwr->time = ktime_to_us(ktime_get());
}
stats->busy_time = adreno_ticks_to_us(cycles,
kgsl_pwrctrl_active_freq(pwr));
}
void adreno_irqctrl(struct kgsl_device *device, int state)
+1
View File
@@ -62,6 +62,7 @@
#define ADRENO_DEFAULT_PWRSCALE_POLICY NULL
#endif
void adreno_debugfs_init(struct kgsl_device *device);
#define ADRENO_ISTORE_START 0x5000 /* Istore offset */
+1 -4
View File
@@ -605,7 +605,6 @@ EXPORT_SYMBOL(kgsl_check_timestamp);
static int kgsl_suspend_device(struct kgsl_device *device, pm_message_t state)
{
int status = -EINVAL;
struct kgsl_pwrscale_policy *policy_saved;
if (!device)
return -EINVAL;
@@ -613,8 +612,6 @@ static int kgsl_suspend_device(struct kgsl_device *device, pm_message_t state)
KGSL_PWR_WARN(device, "suspend start\n");
mutex_lock(&device->mutex);
policy_saved = device->pwrscale.policy;
device->pwrscale.policy = NULL;
kgsl_pwrctrl_request_state(device, KGSL_STATE_SUSPEND);
/* Tell the device to drain the submission queue */
@@ -659,7 +656,7 @@ static int kgsl_suspend_device(struct kgsl_device *device, pm_message_t state)
goto end;
}
kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE);
device->pwrscale.policy = policy_saved;
kgsl_pwrscale_sleep(device);
status = 0;
end:
-6
View File
@@ -292,7 +292,6 @@ struct kgsl_device {
struct list_head events;
struct list_head events_pending_list;
unsigned int events_last_timestamp;
s64 on_time;
/* Postmortem Control switches */
int pm_regs_enabled;
@@ -418,11 +417,6 @@ struct kgsl_device_private {
struct kgsl_process_private *process_priv;
};
struct kgsl_power_stats {
s64 total_time;
s64 busy_time;
};
struct kgsl_device *kgsl_get_device(int dev_idx);
int kgsl_add_event(struct kgsl_device *device, u32 id, u32 ts,
+19 -15
View File
@@ -226,11 +226,8 @@ static int kgsl_pwrctrl_thermal_pwrlevel_store(struct device *dev,
* a policy only change the active clock if it is higher then the new
* thermal level
*/
if (device->pwrscale.policy == NULL ||
pwr->thermal_pwrlevel > pwr->active_pwrlevel)
if (pwr->thermal_pwrlevel > pwr->active_pwrlevel)
kgsl_pwrctrl_pwrlevel_change(device, pwr->thermal_pwrlevel);
mutex_unlock(&device->mutex);
return count;
@@ -285,11 +282,8 @@ static int kgsl_pwrctrl_max_pwrlevel_store(struct device *dev,
* If there is no policy then move to max by default. Otherwise only
* move max if the current level happens to be higher then the new max
*/
if (device->pwrscale.policy == NULL ||
(max_level > pwr->active_pwrlevel))
if (max_level > pwr->active_pwrlevel)
kgsl_pwrctrl_pwrlevel_change(device, max_level);
mutex_unlock(&device->mutex);
return count;
@@ -479,8 +473,7 @@ static int kgsl_pwrctrl_gpuclk_show(struct device *dev,
if (device == NULL)
return 0;
pwr = &device->pwrctrl;
return snprintf(buf, PAGE_SIZE, "%d\n",
pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq);
return snprintf(buf, PAGE_SIZE, "%ld\n", kgsl_pwrctrl_active_freq(pwr));
}
static int kgsl_pwrctrl_idle_timer_store(struct device *dev,
@@ -1179,7 +1172,7 @@ void kgsl_idle_check(struct work_struct *work)
mutex_lock(&device->mutex);
kgsl_pwrscale_idle(device);
kgsl_pwrscale_update(device);
if (device->state == KGSL_STATE_ACTIVE
|| device->state == KGSL_STATE_NAP) {
@@ -1275,12 +1268,25 @@ EXPORT_SYMBOL(kgsl_pre_hwaccess);
static int
_nap(struct kgsl_device *device)
{
struct kgsl_power_stats stats;
switch (device->state) {
case KGSL_STATE_ACTIVE:
if (!device->ftbl->isidle(device)) {
kgsl_pwrctrl_request_state(device, KGSL_STATE_NONE);
return -EBUSY;
}
/*
* Read HW busy counters before going to NAP state.
* The data might be used by power scale govenors
* independently of the HW activity. For example
* the simple-on-demand governer will get the latest
* busy_time data even if the gpu isn't active.
*/
device->ftbl->power_stats(device, &stats);
device->pwrscale.accum_stats.busy_time += stats.busy_time;
kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF);
kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_OFF, KGSL_STATE_NAP);
kgsl_pwrctrl_set_state(device, KGSL_STATE_NAP);
@@ -1300,7 +1306,7 @@ _sleep_accounting(struct kgsl_device *device)
{
kgsl_pwrctrl_busy_time(device, false);
device->pwrctrl.clk_stats.start = ktime_set(0, 0);
device->pwrctrl.time = 0;
kgsl_pwrscale_sleep(device);
}
@@ -1465,7 +1471,7 @@ void kgsl_pwrctrl_enable(struct kgsl_device *device)
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
/* Order pwrrail/clk sequence based upon platform */
kgsl_pwrctrl_pwrrail(device, KGSL_PWRFLAGS_ON);
kgsl_pwrctrl_pwrlevel_change(device, pwr->default_pwrlevel);
kgsl_pwrctrl_pwrlevel_change(device, pwr->active_pwrlevel);
kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_ON, KGSL_STATE_ACTIVE);
kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_ON);
}
@@ -1591,8 +1597,6 @@ void kgsl_active_count_put(struct kgsl_device *device)
BUG_ON(!mutex_is_locked(&device->mutex));
BUG_ON(atomic_read(&device->active_cnt) == 0);
kgsl_pwrscale_idle(device);
if (atomic_dec_and_test(&device->active_cnt)) {
if (device->state == KGSL_STATE_ACTIVE &&
device->requested_state == KGSL_STATE_NONE) {
+12 -1
View File
@@ -88,7 +88,6 @@ struct kgsl_pwrctrl {
uint32_t pcl;
unsigned int idle_needed;
const char *irq_name;
s64 time;
struct kgsl_clk_stats clk_stats;
struct pm_qos_request pm_qos_req_dma;
unsigned int pm_qos_latency;
@@ -117,6 +116,18 @@ static inline unsigned long kgsl_get_clkrate(struct clk *clk)
return (clk != NULL) ? clk_get_rate(clk) : 0;
}
/*
* kgsl_pwrctrl_active_freq - get currently configured frequency
* @pwr: kgsl_pwrctrl structure for the device
*
* Returns the currently configured frequency for the device.
*/
static inline unsigned long
kgsl_pwrctrl_active_freq(struct kgsl_pwrctrl *pwr)
{
return pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq;
}
void kgsl_pwrctrl_set_state(struct kgsl_device *device, unsigned int state);
void kgsl_pwrctrl_request_state(struct kgsl_device *device, unsigned int state);
+379 -292
View File
@@ -14,364 +14,451 @@
#include <linux/export.h>
#include <linux/kernel.h>
#include <asm/page.h>
#include "kgsl.h"
#include "kgsl_pwrscale.h"
#include "kgsl_device.h"
#include "kgsl_trace.h"
struct kgsl_pwrscale_attribute {
struct attribute attr;
ssize_t (*show)(struct kgsl_device *device, char *buf);
ssize_t (*store)(struct kgsl_device *device, const char *buf,
size_t count);
};
#define to_pwrscale(k) container_of(k, struct kgsl_pwrscale, kobj)
#define pwrscale_to_device(p) container_of(p, struct kgsl_device, pwrscale)
#define to_device(k) container_of(k, struct kgsl_device, pwrscale_kobj)
#define to_pwrscale_attr(a) \
container_of(a, struct kgsl_pwrscale_attribute, attr)
#define to_policy_attr(a) \
container_of(a, struct kgsl_pwrscale_policy_attribute, attr)
#define PWRSCALE_ATTR(_name, _mode, _show, _store) \
struct kgsl_pwrscale_attribute pwrscale_attr_##_name = \
__ATTR(_name, _mode, _show, _store)
/* Master list of available policies */
static struct kgsl_pwrscale_policy *kgsl_pwrscale_policies[] = {
#ifdef CONFIG_MSM_SCM
&kgsl_pwrscale_policy_tz,
#endif
#ifdef CONFIG_MSM_SLEEP_STATS_DEVICE
&kgsl_pwrscale_policy_idlestats,
#endif
NULL
};
static ssize_t pwrscale_policy_store(struct kgsl_device *device,
const char *buf, size_t count)
{
int i;
struct kgsl_pwrscale_policy *policy = NULL;
/* The special keyword none allows the user to detach all
policies */
if (!strncmp("none", buf, 4)) {
kgsl_pwrscale_detach_policy(device);
return count;
}
for (i = 0; kgsl_pwrscale_policies[i]; i++) {
if (!strncmp(kgsl_pwrscale_policies[i]->name, buf,
strnlen(kgsl_pwrscale_policies[i]->name,
PAGE_SIZE))) {
policy = kgsl_pwrscale_policies[i];
break;
}
}
if (policy)
if (kgsl_pwrscale_attach_policy(device, policy))
return -EIO;
return count;
}
static ssize_t pwrscale_policy_show(struct kgsl_device *device, char *buf)
{
int ret;
if (device->pwrscale.policy) {
ret = snprintf(buf, PAGE_SIZE, "%s",
device->pwrscale.policy->name);
if (device->pwrscale.enabled == 0)
ret += snprintf(buf + ret, PAGE_SIZE - ret,
" (disabled)");
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
} else
ret = snprintf(buf, PAGE_SIZE, "none\n");
return ret;
}
PWRSCALE_ATTR(policy, 0664, pwrscale_policy_show, pwrscale_policy_store);
static ssize_t pwrscale_avail_policies_show(struct kgsl_device *device,
char *buf)
{
int i, ret = 0;
for (i = 0; kgsl_pwrscale_policies[i]; i++) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s ",
kgsl_pwrscale_policies[i]->name);
}
ret += snprintf(buf + ret, PAGE_SIZE - ret, "none\n");
return ret;
}
PWRSCALE_ATTR(avail_policies, 0444, pwrscale_avail_policies_show, NULL);
static struct attribute *pwrscale_attrs[] = {
&pwrscale_attr_policy.attr,
&pwrscale_attr_avail_policies.attr,
NULL
};
static ssize_t policy_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct kgsl_pwrscale *pwrscale = to_pwrscale(kobj);
struct kgsl_device *device = pwrscale_to_device(pwrscale);
struct kgsl_pwrscale_policy_attribute *pattr = to_policy_attr(attr);
ssize_t ret;
if (pattr->show)
ret = pattr->show(device, pwrscale, buf);
else
ret = -EIO;
return ret;
}
static ssize_t policy_sysfs_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t count)
{
struct kgsl_pwrscale *pwrscale = to_pwrscale(kobj);
struct kgsl_device *device = pwrscale_to_device(pwrscale);
struct kgsl_pwrscale_policy_attribute *pattr = to_policy_attr(attr);
ssize_t ret;
if (pattr->store)
ret = pattr->store(device, pwrscale, buf, count);
else
ret = -EIO;
return ret;
}
static void policy_sysfs_release(struct kobject *kobj)
{
}
static ssize_t pwrscale_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct kgsl_device *device = to_device(kobj);
struct kgsl_pwrscale_attribute *pattr = to_pwrscale_attr(attr);
ssize_t ret;
if (pattr->show)
ret = pattr->show(device, buf);
else
ret = -EIO;
return ret;
}
static ssize_t pwrscale_sysfs_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t count)
{
struct kgsl_device *device = to_device(kobj);
struct kgsl_pwrscale_attribute *pattr = to_pwrscale_attr(attr);
ssize_t ret;
if (pattr->store)
ret = pattr->store(device, buf, count);
else
ret = -EIO;
return ret;
}
static void pwrscale_sysfs_release(struct kobject *kobj)
{
}
static const struct sysfs_ops policy_sysfs_ops = {
.show = policy_sysfs_show,
.store = policy_sysfs_store
};
static const struct sysfs_ops pwrscale_sysfs_ops = {
.show = pwrscale_sysfs_show,
.store = pwrscale_sysfs_store
};
static struct kobj_type ktype_pwrscale_policy = {
.sysfs_ops = &policy_sysfs_ops,
.default_attrs = NULL,
.release = policy_sysfs_release
};
static struct kobj_type ktype_pwrscale = {
.sysfs_ops = &pwrscale_sysfs_ops,
.default_attrs = pwrscale_attrs,
.release = pwrscale_sysfs_release
};
#define PWRSCALE_ACTIVE(_d) \
((_d)->pwrscale.policy && (_d)->pwrscale.enabled)
static void do_devfreq_suspend(struct work_struct *work);
static void do_devfreq_resume(struct work_struct *work);
static void do_devfreq_notify(struct work_struct *work);
/*
* kgsl_pwrscale_sleep - notify governor that device is going off
* @device: The device
*
* Called shortly after all pending work is completed.
*/
void kgsl_pwrscale_sleep(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->sleep)
device->pwrscale.policy->sleep(device, &device->pwrscale);
BUG_ON(!mutex_is_locked(&device->mutex));
if (!device->pwrscale.enabled)
return;
device->pwrscale.time = device->pwrscale.on_time = 0;
/* to call devfreq_suspend_device() from a kernel thread */
queue_work(device->pwrscale.devfreq_wq,
&device->pwrscale.devfreq_suspend_ws);
}
EXPORT_SYMBOL(kgsl_pwrscale_sleep);
/*
* kgsl_pwrscale_wake - notify governor that device is going on
* @device: The device
*
* Called when the device is returning to an active state.
*/
void kgsl_pwrscale_wake(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->wake)
device->pwrscale.policy->wake(device, &device->pwrscale);
struct kgsl_power_stats stats;
BUG_ON(!mutex_is_locked(&device->mutex));
if (!device->pwrscale.enabled)
return;
/* clear old stats before waking */
memset(&device->pwrscale.accum_stats, 0,
sizeof(device->pwrscale.accum_stats));
/* and any hw activity from waking up*/
device->ftbl->power_stats(device, &stats);
device->pwrscale.time = ktime_to_us(ktime_get());
device->pwrscale.next_governor_call = 0;
/* to call devfreq_resume_device() from a kernel thread */
queue_work(device->pwrscale.devfreq_wq,
&device->pwrscale.devfreq_resume_ws);
}
EXPORT_SYMBOL(kgsl_pwrscale_wake);
/*
* kgsl_pwrscale_busy - update pwrscale state for new work
* @device: The device
*
* Called when new work is submitted to the device.
* This function must be called with the device mutex locked.
*/
void kgsl_pwrscale_busy(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->busy)
device->pwrscale.policy->busy(device,
&device->pwrscale);
BUG_ON(!mutex_is_locked(&device->mutex));
if (!device->pwrscale.enabled)
return;
if (device->pwrscale.on_time == 0)
device->pwrscale.on_time = ktime_to_us(ktime_get());
}
EXPORT_SYMBOL(kgsl_pwrscale_busy);
void kgsl_pwrscale_idle(struct kgsl_device *device)
/*
* kgsl_pwrscale_update - update device busy statistics
* @device: The device
*
* Read hardware busy counters when the device is likely to be
* on and accumulate the results between devfreq get_dev_status
* calls. This is limits the need to turn on clocks to read these
* values for governers that run independtly of hardware
* activity (for example, by time based polling).
*/
void kgsl_pwrscale_update(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->idle)
if (device->state == KGSL_STATE_ACTIVE)
device->pwrscale.policy->idle(device,
&device->pwrscale);
}
EXPORT_SYMBOL(kgsl_pwrscale_idle);
BUG_ON(!mutex_is_locked(&device->mutex));
if (!device->pwrscale.enabled)
return;
if (device->pwrscale.next_governor_call == 0)
device->pwrscale.next_governor_call = jiffies;
if (time_before(jiffies, device->pwrscale.next_governor_call))
return;
device->pwrscale.next_governor_call = jiffies
+ msecs_to_jiffies(KGSL_GOVERNOR_CALL_INTERVAL);
/* to call srcu_notifier_call_chain() from a kernel thread */
queue_work(device->pwrscale.devfreq_wq,
&device->pwrscale.devfreq_notify_ws);
}
EXPORT_SYMBOL(kgsl_pwrscale_update);
/*
* kgsl_pwrscale_disable - temporarily disable the governor
* @device: The device
*
* Temporarily disable the governor, to prevent interference
* with profiling tools that expect a fixed clock frequency.
* This function must be called with the device mutex locked.
*/
void kgsl_pwrscale_disable(struct kgsl_device *device)
{
device->pwrscale.enabled = 0;
BUG_ON(!mutex_is_locked(&device->mutex));
if (device->pwrscale.enabled) {
queue_work(device->pwrscale.devfreq_wq,
&device->pwrscale.devfreq_suspend_ws);
device->pwrscale.enabled = false;
kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO);
}
}
EXPORT_SYMBOL(kgsl_pwrscale_disable);
/*
* kgsl_pwrscale_enable - re-enable the governor
* @device: The device
*
* Reenable the governor after a kgsl_pwrscale_disable() call.
* This function must be called with the device mutex locked.
*/
void kgsl_pwrscale_enable(struct kgsl_device *device)
{
device->pwrscale.enabled = 1;
BUG_ON(!mutex_is_locked(&device->mutex));
if (!device->pwrscale.enabled) {
device->pwrscale.enabled = true;
queue_work(device->pwrscale.devfreq_wq,
&device->pwrscale.devfreq_resume_ws);
}
}
EXPORT_SYMBOL(kgsl_pwrscale_enable);
int kgsl_pwrscale_policy_add_files(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
struct attribute_group *attr_group)
/*
* kgsl_devfreq_target - devfreq_dev_profile.target callback
* @dev: see devfreq.h
* @freq: see devfreq.h
* @flags: see devfreq.h
*
* This function expects the device mutex to be unlocked.
*/
int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags)
{
int ret;
struct kgsl_device *device = dev_get_drvdata(dev);
struct devfreq_dev_profile *profile;
struct kgsl_pwrctrl *pwr;
int level = -1, i;
unsigned long cur_freq;
int lubflag = 0;
ret = kobject_add(&pwrscale->kobj, &device->pwrscale_kobj,
"%s", pwrscale->policy->name);
if (device == NULL)
return -ENODEV;
if (freq == NULL)
return -EINVAL;
profile = &device->pwrscale.profile;
pwr = &device->pwrctrl;
if (ret)
return ret;
if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND)
lubflag = 1;
ret = sysfs_create_group(&pwrscale->kobj, attr_group);
if (ret) {
kobject_del(&pwrscale->kobj);
kobject_put(&pwrscale->kobj);
}
return ret;
}
void kgsl_pwrscale_policy_remove_files(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
struct attribute_group *attr_group)
{
sysfs_remove_group(&pwrscale->kobj, attr_group);
kobject_del(&pwrscale->kobj);
kobject_put(&pwrscale->kobj);
}
static void _kgsl_pwrscale_detach_policy(struct kgsl_device *device)
{
if (device->pwrscale.policy != NULL) {
device->pwrscale.policy->close(device, &device->pwrscale);
mutex_lock(&device->mutex);
cur_freq = kgsl_pwrctrl_active_freq(pwr);
if (*freq > cur_freq && pwr->active_pwrlevel > 0) {
/*
* Try to set max pwrlevel which will be limited to thermal by
* kgsl_pwrctrl_pwrlevel_change if thermal is indeed lower
* If LUB - Least Upper Bound - is requested
* - move up just one level,
* otherwise - move up until required freq or higher
*/
kgsl_pwrctrl_pwrlevel_change(device,
device->pwrctrl.max_pwrlevel);
device->pwrctrl.default_pwrlevel =
device->pwrctrl.max_pwrlevel;
level = pwr->active_pwrlevel - 1;
if (!lubflag)
while (*freq > pwr->pwrlevels[level].gpu_freq
&& level > 0)
level--;
} else if (*freq < cur_freq
&& pwr->active_pwrlevel < (pwr->num_pwrlevels - 2)) {
/*
* Move down at least 1 frequency. If we fall out the bottom
* of the loop, use the lowest frequency.
*/
level = (pwr->num_pwrlevels - 1);
for (i = pwr->active_pwrlevel; i < level; i += pwr->step_mul)
if (pwr->pwrlevels[i].gpu_freq <= *freq) {
level = i;
break;
}
} else {
/* already at current freq, min, or max */
level = pwr->active_pwrlevel;
}
device->pwrscale.policy = NULL;
}
void kgsl_pwrscale_detach_policy(struct kgsl_device *device)
{
mutex_lock(&device->mutex);
_kgsl_pwrscale_detach_policy(device);
if (device->pwrscale.enabled)
kgsl_pwrctrl_pwrlevel_change(device, level);
*freq = kgsl_pwrctrl_active_freq(pwr);
mutex_unlock(&device->mutex);
return 0;
}
EXPORT_SYMBOL(kgsl_pwrscale_detach_policy);
EXPORT_SYMBOL(kgsl_devfreq_target);
int kgsl_pwrscale_attach_policy(struct kgsl_device *device,
struct kgsl_pwrscale_policy *policy)
/*
* kgsl_devfreq_get_dev_status - devfreq_dev_profile.get_dev_status callback
* @dev: see devfreq.h
* @freq: see devfreq.h
* @flags: see devfreq.h
*
* This function expects the device mutex to be unlocked.
*/
int kgsl_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *stat)
{
int ret = 0;
struct kgsl_device *device = dev_get_drvdata(dev);
struct kgsl_pwrscale *pwrscale;
s64 tmp;
if (device == NULL)
return -ENODEV;
if (stat == NULL)
return -EINVAL;
pwrscale = &device->pwrscale;
memset(stat, 0, sizeof(*stat));
mutex_lock(&device->mutex);
if (device->pwrscale.policy == policy)
goto done;
if (device->pwrctrl.num_pwrlevels < 3) {
ret = -EINVAL;
goto done;
/* make sure we don't turn on clocks just to read stats */
if (device->state == KGSL_STATE_ACTIVE) {
struct kgsl_power_stats extra;
device->ftbl->power_stats(device, &extra);
device->pwrscale.accum_stats.busy_time += extra.busy_time;
}
if (device->pwrscale.policy != NULL)
_kgsl_pwrscale_detach_policy(device);
tmp = ktime_to_us(ktime_get());
stat->total_time = tmp - pwrscale->time;
pwrscale->time = tmp;
device->pwrscale.policy = policy;
stat->busy_time = pwrscale->accum_stats.busy_time;
device->pwrctrl.default_pwrlevel =
device->pwrctrl.init_pwrlevel;
/* Pwrscale is enabled by default at attach time */
kgsl_pwrscale_enable(device);
stat->current_frequency = kgsl_pwrctrl_active_freq(&device->pwrctrl);
if (policy) {
ret = device->pwrscale.policy->init(device, &device->pwrscale);
if (ret)
device->pwrscale.policy = NULL;
}
trace_kgsl_pwrstats(device, stat->total_time, &pwrscale->accum_stats);
memset(&pwrscale->accum_stats, 0, sizeof(pwrscale->accum_stats));
done:
mutex_unlock(&device->mutex);
return ret;
return 0;
}
EXPORT_SYMBOL(kgsl_pwrscale_attach_policy);
EXPORT_SYMBOL(kgsl_devfreq_get_dev_status);
int kgsl_pwrscale_init(struct kgsl_device *device)
/*
* kgsl_devfreq_get_cur_freq - devfreq_dev_profile.get_cur_freq callback
* @dev: see devfreq.h
* @freq: see devfreq.h
* @flags: see devfreq.h
*
* This function expects the device mutex to be unlocked.
*/
int kgsl_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct kgsl_device *device = dev_get_drvdata(dev);
if (device == NULL)
return -ENODEV;
if (freq == NULL)
return -EINVAL;
mutex_lock(&device->mutex);
*freq = kgsl_pwrctrl_active_freq(&device->pwrctrl);
mutex_unlock(&device->mutex);
return 0;
}
EXPORT_SYMBOL(kgsl_devfreq_get_cur_freq);
/*
* kgsl_devfreq_add_notifier - add a fine grained notifier.
* @dev: The device
* @nb: Notifier block that will recieve updates.
*
* Add a notifier to recieve ADRENO_DEVFREQ_NOTIFY_* events
* from the device.
*/
int kgsl_devfreq_add_notifier(struct device *dev, struct notifier_block *nb)
{
struct kgsl_device *device = dev_get_drvdata(dev);
if (device == NULL)
return -ENODEV;
if (nb == NULL)
return -EINVAL;
return srcu_notifier_chain_register(&device->pwrscale.nh, nb);
}
void kgsl_pwrscale_idle(struct kgsl_device *device)
{
BUG_ON(!mutex_is_locked(&device->mutex));
queue_work(device->pwrscale.devfreq_wq,
&device->pwrscale.devfreq_notify_ws);
}
EXPORT_SYMBOL(kgsl_pwrscale_idle);
/*
* kgsl_devfreq_del_notifier - remove a fine grained notifier.
* @dev: The device
* @nb: The notifier block.
*
* Remove a notifier registered with kgsl_devfreq_add_notifier().
*/
int kgsl_devfreq_del_notifier(struct device *dev, struct notifier_block *nb)
{
struct kgsl_device *device = dev_get_drvdata(dev);
if (device == NULL)
return -ENODEV;
if (nb == NULL)
return -EINVAL;
return srcu_notifier_chain_unregister(&device->pwrscale.nh, nb);
}
EXPORT_SYMBOL(kgsl_devfreq_del_notifier);
/*
* kgsl_pwrscale_init - Initialize pwrscale.
* @dev: The device
* @governor: The initial governor to use.
*
* Initialize devfreq and any non-constant profile data.
*/
int kgsl_pwrscale_init(struct device *dev, const char *governor)
{
struct kgsl_device *device;
struct kgsl_pwrscale *pwrscale;
struct kgsl_pwrctrl *pwr;
struct devfreq *devfreq;
int i, out = 0;
int ret;
ret = kobject_init_and_add(&device->pwrscale_kobj, &ktype_pwrscale,
&device->dev->kobj, "pwrscale");
device = dev_get_drvdata(dev);
if (device == NULL)
return -ENODEV;
if (ret)
return ret;
pwrscale = &device->pwrscale;
pwr = &device->pwrctrl;
kobject_init(&device->pwrscale.kobj, &ktype_pwrscale_policy);
return ret;
srcu_init_notifier_head(&pwrscale->nh);
pwrscale->profile.initial_freq =
pwr->pwrlevels[pwr->default_pwrlevel].gpu_freq;
/* Let's start with 10 ms and tune in later */
pwrscale->profile.polling_ms = 10;
/* do not include the 'off' level or duplicate freq. levels */
for (i = 0; i < (pwr->num_pwrlevels - 1); i += pwr->step_mul)
pwrscale->freq_table[out++] = pwr->pwrlevels[i].gpu_freq;
pwrscale->profile.max_state = out;
/* link storage array to the devfreq profile pointer */
pwrscale->profile.freq_table = pwrscale->freq_table;
/* if there is only 1 freq, no point in running a governor */
if (pwrscale->profile.max_state == 1)
governor = "performance";
devfreq = devfreq_add_device(dev, &pwrscale->profile, governor, NULL);
if (IS_ERR(devfreq))
return PTR_ERR(devfreq);
pwrscale->devfreq = devfreq;
ret = sysfs_create_link(&device->dev->kobj,
&devfreq->dev.kobj, "devfreq");
pwrscale->devfreq_wq = create_freezable_workqueue("kgsl_devfreq_wq");
INIT_WORK(&pwrscale->devfreq_suspend_ws, do_devfreq_suspend);
INIT_WORK(&pwrscale->devfreq_resume_ws, do_devfreq_resume);
INIT_WORK(&pwrscale->devfreq_notify_ws, do_devfreq_notify);
pwrscale->next_governor_call = 0;
return 0;
}
EXPORT_SYMBOL(kgsl_pwrscale_init);
/*
* kgsl_pwrscale_close - clean up pwrscale
* @device: the device
*
* This function should be called with the device mutex locked.
*/
void kgsl_pwrscale_close(struct kgsl_device *device)
{
kobject_put(&device->pwrscale_kobj);
struct kgsl_pwrscale *pwrscale;
BUG_ON(!mutex_is_locked(&device->mutex));
pwrscale = &device->pwrscale;
flush_workqueue(pwrscale->devfreq_wq);
destroy_workqueue(pwrscale->devfreq_wq);
devfreq_remove_device(device->pwrscale.devfreq);
device->pwrscale.devfreq = NULL;
srcu_cleanup_notifier_head(&device->pwrscale.nh);
}
EXPORT_SYMBOL(kgsl_pwrscale_close);
static void do_devfreq_suspend(struct work_struct *work)
{
struct kgsl_pwrscale *pwrscale = container_of(work,
struct kgsl_pwrscale, devfreq_suspend_ws);
struct devfreq *devfreq = pwrscale->devfreq;
devfreq_suspend_device(devfreq);
}
static void do_devfreq_resume(struct work_struct *work)
{
struct kgsl_pwrscale *pwrscale = container_of(work,
struct kgsl_pwrscale, devfreq_resume_ws);
struct devfreq *devfreq = pwrscale->devfreq;
devfreq_resume_device(devfreq);
}
static void do_devfreq_notify(struct work_struct *work)
{
struct kgsl_pwrscale *pwrscale = container_of(work,
struct kgsl_pwrscale, devfreq_notify_ws);
struct devfreq *devfreq = pwrscale->devfreq;
srcu_notifier_call_chain(&pwrscale->nh,
ADRENO_DEVFREQ_NOTIFY_RETIRE,
devfreq);
}
+37 -49
View File
@@ -1,4 +1,4 @@
/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -14,68 +14,56 @@
#ifndef __KGSL_PWRSCALE_H
#define __KGSL_PWRSCALE_H
struct kgsl_pwrscale;
#include <linux/devfreq.h>
#include <linux/msm_adreno_devfreq.h>
struct kgsl_pwrscale_policy {
const char *name;
int (*init)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale);
void (*close)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale);
void (*idle)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale);
void (*busy)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale);
void (*sleep)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale);
void (*wake)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale);
/* devfreq governor call window in msec */
#define KGSL_GOVERNOR_CALL_INTERVAL 5
struct kgsl_power_stats {
u64 busy_time;
};
struct kgsl_pwrscale {
struct kgsl_pwrscale_policy *policy;
struct kobject kobj;
void *priv;
int enabled;
struct devfreq *devfreq;
struct devfreq_dev_profile profile;
unsigned int freq_table[KGSL_MAX_PWRLEVELS];
char last_governor[DEVFREQ_NAME_LEN];
struct kgsl_power_stats accum_stats;
bool enabled;
s64 time;
s64 on_time;
struct srcu_notifier_head nh;
struct workqueue_struct *devfreq_wq;
struct work_struct devfreq_suspend_ws;
struct work_struct devfreq_resume_ws;
struct work_struct devfreq_notify_ws;
unsigned long next_governor_call;
};
struct kgsl_pwrscale_policy_attribute {
struct attribute attr;
ssize_t (*show)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale, char *buf);
ssize_t (*store)(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale, const char *buf,
size_t count);
};
#define PWRSCALE_POLICY_ATTR(_name, _mode, _show, _store) \
struct kgsl_pwrscale_policy_attribute policy_attr_##_name = \
__ATTR(_name, _mode, _show, _store)
extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_tz;
extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats;
extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_msm;
int kgsl_pwrscale_init(struct kgsl_device *device);
int kgsl_pwrscale_init(struct device *dev, const char *governor);
void kgsl_pwrscale_close(struct kgsl_device *device);
int kgsl_pwrscale_attach_policy(struct kgsl_device *device,
struct kgsl_pwrscale_policy *policy);
void kgsl_pwrscale_detach_policy(struct kgsl_device *device);
void kgsl_pwrscale_idle(struct kgsl_device *device);
void kgsl_pwrscale_update(struct kgsl_device *device);
void kgsl_pwrscale_busy(struct kgsl_device *device);
void kgsl_pwrscale_idle(struct kgsl_device *device);
void kgsl_pwrscale_sleep(struct kgsl_device *device);
void kgsl_pwrscale_wake(struct kgsl_device *device);
void kgsl_pwrscale_enable(struct kgsl_device *device);
void kgsl_pwrscale_disable(struct kgsl_device *device);
int kgsl_pwrscale_policy_add_files(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
struct attribute_group *attr_group);
int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags);
int kgsl_devfreq_get_dev_status(struct device *, struct devfreq_dev_status *);
int kgsl_devfreq_get_cur_freq(struct device *dev, unsigned long *freq);
void kgsl_pwrscale_policy_remove_files(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
struct attribute_group *attr_group);
#define KGSL_PWRSCALE_INIT(_gov_list, _num_gov) { \
.enabled = true, \
.profile = { \
.target = kgsl_devfreq_target, \
.get_dev_status = kgsl_devfreq_get_dev_status, \
.get_cur_freq = kgsl_devfreq_get_cur_freq, \
.governor_data = (_gov_list), \
.num_governor_data = (_num_gov), \
} }
#endif
-232
View File
@@ -1,232 +0,0 @@
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*
*/
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/idle_stats_device.h>
#include <linux/cpufreq.h>
#include <linux/notifier.h>
#include <linux/cpumask.h>
#include <linux/tick.h>
#include "kgsl.h"
#include "kgsl_pwrscale.h"
#include "kgsl_device.h"
#define MAX_CORES 4
struct _cpu_info {
spinlock_t lock;
struct notifier_block cpu_nb;
u64 start[MAX_CORES];
u64 end[MAX_CORES];
int curr_freq[MAX_CORES];
int max_freq[MAX_CORES];
};
struct idlestats_priv {
char name[32];
struct msm_idle_stats_device idledev;
struct kgsl_device *device;
struct msm_idle_pulse pulse;
struct _cpu_info cpu_info;
};
static int idlestats_cpufreq_notifier(
struct notifier_block *nb,
unsigned long val, void *data)
{
struct _cpu_info *cpu = container_of(nb,
struct _cpu_info, cpu_nb);
struct cpufreq_freqs *freq = data;
if (val != CPUFREQ_POSTCHANGE)
return 0;
spin_lock(&cpu->lock);
if (freq->cpu < num_possible_cpus())
cpu->curr_freq[freq->cpu] = freq->new / 1000;
spin_unlock(&cpu->lock);
return 0;
}
static void idlestats_get_sample(struct msm_idle_stats_device *idledev,
struct msm_idle_pulse *pulse)
{
struct kgsl_power_stats stats;
struct idlestats_priv *priv = container_of(idledev,
struct idlestats_priv, idledev);
struct kgsl_device *device = priv->device;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
mutex_lock(&device->mutex);
/* If the GPU is asleep, don't wake it up - assume that we
are idle */
if (device->state == KGSL_STATE_ACTIVE) {
device->ftbl->power_stats(device, &stats);
pulse->busy_start_time = pwr->time - stats.busy_time;
pulse->busy_interval = stats.busy_time;
} else {
pulse->busy_start_time = pwr->time;
pulse->busy_interval = 0;
}
pulse->wait_interval = 0;
mutex_unlock(&device->mutex);
}
static void idlestats_busy(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv = pwrscale->priv;
struct kgsl_power_stats stats;
int i, busy, nr_cpu = 1;
if (priv->pulse.busy_start_time != 0) {
priv->pulse.wait_interval = 0;
/* Calculate the total CPU busy time for this GPU pulse */
for (i = 0; i < num_possible_cpus(); i++) {
spin_lock(&priv->cpu_info.lock);
if (cpu_online(i)) {
priv->cpu_info.end[i] =
(u64)ktime_to_us(ktime_get()) -
get_cpu_idle_time_us(i, NULL);
busy = priv->cpu_info.end[i] -
priv->cpu_info.start[i];
/* Normalize the busy time by frequency */
busy = priv->cpu_info.curr_freq[i] *
(busy / priv->cpu_info.max_freq[i]);
priv->pulse.wait_interval += busy;
nr_cpu++;
}
spin_unlock(&priv->cpu_info.lock);
}
priv->pulse.wait_interval /= nr_cpu;
/* This is called from within a mutex protected function, so
no additional locking required */
device->ftbl->power_stats(device, &stats);
/* If total_time is zero, then we don't have
any interesting statistics to store */
if (stats.total_time == 0) {
priv->pulse.busy_start_time = 0;
return;
}
priv->pulse.busy_interval = stats.busy_time;
msm_idle_stats_idle_end(&priv->idledev, &priv->pulse);
}
priv->pulse.busy_start_time = ktime_to_us(ktime_get());
}
static void idlestats_idle(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
int i, nr_cpu;
struct idlestats_priv *priv = pwrscale->priv;
nr_cpu = num_possible_cpus();
for (i = 0; i < nr_cpu; i++)
if (cpu_online(i))
priv->cpu_info.start[i] =
(u64)ktime_to_us(ktime_get()) -
get_cpu_idle_time_us(i, NULL);
msm_idle_stats_idle_start(&priv->idledev);
}
static void idlestats_sleep(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv = pwrscale->priv;
msm_idle_stats_update_event(&priv->idledev,
MSM_IDLE_STATS_EVENT_IDLE_TIMER_EXPIRED);
}
static void idlestats_wake(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
/* Use highest perf level on wake-up from
sleep for better performance */
kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO);
}
static int idlestats_init(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv;
struct cpufreq_policy cpu_policy;
int ret, i;
priv = pwrscale->priv = kzalloc(sizeof(struct idlestats_priv),
GFP_KERNEL);
if (pwrscale->priv == NULL)
return -ENOMEM;
snprintf(priv->name, sizeof(priv->name), "idle_stats_%s",
device->name);
priv->device = device;
priv->idledev.name = (const char *) priv->name;
priv->idledev.get_sample = idlestats_get_sample;
spin_lock_init(&priv->cpu_info.lock);
priv->cpu_info.cpu_nb.notifier_call =
idlestats_cpufreq_notifier;
ret = cpufreq_register_notifier(&priv->cpu_info.cpu_nb,
CPUFREQ_TRANSITION_NOTIFIER);
if (ret)
goto err;
for (i = 0; i < num_possible_cpus(); i++) {
cpufreq_frequency_table_cpuinfo(&cpu_policy,
cpufreq_frequency_get_table(i));
priv->cpu_info.max_freq[i] = cpu_policy.max / 1000;
priv->cpu_info.curr_freq[i] = cpu_policy.max / 1000;
}
ret = msm_idle_stats_register_device(&priv->idledev);
err:
if (ret) {
kfree(pwrscale->priv);
pwrscale->priv = NULL;
}
return ret;
}
static void idlestats_close(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct idlestats_priv *priv = pwrscale->priv;
if (pwrscale->priv == NULL)
return;
cpufreq_unregister_notifier(&priv->cpu_info.cpu_nb,
CPUFREQ_TRANSITION_NOTIFIER);
msm_idle_stats_deregister_device(&priv->idledev);
kfree(pwrscale->priv);
pwrscale->priv = NULL;
}
struct kgsl_pwrscale_policy kgsl_pwrscale_policy_idlestats = {
.name = "idlestats",
.init = idlestats_init,
.idle = idlestats_idle,
.busy = idlestats_busy,
.sleep = idlestats_sleep,
.wake = idlestats_wake,
.close = idlestats_close
};
-269
View File
@@ -1,269 +0,0 @@
/* Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*
*/
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <mach/socinfo.h>
#include <mach/scm.h>
#include "kgsl.h"
#include "kgsl_pwrscale.h"
#include "kgsl_device.h"
#define TZ_GOVERNOR_PERFORMANCE 0
#define TZ_GOVERNOR_ONDEMAND 1
struct tz_priv {
int governor;
struct kgsl_power_stats bin;
unsigned int idle_dcvs;
};
spinlock_t tz_lock;
/* FLOOR is 5msec to capture up to 3 re-draws
* per frame for 60fps content.
*/
#define FLOOR 5000
/* CEILING is 50msec, larger than any standard
* frame length, but less than the idle timer.
*/
#define CEILING 50000
#define TZ_RESET_ID 0x3
#define TZ_UPDATE_ID 0x4
#define TZ_INIT_ID 0x6
/* Trap into the TrustZone, and call funcs there. */
static int __secure_tz_entry2(u32 cmd, u32 val1, u32 val2)
{
int ret;
spin_lock(&tz_lock);
/* sync memory before sending the commands to tz*/
__iowmb();
ret = scm_call_atomic2(SCM_SVC_IO, cmd, val1, val2);
spin_unlock(&tz_lock);
return ret;
}
static int __secure_tz_entry3(u32 cmd, u32 val1, u32 val2,
u32 val3)
{
int ret;
spin_lock(&tz_lock);
/* sync memory before sending the commands to tz*/
__iowmb();
ret = scm_call_atomic3(SCM_SVC_IO, cmd, val1, val2,
val3);
spin_unlock(&tz_lock);
return ret;
}
static ssize_t tz_governor_show(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
char *buf)
{
struct tz_priv *priv = pwrscale->priv;
int ret;
if (priv->governor == TZ_GOVERNOR_ONDEMAND)
ret = snprintf(buf, 10, "ondemand\n");
else
ret = snprintf(buf, 13, "performance\n");
return ret;
}
static ssize_t tz_governor_store(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
const char *buf, size_t count)
{
char str[20];
struct tz_priv *priv = pwrscale->priv;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
int ret;
ret = sscanf(buf, "%20s", str);
if (ret != 1)
return -EINVAL;
mutex_lock(&device->mutex);
if (!strncmp(str, "ondemand", 8))
priv->governor = TZ_GOVERNOR_ONDEMAND;
else if (!strncmp(str, "performance", 11))
priv->governor = TZ_GOVERNOR_PERFORMANCE;
if (priv->governor == TZ_GOVERNOR_PERFORMANCE) {
kgsl_pwrctrl_pwrlevel_change(device, pwr->max_pwrlevel);
pwr->default_pwrlevel = pwr->max_pwrlevel;
} else {
pwr->default_pwrlevel = pwr->init_pwrlevel;
}
mutex_unlock(&device->mutex);
return count;
}
PWRSCALE_POLICY_ATTR(governor, 0644, tz_governor_show, tz_governor_store);
static struct attribute *tz_attrs[] = {
&policy_attr_governor.attr,
NULL
};
static struct attribute_group tz_attr_group = {
.attrs = tz_attrs,
};
static void tz_wake(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale)
{
struct tz_priv *priv = pwrscale->priv;
if (device->state != KGSL_STATE_NAP &&
priv->governor == TZ_GOVERNOR_ONDEMAND)
kgsl_pwrctrl_pwrlevel_change(device,
device->pwrctrl.default_pwrlevel);
}
static void tz_idle(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale)
{
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
struct tz_priv *priv = pwrscale->priv;
struct kgsl_power_stats stats;
int val, idle;
/* In "performance" mode the clock speed always stays
the same */
if (priv->governor == TZ_GOVERNOR_PERFORMANCE)
return;
device->ftbl->power_stats(device, &stats);
priv->bin.total_time += stats.total_time;
priv->bin.busy_time += stats.busy_time;
/* Do not waste CPU cycles running this algorithm if
* the GPU just started, or if less than FLOOR time
* has passed since the last run.
*/
if ((stats.total_time == 0) ||
(priv->bin.total_time < FLOOR))
return;
/* If there is an extended block of busy processing, set
* frequency to turbo. Otherwise run the normal algorithm.
*/
if (priv->bin.busy_time > CEILING) {
val = 0;
kgsl_pwrctrl_pwrlevel_change(device,
KGSL_PWRLEVEL_TURBO);
} else if (priv->idle_dcvs) {
idle = priv->bin.total_time - priv->bin.busy_time;
idle = (idle > 0) ? idle : 0;
val = __secure_tz_entry2(TZ_UPDATE_ID, idle, device->id);
} else {
if (pwr->step_mul > 1)
val = __secure_tz_entry3(TZ_UPDATE_ID,
(pwr->active_pwrlevel + 1)/2,
priv->bin.total_time, priv->bin.busy_time);
else
val = __secure_tz_entry3(TZ_UPDATE_ID,
pwr->active_pwrlevel,
priv->bin.total_time, priv->bin.busy_time);
}
priv->bin.total_time = 0;
priv->bin.busy_time = 0;
/* If the decision is to move to a lower level, make sure the GPU
* frequency drops.
*/
if (val > 0)
val *= pwr->step_mul;
if (val)
kgsl_pwrctrl_pwrlevel_change(device,
pwr->active_pwrlevel + val);
}
static void tz_busy(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
device->on_time = ktime_to_us(ktime_get());
}
static void tz_sleep(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale)
{
struct tz_priv *priv = pwrscale->priv;
__secure_tz_entry2(TZ_RESET_ID, 0, 0);
priv->bin.total_time = 0;
priv->bin.busy_time = 0;
}
#ifdef CONFIG_MSM_SCM
static int tz_init(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale)
{
int i = 0, j = 1, ret = 0;
struct tz_priv *priv;
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
unsigned int tz_pwrlevels[KGSL_MAX_PWRLEVELS + 1];
priv = pwrscale->priv = kzalloc(sizeof(struct tz_priv), GFP_KERNEL);
if (pwrscale->priv == NULL)
return -ENOMEM;
priv->idle_dcvs = 0;
priv->governor = TZ_GOVERNOR_ONDEMAND;
spin_lock_init(&tz_lock);
kgsl_pwrscale_policy_add_files(device, pwrscale, &tz_attr_group);
for (i = 0; i < pwr->num_pwrlevels - 1; i++) {
if (i == 0)
tz_pwrlevels[j] = pwr->pwrlevels[i].gpu_freq;
else if (pwr->pwrlevels[i].gpu_freq !=
pwr->pwrlevels[i - 1].gpu_freq) {
j++;
tz_pwrlevels[j] = pwr->pwrlevels[i].gpu_freq;
}
}
tz_pwrlevels[0] = j;
ret = scm_call(SCM_SVC_DCVS, TZ_INIT_ID, tz_pwrlevels,
sizeof(tz_pwrlevels), NULL, 0);
if (ret) {
KGSL_DRV_ERR(device, "Fall back to idle based GPU DCVS algo");
priv->idle_dcvs = 1;
}
return 0;
}
#else
static int tz_init(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale)
{
return -EINVAL;
}
#endif /* CONFIG_MSM_SCM */
static void tz_close(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale)
{
kgsl_pwrscale_policy_remove_files(device, pwrscale, &tz_attr_group);
kfree(pwrscale->priv);
pwrscale->priv = NULL;
}
struct kgsl_pwrscale_policy kgsl_pwrscale_policy_tz = {
.name = "trustzone",
.init = tz_init,
.busy = tz_busy,
.idle = tz_idle,
.sleep = tz_sleep,
.wake = tz_wake,
.close = tz_close
};
EXPORT_SYMBOL(kgsl_pwrscale_policy_tz);
+25
View File
@@ -287,6 +287,31 @@ TRACE_EVENT(kgsl_gpubusy,
)
);
TRACE_EVENT(kgsl_pwrstats,
TP_PROTO(struct kgsl_device *device, u64 total_time,
struct kgsl_power_stats *pstats),
TP_ARGS(device, total_time, pstats),
TP_STRUCT__entry(
__string(device_name, device->name)
__field(u64, total_time)
__field(u64, busy_time)
),
TP_fast_assign(
__assign_str(device_name, device->name);
__entry->total_time = total_time;
__entry->busy_time = pstats->busy_time;
),
TP_printk(
"d_name=%s total=%lld busy=%lld",
__get_str(device_name),
__entry->total_time, __entry->busy_time
)
);
DECLARE_EVENT_CLASS(kgsl_pwrstate_template,
TP_PROTO(struct kgsl_device *device, unsigned int state),
+7 -10
View File
@@ -559,8 +559,7 @@ static int __devinit z180_probe(struct platform_device *pdev)
if (status)
goto error_close_ringbuffer;
kgsl_pwrscale_init(device);
kgsl_pwrscale_attach_policy(device, Z180_DEFAULT_PWRSCALE_POLICY);
kgsl_pwrscale_init(&pdev->dev, CONFIG_MSM_Z180_DEFAULT_GOVERNOR);
return status;
@@ -955,18 +954,16 @@ z180_drawctxt_destroy(struct kgsl_context *context)
static void z180_power_stats(struct kgsl_device *device,
struct kgsl_power_stats *stats)
{
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
struct kgsl_pwrscale *pwrscale = &device->pwrscale;
s64 tmp = ktime_to_us(ktime_get());
if (pwr->time == 0) {
pwr->time = tmp;
stats->total_time = 0;
memset(stats, 0, sizeof(stats));
if (pwrscale->on_time == 0) {
pwrscale->on_time = tmp;
stats->busy_time = 0;
} else {
stats->total_time = tmp - pwr->time;
pwr->time = tmp;
stats->busy_time = tmp - device->on_time;
device->on_time = tmp;
stats->busy_time = tmp - pwrscale->on_time;
pwrscale->on_time = tmp;
}
}
-2
View File
@@ -26,8 +26,6 @@
#define Z180_DEVICE(device) \
KGSL_CONTAINER_OF(device, struct z180_device, dev)
#define Z180_DEFAULT_PWRSCALE_POLICY NULL
/* Wait a maximum of 10 seconds when trying to idle the core */
#define Z180_IDLE_TIMEOUT (20 * 1000)
+28
View File
@@ -0,0 +1,28 @@
#ifndef MSM_ADRENO_DEVFREQ_H
#define MSM_ADRENO_DEVFREQ_H
#include <linux/notifier.h>
#define ADRENO_DEVFREQ_NOTIFY_SUBMIT 1
#define ADRENO_DEVFREQ_NOTIFY_RETIRE 2
#define ADRENO_DEVFREQ_NOTIFY_IDLE 3
struct device;
int kgsl_devfreq_add_notifier(struct device *, struct notifier_block *);
int kgsl_devfreq_del_notifier(struct device *, struct notifier_block *);
/* same as KGSL_MAX_PWRLEVELS */
#define MSM_ADRENO_MAX_PWRLEVELS 10
struct devfreq_msm_adreno_tz_data {
struct notifier_block nb;
struct {
s64 total_time;
s64 busy_time;
} bin;
unsigned int device_id;
};
#endif