198 lines
4.8 KiB
C
198 lines
4.8 KiB
C
/* Copyright (c) 2010-2011, Code Aurora Forum. 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/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.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;
|
|
unsigned int no_switch_cnt;
|
|
unsigned int skip_cnt;
|
|
};
|
|
|
|
#define SWITCH_OFF 200
|
|
#define SWITCH_OFF_RESET_TH 40
|
|
#define SKIP_COUNTER 500
|
|
#define TZ_RESET_ID 0x3
|
|
#define TZ_UPDATE_ID 0x4
|
|
|
|
#ifdef CONFIG_MSM_SCM
|
|
/* Trap into the TrustZone, and call funcs there. */
|
|
static int __secure_tz_entry(u32 cmd, u32 val)
|
|
{
|
|
__iowmb();
|
|
return scm_call_atomic1(SCM_SVC_IO, cmd, val);
|
|
}
|
|
#else
|
|
static int __secure_tz_entry(u32 cmd, u32 val)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_MSM_SCM */
|
|
|
|
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->thermal_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.thermal_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;
|
|
|
|
/* In "performance" mode the clock speed always stays
|
|
the same */
|
|
|
|
if (priv->governor == TZ_GOVERNOR_PERFORMANCE)
|
|
return;
|
|
|
|
device->ftbl->power_stats(device, &stats);
|
|
if (stats.total_time == 0)
|
|
return;
|
|
|
|
/* If the GPU has stayed in turbo mode for a while, *
|
|
* stop writing out values. */
|
|
if (pwr->active_pwrlevel == 0) {
|
|
if (priv->no_switch_cnt > SWITCH_OFF) {
|
|
priv->skip_cnt++;
|
|
if (priv->skip_cnt > SKIP_COUNTER) {
|
|
priv->no_switch_cnt -= SWITCH_OFF_RESET_TH;
|
|
priv->skip_cnt = 0;
|
|
}
|
|
return;
|
|
}
|
|
priv->no_switch_cnt++;
|
|
} else {
|
|
priv->no_switch_cnt = 0;
|
|
}
|
|
|
|
val = __secure_tz_entry(TZ_UPDATE_ID,
|
|
stats.total_time - stats.busy_time);
|
|
if (val)
|
|
kgsl_pwrctrl_pwrlevel_change(device,
|
|
pwr->active_pwrlevel + val);
|
|
}
|
|
|
|
static void tz_sleep(struct kgsl_device *device,
|
|
struct kgsl_pwrscale *pwrscale)
|
|
{
|
|
struct tz_priv *priv = pwrscale->priv;
|
|
|
|
__secure_tz_entry(TZ_RESET_ID, 0);
|
|
priv->no_switch_cnt = 0;
|
|
}
|
|
|
|
static int tz_init(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale)
|
|
{
|
|
struct tz_priv *priv;
|
|
|
|
/* Trustzone is only valid for some SOCs */
|
|
if (!(cpu_is_msm8x60() || cpu_is_msm8960() || cpu_is_msm8930()))
|
|
return -EINVAL;
|
|
|
|
priv = pwrscale->priv = kzalloc(sizeof(struct tz_priv), GFP_KERNEL);
|
|
if (pwrscale->priv == NULL)
|
|
return -ENOMEM;
|
|
|
|
priv->governor = TZ_GOVERNOR_ONDEMAND;
|
|
kgsl_pwrscale_policy_add_files(device, pwrscale, &tz_attr_group);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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,
|
|
.idle = tz_idle,
|
|
.sleep = tz_sleep,
|
|
.wake = tz_wake,
|
|
.close = tz_close
|
|
};
|
|
EXPORT_SYMBOL(kgsl_pwrscale_policy_tz);
|