android_kernel_samsung_hero.../drivers/cpufreq/cpufreq_limit.c
2016-08-17 16:41:52 +08:00

705 lines
17 KiB
C

/*
* drivers/cpufreq/cpufreq_limit.c
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* Minsung Kim <ms925.kim@samsung.com>
* Chiwoong Byun <woong.byun@samsung.com>
* - 2014/10/24 Add HMP feature to support HMP
* SangYoung Son <hello.son@samsung.com>
* - 2015/08/06 Support MSM8996(same freq table HMP(Gold, Silver))
* - Use opp table for voltage power efficiency
*
* 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.
*/
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/cpufreq.h>
#include <linux/cpufreq_limit.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/suspend.h>
#include <linux/cpu.h>
#include <soc/qcom/msm_cpu_voltage.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
/*
* reduce voltage gap between gold and silver cluster
* find optimal silver clock from voltage of gold clock
*/
#define USE_MATCH_CPU_VOL_FREQ
#if defined(USE_MATCH_CPU_VOL_FREQ)
#define USE_MATCH_CPU_VOL_MAX_FREQ /* max_limit, thermal case */
#undef USE_MATCH_CPU_VOL_MIN_FREQ /* min_limit, boosting case */
#endif
struct cpufreq_limit_handle {
struct list_head node;
unsigned long min;
unsigned long max;
char label[20];
};
static DEFINE_MUTEX(cpufreq_limit_lock);
static LIST_HEAD(cpufreq_limit_requests);
/**
* cpufreq_limit_get - limit min_freq or max_freq, return cpufreq_limit_handle
* @min_freq limit minimum frequency (0: none)
* @max_freq limit maximum frequency (0: none)
* @label a literal description string of this request
*/
struct cpufreq_limit_handle *cpufreq_limit_get(unsigned long min_freq,
unsigned long max_freq, char *label)
{
struct cpufreq_limit_handle *handle;
int i, found;
if (max_freq && max_freq < min_freq)
return ERR_PTR(-EINVAL);
mutex_lock(&cpufreq_limit_lock);
found = 0;
list_for_each_entry(handle, &cpufreq_limit_requests, node) {
if (!strncmp(handle->label, label, strlen(handle->label))) {
found = 1;
pr_debug("%s: %s is found in list\n", __func__, handle->label);
break;
}
}
if (found) {
if (handle->min == min_freq
&& handle->max == max_freq) {
pr_info("%s: %s same values(%lu,%lu) entered. just return.\n",
__func__, handle->label, handle->min, handle->max);
mutex_unlock(&cpufreq_limit_lock);
return handle;
}
}
if (!found) {
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle) {
pr_err("%s: alloc fail for %s .\n", __func__, label);
mutex_unlock(&cpufreq_limit_lock);
return ERR_PTR(-ENOMEM);
}
if (strlen(label) < sizeof(handle->label))
strcpy(handle->label, label);
else
strncpy(handle->label, label, sizeof(handle->label) - 1);
list_add_tail(&handle->node, &cpufreq_limit_requests);
}
handle->min = min_freq;
handle->max = max_freq;
pr_debug("%s: %s,%lu,%lu\n", __func__, handle->label, handle->min,
handle->max);
mutex_unlock(&cpufreq_limit_lock);
/* Re-evaluate policy to trigger adjust notifier for online CPUs */
get_online_cpus();
for_each_online_cpu(i)
cpufreq_update_policy(i);
put_online_cpus();
return handle;
}
/**
* cpufreq_limit_put - release of a limit of min_freq or max_freq, free
* a cpufreq_limit_handle
* @handle a cpufreq_limit_handle that has been requested
*/
int cpufreq_limit_put(struct cpufreq_limit_handle *handle)
{
int i;
if (handle == NULL || IS_ERR(handle))
return -EINVAL;
pr_debug("%s: %s,%lu,%lu\n", __func__, handle->label, handle->min,
handle->max);
mutex_lock(&cpufreq_limit_lock);
list_del(&handle->node);
mutex_unlock(&cpufreq_limit_lock);
get_online_cpus();
for_each_online_cpu(i)
cpufreq_update_policy(i);
put_online_cpus();
kfree(handle);
return 0;
}
#ifdef CONFIG_SCHED_HMP
struct cpufreq_limit_hmp {
unsigned int little_cpu_start;
unsigned int little_cpu_end;
unsigned int big_cpu_start;
unsigned int big_cpu_end;
unsigned long big_min_freq;
unsigned long big_max_freq;
unsigned long little_min_freq;
unsigned long little_max_freq;
unsigned long little_min_lock;
unsigned int little_divider;
unsigned int hmp_boost_type;
unsigned int hmp_boost_active;
/* set limit of max freq limit for guarantee performance */
unsigned int little_limit_max_freq;
};
struct cpufreq_limit_hmp hmp_param = {
.little_cpu_start = 0,
.little_cpu_end = 1,
.big_cpu_start = 2,
.big_cpu_end = 3,
.big_min_freq = 307200,
.big_max_freq = 2150400,
.little_min_freq = 307200,
.little_max_freq = 1593600,
.little_min_lock = 960000 / 1, /* devide value is little_divider */
.little_divider = 1,
.hmp_boost_type = 1, /* 0: disable */
.hmp_boost_active = 0,
.little_limit_max_freq = 1190400,
};
void cpufreq_limit_corectl(int freq)
{
unsigned int cpu;
bool bigout = false;
/* if div is 1, do not core control */
if (hmp_param.little_divider == 1)
return;
if ((freq > -1) && (freq < hmp_param.big_min_freq))
bigout = true;
for_each_possible_cpu(cpu) {
if ((cpu >= hmp_param.big_cpu_start) &&
(cpu <= hmp_param.big_cpu_end)) {
if (bigout)
cpu_down(cpu);
else
cpu_up(cpu);
}
}
}
/**
* cpufreq_limit_get_table - fill the cpufreq table to support HMP
* @buf a buf that has been requested to fill the cpufreq table
*/
ssize_t cpufreq_limit_get_table(char *buf)
{
ssize_t len = 0;
int i, count = 0;
unsigned int freq;
struct cpufreq_frequency_table *table;
/* BIG cluster table */
table = cpufreq_frequency_get_table(hmp_param.big_cpu_start);
if (table == NULL)
return 0;
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++)
count = i;
for (i = count; i >= 0; i--) {
freq = table[i].frequency;
if (freq == CPUFREQ_ENTRY_INVALID)
continue;
if (freq < hmp_param.big_min_freq || freq > hmp_param.big_max_freq)
continue;
len += sprintf(buf + len, "%u ", freq);
}
/* if div is 1, use only big cluster freq table */
if (hmp_param.little_divider == 1)
goto done;
/* Little cluster table */
table = cpufreq_frequency_get_table(hmp_param.little_cpu_start);
if (table == NULL)
return 0;
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++)
count = i;
for (i = count; i >= 0; i--) {
freq = table[i].frequency / hmp_param.little_divider;
if (freq == CPUFREQ_ENTRY_INVALID)
continue;
if (freq < hmp_param.little_min_freq || freq > hmp_param.little_max_freq)
continue;
len += sprintf(buf + len, "%u ", freq);
}
done:
len--;
len += sprintf(buf + len, "\n");
return len;
}
static inline int is_little(unsigned int cpu)
{
return cpu >= hmp_param.little_cpu_start &&
cpu <= hmp_param.little_cpu_end;
}
static inline int is_big(unsigned int cpu)
{
return cpu >= hmp_param.big_cpu_start &&
cpu <= hmp_param.big_cpu_end;
}
static int set_little_divider(struct cpufreq_policy *policy, unsigned long *v)
{
if (is_little(policy->cpu))
*v /= hmp_param.little_divider;
return 0;
}
static int cpufreq_limit_hmp_boost(int enable)
{
unsigned int ret = 0;
pr_debug("%s: enable=%d, type=%d, active=%d\n", __func__,
enable, hmp_param.hmp_boost_type, hmp_param.hmp_boost_active);
if (enable) {
if (hmp_param.hmp_boost_type && !hmp_param.hmp_boost_active) {
hmp_param.hmp_boost_active = enable;
ret = sched_set_boost(1);
if (ret)
pr_err("%s: HMP boost enable failed\n", __func__);
}
}
else {
if (hmp_param.hmp_boost_type && hmp_param.hmp_boost_active) {
hmp_param.hmp_boost_active = 0;
ret = sched_set_boost(0);
if (ret)
pr_err("%s: HMP boost disable failed\n", __func__);
}
}
return ret;
}
static int cpufreq_limit_adjust_freq(struct cpufreq_policy *policy,
unsigned long *min, unsigned long *max)
{
unsigned int hmp_boost_active = 0;
pr_debug("%s+: cpu=%d, min=%ld, max=%ld\n", __func__, policy->cpu, *min, *max);
if (is_little(policy->cpu)) { /* Little */
if (*min >= hmp_param.big_min_freq) { /* Big clock */
*min = hmp_param.little_min_lock * hmp_param.little_divider;
}
else { /* Little clock */
*min *= hmp_param.little_divider;
}
if (*max >= hmp_param.big_min_freq) { /* Big clock */
*max = policy->cpuinfo.max_freq;
}
else { /* Little clock */
*max *= hmp_param.little_divider;
}
}
else { /* BIG */
if (*min >= hmp_param.big_min_freq) { /* Big clock */
hmp_boost_active = 1;
}
else { /* Little clock */
*min = policy->cpuinfo.min_freq;
hmp_boost_active = 0;
}
if (*max >= hmp_param.big_min_freq) { /* Big clock */
pr_debug("%s: big_min_freq=%ld, max=%ld\n", __func__,
hmp_param.big_min_freq, *max);
}
else { /* Little clock */
*max = policy->cpuinfo.min_freq;
hmp_boost_active = 0;
}
cpufreq_limit_hmp_boost(hmp_boost_active);
}
pr_debug("%s-: cpu=%d, min=%ld, max=%ld\n", __func__, policy->cpu, *min, *max);
return 0;
}
#else
static inline int cpufreq_limit_adjust_freq(struct cpufreq_policy *policy,
unsigned long *min, unsigned long *max) { return 0; }
static inline int cpufreq_limit_hmp_boost(int enable) { return 0; }
static inline int set_little_divider(struct cpufreq_policy *policy,
unsigned long *v) { return 0; }
#endif /* CONFIG_SCHED_HMP */
static int cpufreq_limit_notifier_policy(struct notifier_block *nb,
unsigned long val, void *data)
{
struct cpufreq_policy *policy = data;
struct cpufreq_limit_handle *handle;
unsigned long min = 0, max = ULONG_MAX;
#if defined(USE_MATCH_CPU_VOL_MIN_FREQ)
unsigned long adjust_min = 0;
#endif
#if defined(USE_MATCH_CPU_VOL_MAX_FREQ)
unsigned long adjust_max = 0;
#endif
if (val != CPUFREQ_ADJUST)
goto done;
mutex_lock(&cpufreq_limit_lock);
list_for_each_entry(handle, &cpufreq_limit_requests, node) {
if (handle->min > min)
min = handle->min;
if (handle->max && handle->max < max)
max = handle->max;
}
#ifdef CONFIG_SEC_PM
pr_debug("CPUFREQ(%d): %s: umin=%d,umax=%d\n",
policy->cpu, __func__, policy->user_policy.min, policy->user_policy.max);
#ifndef CONFIG_SCHED_HMP /* TODO */
if (policy->user_policy.min > min)
min = policy->user_policy.min;
if (policy->user_policy.max && policy->user_policy.max < max)
max = policy->user_policy.max;
#endif
#endif
mutex_unlock(&cpufreq_limit_lock);
#if defined(USE_MATCH_CPU_VOL_FREQ)
if (is_little(policy->cpu)) {
#if defined(USE_MATCH_CPU_VOL_MIN_FREQ)
/* boost case */
if (min)
adjust_min = msm_match_cpu_voltage(min);
if (adjust_min)
min = adjust_min;
#endif
#if defined(USE_MATCH_CPU_VOL_MAX_FREQ)
/* thermal case */
if (max && (max < hmp_param.little_max_freq) &&
(max >= hmp_param.little_limit_max_freq)) {
adjust_max = msm_match_cpu_voltage(max);
if (adjust_max)
max = MAX(adjust_max,
hmp_param.little_limit_max_freq);
}
#endif
}
#endif
if (!min && max == ULONG_MAX) {
cpufreq_limit_hmp_boost(0);
goto done;
}
if (!min) {
min = policy->cpuinfo.min_freq;
set_little_divider(policy, &min);
}
if (max == ULONG_MAX) {
max = policy->cpuinfo.max_freq;
set_little_divider(policy, &max);
}
/*
* little_divider 1 means,
* little and big has similar power and performance case
* e.g. MSM8996 silver and gold
*/
if (hmp_param.little_divider == 1) {
if (is_big(policy->cpu)) {
/* sched_boost scenario */
if (min > hmp_param.little_max_freq)
cpufreq_limit_hmp_boost(1);
else
cpufreq_limit_hmp_boost(0);
}
} else {
cpufreq_limit_adjust_freq(policy, &min, &max);
}
pr_debug("%s: limiting cpu%d cpufreq to %lu-%lu\n", __func__,
policy->cpu, min, max);
cpufreq_verify_within_limits(policy, min, max);
pr_debug("%s: limited cpu%d cpufreq to %u-%u\n", __func__,
policy->cpu, policy->min, policy->max);
done:
return 0;
}
static struct notifier_block notifier_policy_block = {
.notifier_call = cpufreq_limit_notifier_policy,
.priority = -1,
};
/************************** sysfs begin ************************/
static ssize_t show_cpufreq_limit_requests(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct cpufreq_limit_handle *handle;
ssize_t len = 0;
mutex_lock(&cpufreq_limit_lock);
list_for_each_entry(handle, &cpufreq_limit_requests, node) {
len += sprintf(buf + len, "%s\t%lu\t%lu\n", handle->label,
handle->min, handle->max);
}
mutex_unlock(&cpufreq_limit_lock);
return len;
}
static struct global_attr cpufreq_limit_requests_attr =
__ATTR(cpufreq_limit_requests, 0444, show_cpufreq_limit_requests, NULL);
#ifdef CONFIG_SCHED_HMP
#define MAX_ATTRIBUTE_NUM 12
#define show_one(file_name, object) \
static ssize_t show_##file_name \
(struct kobject *kobj, struct attribute *attr, char *buf) \
{ \
return sprintf(buf, "%u\n", hmp_param.object); \
}
#define show_one_ulong(file_name, object) \
static ssize_t show_##file_name \
(struct kobject *kobj, struct attribute *attr, char *buf) \
{ \
return sprintf(buf, "%lu\n", hmp_param.object); \
}
#define store_one(file_name, object) \
static ssize_t store_##file_name \
(struct kobject *a, struct attribute *b, const char *buf, size_t count) \
{ \
int ret; \
\
ret = sscanf(buf, "%lu", &hmp_param.object); \
if (ret != 1) \
return -EINVAL; \
\
return count; \
}
static ssize_t show_little_cpu_num(struct kobject *kobj, struct attribute *attr, char *buf)
{
return sprintf(buf, "%u-%u\n", hmp_param.little_cpu_start, hmp_param.little_cpu_end);
}
static ssize_t show_big_cpu_num(struct kobject *kobj, struct attribute *attr, char *buf)
{
return sprintf(buf, "%u-%u\n", hmp_param.big_cpu_start, hmp_param.big_cpu_end);
}
show_one_ulong(big_min_freq, big_min_freq);
show_one_ulong(big_max_freq, big_max_freq);
show_one_ulong(little_min_freq, little_min_freq);
show_one_ulong(little_max_freq, little_max_freq);
show_one_ulong(little_min_lock, little_min_lock);
show_one(little_divider, little_divider);
show_one(hmp_boost_type, hmp_boost_type);
static ssize_t store_little_cpu_num(struct kobject *a, struct attribute *b,
const char *buf, size_t count)
{
unsigned int input, input2;
int ret;
ret = sscanf(buf, "%u-%u", &input, &input2);
if (ret != 2)
return -EINVAL;
if (input >= MAX_ATTRIBUTE_NUM || input2 >= MAX_ATTRIBUTE_NUM)
return -EINVAL;
pr_info("%s: %u-%u, ret=%d\n", __func__, input, input2, ret);
hmp_param.little_cpu_start = input;
hmp_param.little_cpu_end = input2;
return count;
}
static ssize_t store_big_cpu_num(struct kobject *a, struct attribute *b,
const char *buf, size_t count)
{
unsigned int input, input2;
int ret;
ret = sscanf(buf, "%u-%u", &input, &input2);
if (ret != 2)
return -EINVAL;
if (input >= MAX_ATTRIBUTE_NUM || input2 >= MAX_ATTRIBUTE_NUM)
return -EINVAL;
pr_info("%s: %u-%u, ret=%d\n", __func__, input, input2, ret);
hmp_param.big_cpu_start = input;
hmp_param.big_cpu_end = input2;
return count;
}
store_one(big_min_freq, big_min_freq);
store_one(big_max_freq, big_max_freq);
store_one(little_min_freq, little_min_freq);
store_one(little_max_freq, little_max_freq);
store_one(little_min_lock, little_min_lock);
static ssize_t store_little_divider(struct kobject *a, struct attribute *b,
const char *buf, size_t count)
{
unsigned int input;
int ret;
ret = sscanf(buf, "%u", &input);
if (ret != 1)
return -EINVAL;
if (input >= MAX_ATTRIBUTE_NUM)
return -EINVAL;
hmp_param.little_divider = input;
return count;
}
static ssize_t store_hmp_boost_type(struct kobject *a, struct attribute *b,
const char *buf, size_t count)
{
unsigned int input;
int ret;
ret = sscanf(buf, "%u", &input);
if (ret != 1)
return -EINVAL;
if (input > 2)
return -EINVAL;
hmp_param.hmp_boost_type = input;
return count;
}
define_one_global_rw(little_cpu_num);
define_one_global_rw(big_cpu_num);
define_one_global_rw(big_min_freq);
define_one_global_rw(big_max_freq);
define_one_global_rw(little_min_freq);
define_one_global_rw(little_max_freq);
define_one_global_rw(little_min_lock);
define_one_global_rw(little_divider);
define_one_global_rw(hmp_boost_type);
#endif /* CONFIG_SCHED_HMP */
static struct attribute *limit_attributes[] = {
&cpufreq_limit_requests_attr.attr,
#ifdef CONFIG_SCHED_HMP
&little_cpu_num.attr,
&big_cpu_num.attr,
&big_min_freq.attr,
&big_max_freq.attr,
&little_min_freq.attr,
&little_max_freq.attr,
&little_min_lock.attr,
&little_divider.attr,
&hmp_boost_type.attr,
#endif
NULL,
};
static struct attribute_group limit_attr_group = {
.attrs = limit_attributes,
.name = "cpufreq_limit",
};
/************************** sysfs end ************************/
static int __init cpufreq_limit_init(void)
{
int ret;
ret = cpufreq_register_notifier(&notifier_policy_block,
CPUFREQ_POLICY_NOTIFIER);
if (ret)
return ret;
ret = cpufreq_get_global_kobject();
if (!ret) {
ret = sysfs_create_group(cpufreq_global_kobject,
&limit_attr_group);
if (ret)
cpufreq_put_global_kobject();
}
return ret;
}
static void __exit cpufreq_limit_exit(void)
{
cpufreq_unregister_notifier(&notifier_policy_block,
CPUFREQ_POLICY_NOTIFIER);
sysfs_remove_group(cpufreq_global_kobject, &limit_attr_group);
cpufreq_put_global_kobject();
}
MODULE_AUTHOR("Minsung Kim <ms925.kim@samsung.com>");
MODULE_DESCRIPTION("'cpufreq_limit' - A driver to limit cpu frequency");
MODULE_LICENSE("GPL");
module_init(cpufreq_limit_init);
module_exit(cpufreq_limit_exit);