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