blob: 02ada386fcbf2ac04cf2b5fa9e85c1aaf21e614c [file] [log] [blame]
/* Copyright (c) 2010-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/export.h>
#include <linux/kernel.h>
#include <asm/page.h>
#include "kgsl.h"
#include "kgsl_pwrscale.h"
#include "kgsl_device.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
#ifdef CONFIG_MSM_DCVS
&kgsl_pwrscale_policy_msm,
#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)
void kgsl_pwrscale_sleep(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->sleep)
device->pwrscale.policy->sleep(device, &device->pwrscale);
}
EXPORT_SYMBOL(kgsl_pwrscale_sleep);
void kgsl_pwrscale_wake(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->wake)
device->pwrscale.policy->wake(device, &device->pwrscale);
}
EXPORT_SYMBOL(kgsl_pwrscale_wake);
void kgsl_pwrscale_busy(struct kgsl_device *device)
{
if (PWRSCALE_ACTIVE(device) && device->pwrscale.policy->busy)
device->pwrscale.policy->busy(device,
&device->pwrscale);
}
void kgsl_pwrscale_idle(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);
void kgsl_pwrscale_disable(struct kgsl_device *device)
{
device->pwrscale.enabled = 0;
}
EXPORT_SYMBOL(kgsl_pwrscale_disable);
void kgsl_pwrscale_enable(struct kgsl_device *device)
{
device->pwrscale.enabled = 1;
}
EXPORT_SYMBOL(kgsl_pwrscale_enable);
int kgsl_pwrscale_policy_add_files(struct kgsl_device *device,
struct kgsl_pwrscale *pwrscale,
struct attribute_group *attr_group)
{
int ret;
ret = kobject_add(&pwrscale->kobj, &device->pwrscale_kobj,
"%s", pwrscale->policy->name);
if (ret)
return ret;
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);
/*
* Try to set max pwrlevel which will be limited to thermal by
* kgsl_pwrctrl_pwrlevel_change if thermal is indeed lower
*/
kgsl_pwrctrl_pwrlevel_change(device,
device->pwrctrl.max_pwrlevel);
}
device->pwrscale.policy = NULL;
}
void kgsl_pwrscale_detach_policy(struct kgsl_device *device)
{
mutex_lock(&device->mutex);
_kgsl_pwrscale_detach_policy(device);
mutex_unlock(&device->mutex);
}
EXPORT_SYMBOL(kgsl_pwrscale_detach_policy);
int kgsl_pwrscale_attach_policy(struct kgsl_device *device,
struct kgsl_pwrscale_policy *policy)
{
int ret = 0;
mutex_lock(&device->mutex);
if (device->pwrscale.policy == policy)
goto done;
if (device->pwrctrl.num_pwrlevels < 3) {
ret = -EINVAL;
goto done;
}
if (device->pwrscale.policy != NULL)
_kgsl_pwrscale_detach_policy(device);
device->pwrscale.policy = policy;
/* Pwrscale is enabled by default at attach time */
kgsl_pwrscale_enable(device);
if (policy) {
ret = device->pwrscale.policy->init(device, &device->pwrscale);
if (ret)
device->pwrscale.policy = NULL;
}
done:
mutex_unlock(&device->mutex);
return ret;
}
EXPORT_SYMBOL(kgsl_pwrscale_attach_policy);
int kgsl_pwrscale_init(struct kgsl_device *device)
{
int ret;
ret = kobject_init_and_add(&device->pwrscale_kobj, &ktype_pwrscale,
&device->dev->kobj, "pwrscale");
if (ret)
return ret;
kobject_init(&device->pwrscale.kobj, &ktype_pwrscale_policy);
return ret;
}
EXPORT_SYMBOL(kgsl_pwrscale_init);
void kgsl_pwrscale_close(struct kgsl_device *device)
{
kobject_put(&device->pwrscale_kobj);
}
EXPORT_SYMBOL(kgsl_pwrscale_close);